Compare commits

...

294 Commits

Author SHA1 Message Date
poka 68c8a61939 add gdev.p2p.legal endpoint 2022-11-27 03:30:27 +01:00
poka 1878ea5f63 bump v0.0.12 2022-11-27 02:10:00 +01:00
poka 41b9db0dfb Gecko is now pluggued to indexer v0.3.0 with g1 history data 2022-11-27 01:59:58 +01:00
poka 61d28e79d6 flutter: upgrade to 3.3.9 2022-11-27 01:39:59 +01:00
pokapow 4f07c390bd Merge branch 'fix_anErrorOccured' into 'master'
Fix typo: anErrorOccurred

See merge request clients/gecko!36
2022-11-26 16:38:05 +01:00
Eloi Torrents 58c82069d4 Fix typo: anErrorOccurred 2022-11-24 10:08:02 +01:00
pokapow e0d1d3a085 Merge branch 'es_translations' into 'master'
Es translations

See merge request clients/gecko!35
2022-11-24 01:22:41 +01:00
Eloi Torrents 66dca555ab Add es translations 2022-11-24 00:19:22 +00:00
pokapow 54b721627b Merge branch 'fix_typos' into 'master'
Fix typos

See merge request clients/gecko!34
2022-11-24 01:18:22 +01:00
Eloi Torrents 985c888a47 Fix typos 2022-11-23 14:13:26 +01:00
guenoel cbeaacfca8 Merge branch 'ios-integration' into 'master'
Ios integration

See merge request clients/gecko!33
2022-11-16 15:54:34 +01:00
guenoel 32beb33be4 remove macos and windows deps 2022-11-16 15:50:18 +01:00
guenoel 459a76452b update ios deps 2022-11-16 15:49:49 +01:00
guenoel 57d68e012d update gitignore again 2022-11-16 15:47:38 +01:00
guenoel ae30355b9b gitignore mac, ios & windows 2022-11-16 15:30:49 +01:00
pokapow ebc69445ed Merge branch 'calculTxFees' into 'master'
Calcul tx fees

See merge request clients/gecko!32
2022-11-16 15:18:37 +01:00
poka be52158959 fix: unused method 2022-11-16 14:30:09 +01:00
poka 228e556198 fix: flutter format 2022-11-16 13:49:13 +01:00
poka 2c7dcd0ee6 try to fix drag feedback offset again 2022-11-16 13:39:42 +01:00
poka aa917b1c25 try to fix drag feedback offset 2022-09-23 21:41:41 +02:00
poka a0b0423856 estimate fees are ok and diplayed to user 2022-09-14 15:57:18 +02:00
poka 6443e04191 apply const colors 2022-09-12 12:38:32 +02:00
poka bf58e8e3d1 refactor Ud ratio compute 2022-09-12 12:04:08 +02:00
poka a0d6bfaf22 Merge branch 'codemetrics' 2022-09-12 11:35:24 +02:00
poka 0728bc6aba codemetrics: apply check-unused-files and check-unused-code 2022-09-12 11:32:41 +02:00
poka d927ccea39 install codemetric; apply changes for check-unnecessary-nullable 2022-09-12 11:22:26 +02:00
poka 2b0a0428a0 hot fix: bad UD ratio 2022-09-12 08:52:27 +02:00
poka eb9275fa84 bump v0.0.11+30 2022-09-12 08:36:59 +02:00
pokapow 09e1592f55 Merge branch 'dragNDropWalletsTransfert' into 'master'
Drag n drop wallets transfert

See merge request clients/gecko!31
2022-09-12 08:36:12 +02:00
poka a432db334d remove unused commented code 2022-09-12 08:28:19 +02:00
poka ad0019522f improve payment popup 2022-09-12 08:24:13 +02:00
poka d76ad9b7e3 improve dargndrop UX 2022-09-12 07:26:13 +02:00
poka 8b32bb8e26 payment inter wallets is working; bug with feedback position 2022-09-12 04:52:34 +02:00
poka edbe1d8b5c bump v0.0.11+28 2022-09-11 15:07:27 +02:00
pokapow 7f5dbf04ee Merge branch 'implementUdUnit' into 'master'
Implement ud unit

See merge request clients/gecko!30
2022-09-11 14:10:31 +02:00
poka 57cdc23436 fix bad balance displayed 2022-09-11 14:04:52 +02:00
poka 0233816f68 implement UD unit view 2022-09-11 13:14:52 +02:00
poka 776a0f8c75 improve migration text infos 2022-09-11 07:53:34 +02:00
pokapow 164fc16ed8 Merge branch 'revocation-mechanisme' into 'master'
Revocation mechanisme

See merge request clients/gecko!29
2022-09-10 06:35:22 +02:00
poka 83189990c6 test: verify you cannot revoke a smith member 2022-09-10 06:27:27 +02:00
poka 0d54030c1a can't revoke if smith member and explain why 2022-09-10 06:26:49 +02:00
poka 147557f545 increase transaction timeout to 18s 2022-09-09 10:41:53 +02:00
poka fd281ad822 add script to launch all tests 2022-09-09 01:39:11 +02:00
poka f7e3066352 fix test revocation 2022-09-09 01:12:17 +02:00
poka e2ed9c5c98 fix identity revocation call 2022-09-08 20:52:43 +02:00
poka 3825bf0488 fix revocation payload 2022-09-08 20:15:19 +02:00
poka e752de474d fix some tx bad status 2022-09-08 20:14:45 +02:00
poka 23b6858a60 add test: identity revocation 2022-09-08 14:37:00 +02:00
poka 8663186a4a wip: prepare extrinsic revocation 2022-09-06 12:14:50 +02:00
poka f46acb23e4 setSender methode 2022-09-06 09:29:29 +02:00
pokapow 549996578b Merge branch 'end2EndTests' into 'master'
End2end tests

See merge request clients/gecko!28
2022-09-05 09:47:27 +02:00
poka 34c69b64ee bump CI v0.0.11 2022-09-05 09:42:44 +02:00
poka 0fe0c4d3c5 remove unused deps 2022-09-05 08:35:24 +02:00
poka 03a9f1ea19 fix flutter format 2022-09-05 08:12:24 +02:00
poka d9222fe6ba add todo fix random bad widget ancestor after onboarding 2022-09-05 08:09:12 +02:00
poka 58572a5e0e migrate flutter 3.3.0 2022-09-05 04:15:27 +02:00
Hugo Trentesaux 7491b9b489 Migration docker compose 2022-09-01 16:43:55 +02:00
poka ad9ee382d3 wip: test onboarding 2022-09-01 04:51:51 +02:00
pokapow e01dea9329 Mettre à jour integration_test/README.md 2022-08-31 23:22:23 +02:00
poka 425677827c Add readme fo integration_tests 2022-08-31 23:21:11 +02:00
poka a8dbf79eaa improve test migrate Cesium wallet identity: check if balance is well transfered 2022-08-29 20:51:45 +02:00
poka 37dee7f866 fix: transferAll in batch for identity migration 2022-08-29 20:50:43 +02:00
poka 9301eaf1a7 add test: migrate Cesium wallet identity 2022-08-29 06:03:05 +02:00
poka 939c6b3108 remove old integration test bash script 2022-08-28 05:15:58 +02:00
poka 03ab85a282 refactor test utility; add ud creation state test 2022-08-28 05:08:41 +02:00
poka 525c375c71 add test: certs state on wallet view 2022-08-27 23:27:02 +02:00
poka 18743c871b fix: set good sender for cert action 2022-08-27 23:26:37 +02:00
poka 3761494606 refactor tests env 2022-08-27 05:43:43 +02:00
poka aa7bc74645 add .env to git repository with default value 2022-08-27 00:31:19 +02:00
poka 05f41e16a9 improve test speed 2022-08-25 00:34:27 +02:00
poka c060c85df8 flutter format 2022-08-24 23:59:51 +02:00
poka 27e4de7254 add coments to importChest test 2022-08-24 23:09:56 +02:00
poka a03f711539 add certtification workflow test 2022-08-24 21:53:16 +02:00
poka fbc8cbd0d9 improve spawnBlock in tests 2022-08-24 06:44:40 +02:00
poka 5744155400 can spawn new block just after transactions 2022-08-24 06:36:26 +02:00
poka afae4130a6 add script to launch duniter local node before integration tests 2022-08-23 23:46:01 +02:00
poka fc80f5693c test: execute a transaction to ChristCosmic 2022-08-23 02:13:54 +02:00
poka cbbd4eee34 refactoring: Declare keys for all widgets int widgets_keys.dart 2022-08-23 00:25:16 +02:00
poka dcb64c74b6 test integration is upgrade and working for restore chest 2022-08-22 22:58:54 +02:00
poka 9239901608 build: fix publish 2022-08-21 06:52:31 +02:00
poka 3abf2666ba can copy address from generated Cesium salt/password 2022-08-21 06:35:36 +02:00
poka ebc33e4f6c can auto push release with forum message 2022-08-21 06:35:06 +02:00
poka 2facd8cd7f lock identity name format on confirmation 2022-08-21 01:30:26 +02:00
poka dd49e76fa1 improve UX identity confirmation action 2022-08-21 00:44:23 +02:00
pokapow 5369599b20 Merge branch 'importCsAccounts' into 'master'
Import cs accounts

See merge request clients/gecko!27
2022-08-20 23:22:38 +02:00
poka ae5fe3b128 fix: flutter format 2022-08-20 23:18:59 +02:00
poka 3100dc5126 fix: bad rendering canCert if identity is created 2022-08-20 22:46:21 +02:00
poka 92992c2f69 fix cesium account import 2022-08-20 20:40:33 +02:00
poka ad601be19c can migrate identity from standart mnemonic derivation 2022-08-20 20:08:02 +02:00
poka c840753f4a identity migration is working 2022-08-20 16:55:55 +02:00
poka 99f939d5a2 WIP: message to sign is ok, but bad use of Uint8List signature 2022-08-19 23:15:41 +02:00
poka 506110cfc8 use binary concatenation instead of string 2022-08-19 19:23:38 +02:00
poka d42e715482 WIP: continue digging identity migration 2022-08-19 14:10:27 +02:00
poka 237e581d0f Ready to migrate identity, wait for signMessage methode in sdk
https://github.com/polkawallet-io/sdk/issues/31
2022-08-18 18:22:36 +02:00
poka 1b896960ea can migrate GD from Cesium wallets 2022-08-18 14:26:12 +02:00
poka dbfd033fba new screen for g1V1 salt/password import 2022-08-18 01:31:47 +02:00
poka 5ce381b174 fix: can't cert if identity is not confirmed 2022-08-17 23:28:19 +02:00
poka 0983c1d01e add messages about offline status in onboarding screens 2022-08-17 23:01:20 +02:00
poka 0687ae072f get onchain consts; fix batch validate membership 2022-08-17 17:50:05 +02:00
poka e8acb59f3e add ss58 test 2022-08-16 21:50:35 +02:00
poka 1995aaf4fb bump 0.0.9+22 2022-08-16 20:00:22 +02:00
poka 4a7f68626d fix: batch cert and validate membership 2022-08-16 19:59:53 +02:00
poka 4d027e0555 fix: import scanned wallets 2022-08-16 16:13:11 +02:00
poka 6af9199a76 add methode to import Cs account 2022-08-16 01:44:45 +02:00
poka 8a30ba70d7 wip: fix used chest import 2022-08-15 18:54:11 +02:00
poka 9a645e3733 bump v0.0.9+21 2022-08-15 18:09:03 +02:00
poka 1ed9f3d460 remove gd1 bootstrap endpoints 2022-08-15 18:05:07 +02:00
poka b546078c8f fix: sentry 3334937784 2022-08-15 17:59:12 +02:00
poka eaf81aa701 fix: sentry 3491636110 2022-08-15 17:31:17 +02:00
poka 3fad0f03f0 Add infos about current scan in last import screen 2022-08-15 12:58:47 +02:00
poka edb8735109 fix: scan derivation balances check 2022-08-15 12:35:19 +02:00
poka 5b0ea1b2af bump 0.0.9+19 2022-08-14 22:26:53 +02:00
poka d9aa66e1ec order contacts by usernames 2022-08-14 22:16:46 +02:00
poka c6d5b30090 add contacts management 2022-08-14 20:02:02 +02:00
poka 7fd82df733 remove unused sandbox screen 2022-08-14 18:10:03 +02:00
pokapow 6d55b551b1 Merge branch 'featuresGdev2' into 'master'
Features gdev2

See merge request clients/gecko!26
2022-08-14 17:04:37 +02:00
poka 7b778a74b1 check if node is connected before check balances 2022-08-14 16:34:24 +02:00
poka c603d2b201 fix: can delete wallet just after set it to default 2022-08-13 13:45:43 +02:00
poka 2d1b3731bb batch call is working for claimUd 2022-08-12 10:04:55 +02:00
poka 99c559d38c improve balance results; workaround batch (wip) 2022-08-11 19:19:50 +02:00
poka cd91ea838b refactor substrate_sdk.dart provider file; Add local node entry in settings 2022-08-11 15:47:08 +02:00
poka c487749f52 improve batch meca 2022-08-10 07:18:04 +02:00
poka 2526f382f1 type and lock balance computing 2022-08-09 12:02:02 +02:00
poka 2677cb8eb8 displayed balances are now transferabledBalances with unclaimed UDs 2022-08-09 11:26:45 +02:00
poka d71261d0ec WIP: unclaimed UD are almost calculated 2022-08-09 10:19:16 +02:00
poka 29754e23b5 prepare to implement unclaimedUd evaluation 2022-08-08 19:40:24 +02:00
poka 175334457a Add dead methode to get ss58 prefix from const storage; remove unused comments 2022-08-08 18:56:59 +02:00
poka a88034b206 Use certsByReceiver to gets certValidityPeriod instead of obsolete certsByReceiver call 2022-08-08 17:03:34 +02:00
poka 81772f923a implement batch for membership validation 2022-08-08 16:29:43 +02:00
poka d8f3936e20 fix: ignore certs state if dest identity is null 2022-08-08 15:03:22 +02:00
poka ef3ae9db4a upgrade to gradle 7.2.2 2022-08-08 13:43:49 +02:00
poka 68175924cb upgrade deps 2022-08-06 02:46:53 +02:00
poka dce9fc2117 fix gradle with new version 2022-08-06 02:40:26 +02:00
poka fad6ebd878 Upgrade dependencies; apply new lints infos 2022-08-06 02:22:41 +02:00
poka 4fb40ff3f1 fallback to english if user lang is not supported in debug mode 2022-08-06 01:45:06 +02:00
poka f44dd4aa76 add current bloc number in settings; remove Substrate debug menu 2022-07-22 22:36:11 +02:00
poka 10eeeba601 UI: Improve align of endpoints selection elements 2022-07-22 22:24:42 +02:00
poka a7d2e9a5d2 UI: Move QRCode to appbar in wallet option screen 2022-07-22 22:18:03 +02:00
poka c203364dc3 improve design of name + balance on wallet_home screen 2022-07-22 22:08:41 +02:00
poka 90f91be987 don't reset graphql cache on startup 2022-07-22 20:55:42 +02:00
pokapow f3cb9faca7 Merge branch 'manualNodeChange' into 'master'
wip: use dropdown to chose auto or manual endpoints

See merge request clients/gecko!25
2022-07-16 21:39:53 +02:00
poka e5ea67b6d0 bump +15 2022-07-16 21:25:08 +02:00
poka c5960f8376 can set custom indexer endpoint 2022-07-16 21:22:12 +02:00
poka b975061a67 change indexer endpoint in settings 2022-07-16 21:03:40 +02:00
poka 0f43100969 workflow for auto and custom endpoint is OK 2022-07-16 17:25:58 +02:00
poka c07cc9049b workaround dropdown 2022-07-14 18:18:19 +02:00
poka 5e5e6ee437 wip: use dropdown to chose auto or manual endpoints 2022-07-14 15:20:57 +02:00
pokapow b2af9daa3b Merge branch 'feature/publish-gecko-for-ios' into 'master'
Feature/publish gecko for ios

See merge request clients/gecko!24
2022-07-12 19:34:44 +02:00
poka d1976db24a add NSPhotoLibraryUsageDescription 2022-07-12 19:28:08 +02:00
cgeek ec953b380a feature(#27): add TeamID + `ITSAppUsesNonExemptEncryption` property 2022-07-12 19:28:03 +02:00
poka 183e89e32b fix: android theme 2022-07-09 00:00:37 +02:00
poka 366d2712fa bump +14 2022-07-08 23:48:49 +02:00
poka 39d87c765a fix: show no data message if history profile is empty 2022-07-08 23:48:07 +02:00
poka d25ad55b02 rollback to previous main.js version 2022-07-08 23:45:46 +02:00
poka 80ccc1f766 change github https sdk dependency 2022-07-08 20:18:34 +02:00
poka af3451b662 increase indexer timeout 2022-06-28 12:56:13 +02:00
poka c58d859d07 fix: Use _removableOn instead of _renewableOn for certification renewale delay (#13) 2022-06-28 12:23:14 +02:00
poka fd9467ef79 fix: cast double to int before sign and send transcation (#14) 2022-06-24 23:32:13 +02:00
poka e9c9019b50 remove todo 2022-06-18 16:39:40 +02:00
poka 51894fe6be flutter format 2022-06-18 03:21:54 +02:00
poka f02cd7d192 Merge branch 'easyLocalization' 2022-06-18 03:19:01 +02:00
poka b7b01bc45f bump version 2022-06-18 03:18:21 +02:00
poka a64a3c6948 Add Spanish template with a few translated sentences 2022-06-18 03:13:12 +02:00
poka e71e00c995 App is fully translate en/fr ! 2022-06-18 03:01:22 +02:00
Hugo Trentesaux 87a6f444b3 translation 4 2022-06-18 01:50:06 +02:00
Hugo Trentesaux c04416ae0e onboarding translation 3 2022-06-18 00:48:07 +02:00
Hugo Trentesaux c62c63f064 onboarding translation 2 2022-06-17 23:10:01 +02:00
Hugo Trentesaux f688bc39c7 onboarding translation 2022-06-17 22:19:14 +02:00
poka a4d72d036f factorize isMd text 2022-06-17 21:50:18 +02:00
poka 6f7dd4e413 cosmetic 2022-06-17 21:40:08 +02:00
poka 65f6f0a237 replace Multiple TextSpan for bold words by Markdown widget in onboarding 2022-06-17 21:34:47 +02:00
poka ad1a517553 fix flutter format 2022-06-17 20:18:54 +02:00
Hugo Trentesaux 8555c579bd welcome multilang! 2022-06-17 01:13:14 +02:00
poka b89de85e90 hello multilang ! 2022-06-16 22:29:42 +02:00
poka 1c3aa6997b change history to activity 2022-06-16 20:16:32 +02:00
poka 431089f42e Merge branch 'activityScreen' 2022-06-16 20:10:01 +02:00
poka b03a280356 improve headerProfileView: move to provider 2022-06-16 20:09:35 +02:00
poka a4f5b44a13 Hello Activity screen; paged history works for all profiles 2022-06-16 19:45:34 +02:00
poka 1c4da17db9 WIP: Refactore history screen to new activity screen 2022-06-16 02:43:30 +02:00
poka 4e9e2d40d1 change gif 3 2022-06-16 01:40:47 +02:00
poka 31619359d5 change gif demo2 2022-06-16 01:35:22 +02:00
poka e62f8fa69e change demo gif 2022-06-16 01:34:05 +02:00
poka f509703084 fix: deps for CI 2022-06-15 02:03:07 +02:00
poka 0781f26bc1 Merge branch 'implementIndexer' 2022-06-15 01:15:04 +02:00
poka 0ada1635c6 can search username in indexer 2022-06-15 01:14:23 +02:00
poka e90e3063ae fix flutter format 2022-06-14 22:10:35 +02:00
poka 58b6fba7ef gitlab-ci.yml; try to fix 2 2022-06-14 22:08:52 +02:00
poka 64e9ff1b44 gitlab-ci.yml; try to fix 2022-06-14 22:06:34 +02:00
poka d59d0c015a gitlab-ci.yml: remove comment 2022-06-14 22:00:15 +02:00
poka 05a7c82871 gitlab-ci.yml: remove space vefore comment 2022-06-14 21:59:32 +02:00
poka b196ed2bf1 check online indexer on startup^in bootstrap nodes; Don't use it in app if offline 2022-06-14 21:55:33 +02:00
poka 2ea2eeb255 wip indexer 2 2022-06-14 19:35:54 +02:00
poka d38cabe1eb wip indexer 2022-06-14 19:35:54 +02:00
poka 7f83965a8b Indexer client implemented; Display identity name when exist 2022-06-14 19:35:54 +02:00
poka cdfa62aad5 update CI image version; ignore build.gradle changes 2022-06-14 19:35:29 +02:00
poka 86e2f3b2d7 feat: crop circle avatar on import 2022-06-12 22:12:00 +02:00
poka 5ee80a5af4 feat: ask password for mnemonic display even if it's cached 2022-06-12 21:26:16 +02:00
poka 63c71fed01 feat: Edit wallet name in popup 2022-06-12 21:19:05 +02:00
poka 9ec7a6594d can't pay if offline 2022-06-12 19:56:43 +02:00
poka 1a49035cbf fix: bad parent widget in search; improve lost connection info 2022-06-12 19:43:13 +02:00
poka 7cbf328123 fix: check if node is connected before chest creation/import; Do not allow wallet generation if node is disconnected; live check 2022-06-12 19:20:15 +02:00
poka 7c11146278 Check network state, try to reconnect if network up, display message if offline 2022-06-12 18:03:17 +02:00
poka 29505ad1be update README: change roadmap 2022-06-10 23:16:42 +02:00
poka d62691d6f9 fix: null balance deleting check 2022-06-10 19:26:54 +02:00
poka fd5504eadd fix: paymentPopup: do not allow '.' or ',' in first 2022-06-10 18:07:30 +02:00
poka ed16565535 fix: allow comma in payment pop, replaced by point 2022-06-10 15:30:10 +02:00
poka 71d321700b restore chest: can use space to go next case 2022-06-10 01:24:50 +02:00
poka 4e69b945ed fix: can certify after renewable spend time 2022-06-09 23:42:20 +02:00
poka eeb7b03259 fix: change cacheBalance type 2022-06-09 23:08:28 +02:00
poka aebcbc6bdf add timeout to scanDerivation 2022-06-09 01:28:02 +02:00
poka 89b6527d10 bump polkawallet-sdk to change webview port from 8080 to 6234 2022-06-08 20:37:28 +02:00
poka 9962160608 set release keys 2022-06-08 15:32:44 +02:00
poka 69122f1d59 canDelete if balance == 0 or > 2 2022-06-08 04:52:20 +02:00
poka 89c52ab8d7 Delete wallet only if has no consumers 2022-06-08 04:24:06 +02:00
poka 20c5ebafcc canPay: detect creations fees if needed; can't empty your own wallet 2022-06-08 03:14:08 +02:00
poka 55f08dddd6 transferKeepAlive for all payments; transferAll to defaultWallet when deleting wallet with non null balance 2022-06-08 02:30:19 +02:00
poka 86467c06f3 Merge branch 'derivationsDetection' 2022-06-07 22:17:48 +02:00
poka b7817a3299 improve paste mnemonic button; set numberScan to 20 2022-06-07 22:16:56 +02:00
poka 263b99fb95 Scan derivation on import is OK 2022-06-07 21:36:57 +02:00
poka 4625fa1d70 fix the fix 2022-06-07 19:15:11 +02:00
poka 3f75fa5c20 fix: do not valid mnemonic word if others exist in import 2022-06-07 19:06:46 +02:00
poka d7fcf0636f wip: try to import existing wallets 2022-06-07 01:10:40 +02:00
poka a1767d289f improve member status view 2022-06-06 17:52:42 +02:00
poka 48e2b09b22 fix: layout cert message; show renewable message before cert delay 2022-06-06 17:05:35 +02:00
poka 5c49be4f12 bump v0.0.8+0 dataVersion 4 2022-06-05 21:28:16 +02:00
poka c82700a45f show cert state on profile view 2022-06-05 19:58:04 +02:00
poka bd87aee7ec fix: check if nextIssuableOn < blocNumber 2022-06-05 14:44:18 +02:00
poka 684974cd59 manage membership revoke 2022-06-05 02:22:22 +02:00
poka 36bc2f571f improve idty confirmation 2022-06-04 23:32:44 +02:00
poka 9eacbef2a8 feat: detect if a wallet can cert an address; improve errors view; 2022-06-04 17:24:33 +02:00
poka 6280517f8d improve certs view 2022-06-04 15:08:22 +02:00
poka 1cd3f74c6c feat: add certifications up and down; Check if wallet exist in wallet_view 2022-06-03 18:15:45 +02:00
poka 1eb966046c WIP: get received/sent certs 2022-06-02 19:58:23 +02:00
poka e7e4bca4e3 fix: no need of nullable defaultWallet 2022-06-02 15:43:22 +02:00
poka 6143a05596 fix: go gack to search list after cert 2022-06-02 13:19:32 +02:00
poka 7a9f2442e2 Change qrscan package with updtated API, compatible with iOS 2022-06-01 23:59:56 +02:00
poka 93f041eed8 upgrade dependencies 2022-06-01 22:49:35 +02:00
poka 6a57572434 Improve home message style 2022-06-01 21:00:17 +02:00
poka 8e49ac73ff fix: chest change not consistent; feat: Can create custom derivation number, and root wallet 2022-06-01 15:17:07 +02:00
poka 51182efe97 fix: isCacheChecked is false if null 2022-06-01 09:54:12 +02:00
poka 09116b956b fix: lowercase for mnemonic words; set pin cache choice to false as default 2022-06-01 07:33:56 +02:00
poka 86931bab5b add lock pin state 2022-05-31 18:58:44 +02:00
poka 8c1383bdd4 Complete refactoring of unlocking chests; Can cache pin code 15 minutes 2022-05-31 18:23:56 +02:00
poka d91d2cddb3 bump v7 2022-05-31 13:56:22 +02:00
poka 7c29bdc73c try release publish key 2022-05-31 11:44:36 +02:00
poka 19020f93f6 gradle 2022-05-30 20:30:06 +02:00
poka 15d0046e18 Don't load Cs+ image on wallet_view 2022-05-30 15:39:23 +02:00
poka 1a6dfdd617 Disconnect completly GVA and Cs+ 2022-05-30 15:32:00 +02:00
poka 7bef4a9cd3 bugs fix 2022-05-30 14:48:12 +02:00
poka 1a7224fd57 fix: no delay needed anymore in importKeyStore (good address rendering) 2022-05-30 06:22:43 +02:00
poka 54bc774aa3 Add todo comment about address delayed 2022-05-30 05:31:33 +02:00
poka cd6c3e9c70 Dynamic home message; improve node statut UX 2022-05-29 04:30:03 +02:00
poka 488435405c fix: no chest address anymore; Delete all wallet on startup if bad dataVersion 2022-05-29 01:38:27 +02:00
poka 98bb94ab78 Add screen to show chest mnemonic 2022-05-29 00:00:57 +02:00
poka 10f04c1a5a balanceBuilder on choose_wallets; bugs fix 2022-05-28 21:48:04 +02:00
poka f5c14e8924 fix: refresh bugs 2022-05-28 21:15:47 +02:00
poka f535bea932 Persitents profiles images; bugs fix; balances on wallets_home 2022-05-28 19:13:30 +02:00
poka df84eb7a48 cleaning code 2022-05-28 06:12:39 +02:00
poka 5aab5bb385 Use 'AAAAA' password even in production mode for tests 2022-05-28 05:59:56 +02:00
poka 104d00ff7e feat: Change short pubkey format 2022-05-28 00:17:50 +02:00
poka 2a4b54d9eb Merge branch 'bottomBar' 2022-05-28 00:04:59 +02:00
poka 3b0a49f216 fix: reponsive; Add 2 bootstrap endpoints 2022-05-28 00:04:37 +02:00
poka 7ae1d2ecd2 fix: Expanded flex bottomBar 2022-05-27 17:49:02 +02:00
poka 79e0773820 Add optionnal bottomAppBar 2022-05-27 16:58:03 +02:00
poka 459b3f0c29 More debug in snackBar 2022-05-27 12:54:25 +02:00
poka 8c390e511e fix: empty endpoint text if node not connected 2022-05-27 11:22:43 +02:00
poka 05b425c3d2 Set gdev first bootstrap; Can create idty, can cert; 2022-05-27 08:54:29 +02:00
poka 1227e15fa2 Set first gdev endpoint in bootstrap list; fix: default wallet on stratup 2022-05-27 06:11:09 +02:00
poka 6cbe5b2fd6 fix: responsive balance text 2022-05-26 02:35:29 +02:00
poka a6c9947b32 Merge branch 'improvePaymentUX' 2022-05-26 02:20:06 +02:00
poka 3672f4cd94 Refactoring of payment workflow UX 2022-05-26 02:19:29 +02:00
poka 0009fc4009 WIP: refactor payment UX workflow 2022-05-25 20:40:55 +02:00
poka e9291d9277 improve responsive; skip tuto after onboarding 2022-05-25 20:40:25 +02:00
poka 16d42d7f5f fix: Android 12 compatibility; Remove late cesiumSeed; 2022-05-25 09:41:48 +02:00
poka 047720e65f Merge branch 'paymentEndingRefactor' 2022-05-24 18:47:04 +02:00
poka 04ffabf052 fix: bad cache on balances 2022-05-24 18:31:41 +02:00
poka 14f784fdc9 Implement identity workflow 2022-05-24 16:51:40 +02:00
poka adcb876203 Add commented code to test endpoint connection 2022-05-23 12:39:01 +02:00
poka 7a6bda3545 Merge branch 'substrate-sdk' 2022-05-23 11:08:24 +02:00
poka f65106e798 onboarding: change pages size 2022-05-23 11:08:07 +02:00
poka ed734e271e Refactoring onboarding 2022-05-23 10:53:44 +02:00
poka 847619bef0 fix: organisation name 2022-05-22 01:42:43 +02:00
poka 5472a03bf3 fix: debug name 2022-05-21 06:52:30 +02:00
poka 3df8b44d67 so much UI changes/refacto 2022-05-21 06:47:26 +02:00
poka 81d1b7dd30 WIP: Bad icon 2022-05-20 16:02:42 +02:00
poka c5317f984d Improve wallet generation; Add menu to change endpoint; 2022-05-20 15:15:29 +02:00
poka 345a600599 Change derivation %3 to %2; Harden derivation 2022-05-20 07:22:57 +02:00
poka d53facca05 Can pay with substrate 2022-05-19 13:44:22 +02:00
poka 48d997c835 Globalized done 2022-05-19 09:07:26 +02:00
poka a72390f428 WIP: Continue digging global substratization 2022-05-19 07:00:25 +02:00
poka b1b27743a0 Upgrade to flutter 3 2022-05-16 22:27:25 +02:00
poka 88b3e9fb9b WIP: globalize changes 2022-05-04 19:00:09 +02:00
poka 370dabc01b Move sandbox button on setting tab 2022-05-04 16:41:08 +02:00
poka 4389f65ee0 Decompose seed getting 2022-03-02 22:21:53 +01:00
199 changed files with 13739 additions and 10100 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
ip_address=127.0.0.1

6
.gitignore vendored
View File

@ -55,3 +55,9 @@ scripts/private/
AppDir/
appimage-builder-cache/
AppImageBuilder.yml
android/app/build.gradle
integration_test/duniter/data/chains/
# Ignore PC deps
macos/
windows/

View File

@ -5,7 +5,7 @@ stages:
- package
.env:
image: axiomteam/gecko-ci:v0.0.8
image: axiomteam/gecko-ci:v0.0.11
tags:
- redshift
@ -17,6 +17,8 @@ format:
- if: $CI_COMMIT_TAG || $CI_MERGE_REQUEST_ID
- when: manual
stage: format
script:
- flutter format --set-exit-if-changed lib
build_and_test:
extends: .env
@ -30,25 +32,6 @@ build_and_test:
- redshift
script:
- flutter analyze
# - flutter test
lint:
extends: .env
rules:
- if: $CI_COMMIT_REF_NAME =~ /^wip*$/
when: manual
- if: $CI_COMMIT_TAG || $CI_MERGE_REQUEST_ID
- when: manual
stage: quality
audit_dependencies:
extends: .env
rules:
- if: $CI_COMMIT_REF_NAME =~ /^wip*$/
when: manual
- if: $CI_COMMIT_TAG || $CI_MERGE_REQUEST_ID
- when: manual
stage: quality
releases:test:
extends: .env

View File

@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818
revision: 85684f9300908116a78138ea4c6036c35c9a1236
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
- platform: android
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -10,12 +10,9 @@ The development is quite early, you can participate in the discussion [on the Du
<div align="center">
![Demo Gif](https://git.p2p.legal/axiom-team/gecko/raw/branch/master/assets/Demo-0.0.1+0.gif)
![Demo Gif](https://git.duniter.org/clients/gecko/-/raw/master/images/demo-0.0.9+2.gif)
<br><br>
![Foo](https://git.p2p.legal/axiom-team/gecko/raw/commit/1cd2d63fe02949edabb69aa5fc498512c01db416/images/art/bb_gecko.png)
</div>
## Develop
@ -89,30 +86,4 @@ then
## Roadmap
- v0.1.0 (expected date: 21-08-16)
- Reorganization of persistent data
- Complete implementation of Figma model (made by Boris)
- Account management (creation, security)
- Payment (QR-code generation / reading, form)
- Viewing transaction history
- Finalization of integration tests and unit tests
- Completing the network scan when starting the application
- F-Droid publication
- v1.0
- Multi-vault management
- Cesium import
- Advanced search
- Item basket management
- Transaction monitoring
- Contacts / Messaging
- IOS compatibility
- Sharding (sharing of key fragments)
- Apple AppStore and Google PlayStore publication
- Mock-up and UX design of future functionalities
- v2.0
- Opaque bypass
- NFC payment
- Desktop compatibility
- Web of trust management (certifications, promises of certifications)
- Calendar / community
-> https://pad.p2p.legal/gecko-roadmap-2022?view

View File

@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 31
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@ -46,7 +46,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "gecko.axiomteam.fr"
minSdkVersion 19
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
@ -65,11 +65,14 @@ android {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
useProguard true
signingConfig signingConfigs.release //poka: comment this to build unsigned release, or set to signingConfigs.debug to sign with debug keys
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
debuggable true
}
}
}
@ -79,4 +82,5 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.work:work-runtime-ktx:2.7.0'
}

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gecko">
package="gecko.axiomteam.fr">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gecko">
package="gecko.axiomteam.fr">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
@ -19,30 +19,32 @@
<activity
android:requestLegacyExternalStorage="true"
android:name=".MainActivity"
android:icon="@mipmap/ic_launcher"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
<!-- <meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:icon="@mipmap/ic_launcher"
/> -->
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -1,4 +1,4 @@
package com.example.gecko
package gecko.axiomteam.fr
import io.flutter.embedding.android.FlutterActivity

View File

@ -0,0 +1,6 @@
package gecko.axiomteam.gecko
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gecko">
package="gecko.axiomteam.fr">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.kotlin_version = '1.7.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
android.enableJetifier=true

View File

@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
assets/gecko_certify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
assets/medal.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

BIN
assets/skull_Icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

199
assets/translations/en.json Normal file
View File

@ -0,0 +1,199 @@
{
"searchWallet": "Search\nwallet",
"manageWallets": "Manage\nwallets",
"scanQRCode": "Scan a\nQR code",
"wellConnectedToNode": "You are well connected to node\n{}",
"networkLost": "Network has been lost...",
"noDuniterEndointAvailable": "No server available...",
"connectionPending": "Connection pending...",
"noLizard": "no lounge lizard ;-)",
"loading": "Loading...",
"forgot_password.png": "forgot_password_en.png",
"warningForgotPassword": "In a blockchain, there is no email recovery procedure. Only your recovery phrase can allow you to recover your Ğ1 at any time.",
"fastAppDescription": "The {} app payment\nfaster than a reptile of Vietnam",
"createWallet": "Create a wallet",
"restoreWallet": "Restore my wallets",
"parameters": "Parameters",
"chooseAnotherMnemonic": "Choose an other\nmnemonic sentence",
"iNotedMyMnemonic": "I wrote down my sentence",
"printMyMnemonic": "Print my mnemonic sentence",
"manageChest": "Configure this chest",
"changeChest": "Change chest",
"geckoChest": "Ğecko chest",
"toUnlockEnterPassword": "To unlock your safe, enter your secret code, away from prying lizards:",
"rememberPassword": "Keep this code in memory for 15 minutes",
"myRootWallet": "My root wallet",
"currentWallet": "My current chest",
"wallet": "Wallet",
"displayMnemonic": "Display my mnemonic sentence",
"changePassword": "Change my password",
"createDerivation": "Create a new derivation",
"createCustomDerivation": "Create a new custom derivation",
"deleteChest": "Delete this chest",
"openThisChest": "Open this chest",
"createChest": "Create a new chest",
"importChest": "Import a chest",
"selectMyChest": "Select my chest",
"accessMyChest": "Access my chest",
"manageMembership": "Manage my membership",
"chooseThisWallet": "Choose this wallet",
"thisWalletIsDefault": "This wallet is the default one",
"defineWalletAsDefault": "Define this as the default one",
"displayActivity": "Display activity",
"displayNActivity": "Display\nactivity",
"memberValidated": "Validated member!",
"copyAddress": "Copy\naddress",
"copy": "Copy",
"thisAddressHasBeenCopiedToClipboard": "This address has been copied to clipboard",
"chooseWalletName": "Choose a new name\nfor your wallet:",
"choosePassword": "Choose a random password:",
"chooseDerivation": "Choose a derivation:",
"validate": "Validate",
"confirm": "Confirm",
"confirmPayment": "Confirm payment",
"geckoGenerateYourWalletFromMnemonic": "Ğecko builds your wallet from a **restoration sentence**. It is a bit like the blueprint that builds your wallet.",
"keepThisMnemonicSecure": "Keep this sentence carefully, because without it Ğecko will not be able to rebuild your wallets the day you change your phone.",
"geckoGeneratedYourMnemonicKeepItSecret": "Ğecko generated your mnemonic successfully! Keep it secret, because anyone who knows it can access all your wallets.",
"newWallet": "New Wallet",
"itsTimeToUseAPenAndPaper": "It's time to take a **pen and paper** in order to write down your mnemonic.",
"yourMnemonic": "Your mnemonic",
"gecko_also_can_forget.png": "gecko_also_can_forget_en.png",
"didYouNoteMnemonicToBeSureTypeWord": "Did you write down your menmonic?\n\n To be sure, please type the **{}th word** of your restoration phrase in the field below:",
"geckoWillGenerateAPassword": "Gecko will now generate for you a short password that will allow you to quickly access your wallets, without having to type your recovery sentence every time.",
"myPassword": "My password",
"thisPasswordProtectsYourWalletsInASecureChest": "This secret code protects your wallets in a safe **which only you have the code for**, so that your wallets cannot be used by others.",
"hereIsThePasswordKeepIt": "And here is your password!\n\nMemorize it or write it down, because you will be asked **every time** you want to make a payment on this device.",
"chooseAnotherPassword": "Choose an other password",
"iNotedMyPassword": "I noted my password",
"geckoWillCheckPassword": "Gecko will check with you if you have remembered your secret code.\n\n Type your secret code in the field below to check that you have written it down correctly.",
"yourChestAndWalletWereCreatedSuccessfully": "Super!\n\nYour chest and your first portfolio have been created with great success.\n\nCongratulations!",
"allGood": "That's all good!",
"areYouSureToDeleteWallet": "Are you sure you want to delete the chest \"{}\"?",
"areYouSureForgetAllChests": "Are you sure you want to forget all your chests?",
"areYouSureToForgetWallet": "Are you sure you wan to forget the wallet \"{}\"?",
"areYouSureYouWantToCertify": "Are you sure you want to certify the address:\n\n{}",
"yes": "Yes",
"no": "No",
"keepYourMnemonicSecret": "Try to keep this phrase a secret, as it allows anyone who knows it to access all your wallets.",
"iGeneratedYourMnemonicKeepItSecret": "I've generated your restoration phrase!\n Try to keep it a secret, as it allows anyone who knows it to access all your portfolios.",
"myMnemonic": "My mnemonic",
"close": "Close",
"toRestoreEnterMnemonic": "To restore your Gecko wallets, enter in the fields below the 12 words that constitute your restoration phrase:",
"pasteFromClipboard": "Paste from\nclipboard",
"restoreAChest": "Restore a chest",
"restoreThisChest": "Restore this chest",
"continue": "Continue",
"itsTheGoodWord": "It's the good word!",
"nthMnemonicWord": "word of your mnemonic",
"1th": "First",
"2th": "Second",
"3th": "Third",
"4th": "Fourth",
"5th": "Fifth",
"6th": "Sixth",
"7th": "Seventh",
"8th": "Eighth",
"9th": "Ninth",
"10th": "Tenth",
"11th": "Eleventh",
"12th": "Twelfth",
"yourPasswordLengthIsX": "Your password length is {}",
"noIdentity": "No identity",
"identityCreated": "Identity created",
"identityConfirmed": "Identity confirmed",
"identityExpired": "Identity expired",
"confirmYourIdentity": "Confirm your identity",
"noDuniterNodeAvailableTryLater": "No Duniter node available, please try again later",
"youAreConnectedToNode": "You are connected to node",
"accountActivity": "Account activity",
"noNetworkNoHistory": "Network state does not allow\nto display account history",
"noDataToDisplay": "No data to be displayed.",
"noTransactionToDisplay": "No transaction to display",
"month1": "January",
"month2": "February",
"month3": "March",
"month4": "April",
"month5": "May",
"month6": "June",
"month7": "July",
"month8": "August",
"month9": "September",
"month10": "October",
"month11": "November",
"month12": "December",
"today": "Today",
"yesterday": "Yesterday",
"thisWeek": "This week",
"chestNotCompatibleMustReinstallGecko": "The version of your safes is no longer compatible with this version of Ğecko.\nAll your safes will be forgotten, you must import them again.",
"notConnectedToInternet": "You are not connected to internet",
"researchResults": "Results of your research",
"resultsFor": "Results for ",
"forgetAllMyChests": "Forget all my chests",
"transaction": "Transaction",
"certification": "Certification",
"identityConfirm": "Identity confirmation",
"revokeAdhesion": "Adhesion revocation",
"strangeTransaction": "Strange transaction",
"sending": "Sending...",
"propagating": "Propagating...",
"validating": "Validating...",
"anErrorOccurred": "An error occurred",
"24hbetweenCerts": "You have to wait 24h between certs",
"canNotCertifySelf": "You can not certify yourself",
"nameAlreadyExist": "This name is already taken",
"2GDtoKeepAlive": "You have to keep at least 2ĞD to keep your account alive",
"youHaveToFeedThisAccountBeforeUsing": "You have to feed this account\nbefore using it.",
"execTimeoutOver": "Execution timeout is over",
"seeAWallet": "See a wallet",
"mustWaitXBeforeCertify": "You have to wait\n{} before\ncertifying again",
"mustConfirmHisIdentity": "This person must confirm\nhis identity before can be\ncertified",
"canRenewCertInX": "You can renew\nthis certification\nin {}",
"executeATransfer": "Execute a transfer",
"executeTheTransfer": "Execute the transfer",
"doATransfer" : "Execute a\ntransfer",
"seconds": "{} seconds",
"minutes": "{} minutes",
"hours": "{} hours {}",
"days": "{} days",
"months": "{} months",
"certify": "Certify",
"from": "From:",
"to": "To:",
"amount": "Amount:",
"choiceOfSourceWallet": "Choose a source wallet",
"extrinsicInProgress": "{} in progress",
"extrinsicValidated": "{} validated !",
"fromMinus": "from",
"toMinus": "to",
"deleteThisWallet": "Delete this wallet",
"cancel": "Cancel",
"inBlockchainResult": "In {} blockchain",
"search": "Search",
"currencyNode": "{} node :",
"contactsManagementWithNbr": "My contacts ({})",
"contactsManagement": "My contacts",
"noContacts": "You don't have any contact",
"addContact": "Add\nto contacts",
"removeContact": "Remove\nthis contact",
"derivationsScanProgress": "Scan address {}/{}",
"youAreOffline": "You are offline...",
"importG1v1": "Import old G1v1 account",
"selectDestWallet": "Select a target wallet:",
"youMustWaitBeforeCashoutThisAccount": "You have to wait a few moment before migrate this account",
"thisAccountIsEmpty": "This account is empty",
"youCannotMigrateIdentityToExistingIdentity": "You cannot migrate an identity\nto an account that already has an identity",
"importOldAccount": "Import your old account",
"enterCesiumId": "Enter your Cesium ID",
"enterCesiumPassword": "Enter your Cesium password",
"migrateAccount": "Migrate account",
"migrateIdentity": "Migrate identity",
"identityMigration": "Identity migration",
"areYouSureMigrateIdentity": "Are you sure you want to permanently migrate identity **{}** with balance of **{}** ?",
"someoneCreatedYourIdentity": "Someone created your {} identity !",
"confirmMyIdentity": "Confirm my identity",
"revokeMyIdentity": "Revoke my identity",
"youCannotRevokeThisIdentity": "You cannot revoke this identity while\nit is member of the blacksmiths web",
"showUdAmounts": "Show amounts in UD",
"ud": "{}UD",
"chooseATargetWallet": "Choose a target wallet"
}

199
assets/translations/es.json Normal file
View File

@ -0,0 +1,199 @@
{
"searchWallet": "Buscar\nbilletera",
"manageWallets": "Gestionar\nbilleteras",
"scanQRCode": "Escanear un\ncódigo QR",
"wellConnectedToNode": "Estas bien conectada al nodo\n{}",
"networkLost": "Se ha perdido la red...",
"noDuniterEndointAvailable": "No hay servidor disponible...",
"connectionPending": "Conexión pendiente...",
"noLizard": "no hay lagarto ;-)",
"loading": "Cargando...",
"forgot_password.png": "forgot_password_en.png",
"warningForgotPassword": "In a blockchain, there is no email recovery procedure. Only your recovery phrase can allow you to recover your Ğ1 at any time.",
"fastAppDescription": "La aplicación de pago {}\nmás rápida que un reptil de Vietnam",
"createWallet": "Crear una billetera",
"restoreWallet": "Restaurar mis billeteras",
"parameters": "Parámetros",
"chooseAnotherMnemonic": "Choose an other\nmnemonic sentence",
"iNotedMyMnemonic": "He escrito mi frase",
"printMyMnemonic": "Print my mnemonic sentence",
"manageChest": "Configure this chest",
"changeChest": "Change chest",
"geckoChest": "Ğecko chest",
"toUnlockEnterPassword": "To unlock your safe, enter your secret code, away from prying lizards:",
"rememberPassword": "Keep this code in memory for 15 minutes",
"myRootWallet": "Mi billetera principal",
"currentWallet": "My current chest",
"wallet": "Billetera",
"displayMnemonic": "Display my mnemonic sentence",
"changePassword": "Cambiar mi contraseña",
"createDerivation": "Create a new derivation",
"createCustomDerivation": "Create a new custom derivation",
"deleteChest": "Delete this chest",
"openThisChest": "Open this chest",
"createChest": "Create a new chest",
"importChest": "Import a chest",
"selectMyChest": "Select my chest",
"accessMyChest": "Access my chest",
"manageMembership": "Manage my membership",
"chooseThisWallet": "Elegir esta billetera",
"thisWalletIsDefault": "This wallet is the default one",
"defineWalletAsDefault": "Define this as the default one",
"displayActivity": "Display activity",
"displayNActivity": "Display\nactivity",
"memberValidated": "Miembro validado!",
"copyAddress": "Copiar\ndirección",
"copy": "Copiar",
"thisAddressHasBeenCopiedToClipboard": "Esta dirección se ha copiado al cortapapeles",
"chooseWalletName": "Choose a new name\nfor your wallet:",
"choosePassword": "Choose a random password:",
"chooseDerivation": "Choose a derivation:",
"validate": "Validar",
"confirm": "Confirmar",
"confirmPayment": "Confirmar pago",
"geckoGenerateYourWalletFromMnemonic": "Ğecko builds your wallet from a **restoration sentence**. It is a bit like the blueprint that builds your wallet.",
"keepThisMnemonicSecure": "Keep this sentence carefully, because without it Ğecko will not be able to rebuild your wallets the day you change your phone.",
"geckoGeneratedYourMnemonicKeepItSecret": "Ğecko generated your mnemonic successfully! Keep it secret, because anyone who knows it can access all your wallets.",
"newWallet": "New Wallet",
"itsTimeToUseAPenAndPaper": "It's time to take a **pen and paper** in order to write down your mnemonic.",
"yourMnemonic": "Your mnemonic",
"gecko_also_can_forget.png": "gecko_also_can_forget_en.png",
"didYouNoteMnemonicToBeSureTypeWord": "Did you write down your menmonic?\n\n To be sure, please type the **{}th word** of your restoration phrase in the field below:",
"geckoWillGenerateAPassword": "Gecko will now generate for you a short password that will allow you to quickly access your wallets, without having to type your recovery sentence every time.",
"myPassword": "My password",
"thisPasswordProtectsYourWalletsInASecureChest": "This secret code protects your wallets in a safe **which only you have the code for**, so that your wallets cannot be used by others.",
"hereIsThePasswordKeepIt": "And here is your password!\n\nMemorize it or write it down, because you will be asked **every time** you want to make a payment on this device.",
"chooseAnotherPassword": "Elige otra contraseña",
"iNotedMyPassword": "I noted my password",
"geckoWillCheckPassword": "Gecko will check with you if you have remembered your secret code.\n\n Type your secret code in the field below to check that you have written it down correctly.",
"yourChestAndWalletWereCreatedSuccessfully": "Super!\n\nYour chest and your first portfolio have been created with great success.\n\nCongratulations!",
"allGood": "That's all good!",
"areYouSureToDeleteWallet": "Are you sure you want to delete the chest \"{}\"?",
"areYouSureForgetAllChests": "Are you sure you want to forget all your chests?",
"areYouSureToForgetWallet": "Are you sure you wan to forget the wallet \"{}\"?",
"areYouSureYouWantToCertify": "Are you sure you want to certify the address:\n\n{}",
"yes": "Si",
"no": "No",
"keepYourMnemonicSecret": "Try to keep this phrase a secret, as it allows anyone who knows it to access all your wallets.",
"iGeneratedYourMnemonicKeepItSecret": "I've generated your restoration phrase!\n Try to keep it a secret, as it allows anyone who knows it to access all your portfolios.",
"myMnemonic": "My mnemonic",
"close": "Close",
"toRestoreEnterMnemonic": "To restore your Gecko wallets, enter in the fields below the 12 words that constitute your restoration phrase:",
"pasteFromClipboard": "Paste from\nclipboard",
"restoreAChest": "Restore a chest",
"restoreThisChest": "Restore this chest",
"continue": "Continuar",
"itsTheGoodWord": "It's the good word!",
"nthMnemonicWord": "word of your mnemonic",
"1th": "Primera",
"2th": "Segunda",
"3th": "Tercera",
"4th": "Cuarta",
"5th": "Quinta",
"6th": "Sexta",
"7th": "Séptima",
"8th": "Octava",
"9th": "Novena",
"10th": "Décima",
"11th": "Undécima",
"12th": "Duodécima",
"yourPasswordLengthIsX": "La longitud de tu contraseña es {}",
"noIdentity": "No identity",
"identityCreated": "Identidad creada",
"identityConfirmed": "Identidad confirmada",
"identityExpired": "Identitdad caducada",
"confirmYourIdentity": "Confirma tu identidad",
"noDuniterNodeAvailableTryLater": "No Duniter node available, please try again later",
"youAreConnectedToNode": "You are connected to node",
"accountActivity": "Actividad de la cuenta",
"noNetworkNoHistory": "Network state does not allow\nto display account history",
"noDataToDisplay": "No data to be displayed.",
"noTransactionToDisplay": "No transaction to display",
"month1": "Enero",
"month2": "Febrero",
"month3": "Marzo",
"month4": "Abril",
"month5": "Mayo",
"month6": "Junio",
"month7": "Julio",
"month8": "Agosto",
"month9": "Septiembre",
"month10": "Octubre",
"month11": "Noviembre",
"month12": "Diciembre",
"today": "Hoy",
"yesterday": "Ayer",
"thisWeek": "Esta semana",
"chestNotCompatibleMustReinstallGecko": "The version of your safes is no longer compatible with this version of Ğecko.\nAll your safes will be forgotten, you must import them again.",
"notConnectedToInternet": "No estas conectado a internet",
"researchResults": "Results of your research",
"resultsFor": "Results for ",
"forgetAllMyChests": "Forget all my chests",
"transaction": "Transaccion",
"certification": "Certificacion",
"identityConfirm": "Identity confirmation",
"revokeAdhesion": "Adhesion revocation",
"strangeTransaction": "Strange transaction",
"sending": "Enviando...",
"propagating": "Propagando...",
"validating": "Validando...",
"anErrorOccurred": "Ocurrió un error",
"24hbetweenCerts": "You have to wait 24h between certs",
"canNotCertifySelf": "You can not certify yourself",
"nameAlreadyExist": "This name is already taken",
"2GDtoKeepAlive": "You have to keep at least 2ĞD to keep your account alive",
"youHaveToFeedThisAccountBeforeUsing": "You have to feed this account\nbefore using it.",
"execTimeoutOver": "Execution timeout is over",
"seeAWallet": "Ver una billetera",
"mustWaitXBeforeCertify": "Tienes que esperar\n{} antes de\nvolver a certificar",
"mustConfirmHisIdentity": "This person must confirm\nhis identity before can be\ncertified",
"canRenewCertInX": "You can renew\nthis certification\nin {}",
"executeATransfer": "Execute a transfer",
"executeTheTransfer": "Execute the transfer",
"doATransfer" : "Execute a\ntransfer",
"seconds": "{} segundos",
"minutes": "{} minutos",
"hours": "{} horas {}",
"days": "{} dias",
"months": "{} meses",
"certify": "Certify",
"from": "De:",
"to": "A:",
"amount": "Importe:",
"choiceOfSourceWallet": "Choose a source wallet",
"extrinsicInProgress": "{} en progreso",
"extrinsicValidated": "{} validado !",
"fromMinus": "de",
"toMinus": "a",
"deleteThisWallet": "Delete this wallet",
"cancel": "Cancelar",
"inBlockchainResult": "In {} blockchain",
"search": "Buscar",
"currencyNode": "{} nodo :",
"contactsManagementWithNbr": "Mis contactos ({})",
"contactsManagement": "Mis contactos",
"noContacts": "You don't have any contact",
"addContact": "Add\nto contacts",
"removeContact": "Remove\nthis contact",
"derivationsScanProgress": "Scan address {}/{}",
"youAreOffline": "You are offline...",
"importG1v1": "Import old G1v1 account",
"selectDestWallet": "Select a target wallet:",
"youMustWaitBeforeCashoutThisAccount": "You have to wait a few moment before migrate this account",
"thisAccountIsEmpty": "This account is empty",
"youCannotMigrateIdentityToExistingIdentity": "You cannot migrate an identity\nto an account that already has an identity",
"importOldAccount": "Import your old account",
"enterCesiumId": "Ingrese su ID de Cesium",
"enterCesiumPassword": "Ingrese su contraseña de Cesium",
"migrateAccount": "Migrate account",
"migrateIdentity": "Migrate identity",
"identityMigration": "Identity migration",
"areYouSureMigrateIdentity": "Are you sure you want to permanently migrate identity **{}** with balance of **{}** ?",
"someoneCreatedYourIdentity": "Someone created your {} identity !",
"confirmMyIdentity": "Confirmar mi identidad",
"revokeMyIdentity": "Revocar mi identidad",
"youCannotRevokeThisIdentity": "You cannot revoke this identity while\nit is member of the blacksmiths web",
"showUdAmounts": "Show amounts in UD",
"ud": "{}UD",
"chooseATargetWallet": "Elija una billetera de destino"
}

200
assets/translations/fr.json Normal file
View File

@ -0,0 +1,200 @@
{
"searchWallet": "Rechercher un\nportefeuille",
"manageWallets": "Gérer mes\nportefeuilles",
"scanQRCode": "Scanner un\nQR code",
"wellConnectedToNode": "Vous êtes bien connecté aux noeud\n{}",
"networkLost": "Le réseau a été perdu...",
"noDuniterEndointAvailable": "Aucun serveur disponible...",
"connectionPending": "Connexion en cours...",
"noLizard": "y'a pas de lézard ;-)",
"loading": "Chargement en cours...",
"forgot_password.png": "forgot_password_fr.png",
"warningForgotPassword": "Dans une blockchain, pas de procédure de récupération par mail. Seule votre phrase de restauration peut vous permettre de récupérer vos Ğ1 à tout moment.",
"fastAppDescription": "Lapplication de paiement {}\nplus rapide quun reptile du Vietnam",
"createWallet": "Créer un portefeuille",
"restoreWallet": "Restaurer mes portefeuilles",
"parameters": "Paramètres",
"chooseAnotherMnemonic": "Choisir une autre phrase",
"iNotedMyMnemonic": "J'ai noté ma phrase",
"printMyMnemonic": "Imprimer ma phrase de restauration",
"manageChest": "Paramétrer ce coffre",
"changeChest": "Changer de coffre",
"geckoChest": "Coffre à Ğecko",
"toUnlockEnterPassword": "Pour déverrouiller votre coffre, composez votre code secret à labri des lézards indiscrets :",
"rememberPassword": "Garder ce code en mémoire 15 minutes",
"myRootWallet": "Mon portefeuille racine",
"currentWallet": "Mon portefeuille courant",
"wallet": "Portefeuille",
"displayMnemonic": "Afficher ma phrase de restauration",
"changePassword": "Changer mon code secret",
"createDerivation": "Créer une autre dérivation",
"createCustomDerivation": "Créer une dérivation personnalisé",
"deleteChest": "Supprimer ce coffre",
"openThisChest": "Ouvrir ce coffre",
"createChest": "Créer un nouveau coffre",
"importChest": "Importer un coffre",
"selectMyChest": "Sélectionner mon coffre",
"accessMyChest": "Accéder à mon coffre",
"manageMembership": "Gérer mon adhésion",
"chooseThisWallet": "Choisir ce portefeuille",
"thisWalletIsDefault": "Ce portefeuille est celui par defaut",
"defineWalletAsDefault": "Définir comme portefeuille par défaut",
"displayActivity": "Voir l'activité",
"displayNActivity": "Voir\nl'activité",
"memberValidated": "Membre validé !",
"copyAddress": "Copier\nl'adresse",
"copy": "Copier",
"thisAddressHasBeenCopiedToClipboard": "Cette adresse a été copié dans votre presse-papier.",
"chooseWalletName": "Choisissez un nouveau nom\npour votre portefeuille :",
"choosePassword": "Choisissez un code secret autogénéré :",
"chooseDerivation": "Choisissez une dérivation:",
"validate": "Valider",
"confirm": "Confirmer",
"confirmPayment": "Confirmer le paiement",
"geckoGenerateYourWalletFromMnemonic": "Ğecko fabrique votre portefeuille à partir dune **phrase de restauration**. Elle est un peu comme le plan qui permet de construire votre portefeuille.",
"keepThisMnemonicSecure": "Conservez cette phrase précieusement, car sans elle Ğecko ne pourra pas reconstruire vos portefeuilles le jour où vous changez de téléphone.",
"geckoGeneratedYourMnemonicKeepItSecret": "Gecko a généré votre phrase de restauration ! Tâchez de la garder bien secrète, car elle permet à quiconque la connaît daccéder à tous vos portefeuilles.",
"newWallet": "Nouveau portefeuille",
"itsTimeToUseAPenAndPaper": "Il est temps de vous munir d**un papier et dun crayon** afin de pouvoir noter votre phrase de restauration.",
"yourMnemonic": "Votre phrase de restauration",
"gecko_also_can_forget.png": "gecko_also_can_forget_fr.png",
"didYouNoteMnemonicToBeSureTypeWord": "Avez-vous bien noté votre phrase de restauration ?\n\nPour en être sûr, veuillez taper dans le champ ci-dessous le **{}ème mot** de votre phrase de restauration :",
"geckoWillGenerateAPassword": "Gecko va maintenant générer pour vous un code secret court qui vous permettra daccéder rapidement à vos portefeuilles, sans avoir à taper votre phrase de restauration à chaque fois.",
"myPassword": "Mon code secret",
"thisPasswordProtectsYourWalletsInASecureChest": "Ce code secret protège vos portefeuilles dans un coffre-fort **dont vous seul possédez le code**, de sorte que vos portefeuilles seront inutilisables par dautres.",
"hereIsThePasswordKeepIt": "Et voilà votre code secret !\n\nMémorisez-le ou notez-le, car il vous sera demandé **à chaque fois** que vous voudrez effectuer un paiement sur cet appareil.",
"chooseAnotherPassword": "Choisir un autre code secret",
"iNotedMyPassword": "J'ai noté mon code secret",
"geckoWillCheckPassword": "Gecko va vérifier avec vous si vous avez bien mémorisé votre code secret.\n\nTapez votre code secret dans le champ ci-dessous pour vérifier que vous lavez bien noté.",
"yourChestAndWalletWereCreatedSuccessfully": "Top !\n\nVotre coffre votre premier portefeuille ont été créés avec un immense succès.\n\nFélicitations !",
"allGood": "Cest tout bon !",
"areYouSureToDeleteWallet": "Êtes-vous sûr de vouloir supprimer le coffre \"{}\" ?",
"areYouSureForgetAllChests": "Êtes-vous sûr de vouloir oublier tous vos coffres ?",
"areYouSureToForgetWallet": "Êtes-vous sûr de vouloir oublier le portefeuille \"{}\" ?",
"areYouSureYouWantToCertify": "Êtes-vous certain de vouloir certifier l'adresse:\n\n{}",
"yes": "Oui",
"no": "Non",
"keepYourMnemonicSecret": "Tâchez de garder cette phrase bien secrète, car elle permet à quiconque la connaît daccéder à tous vos portefeuilles.",
"iGeneratedYourMnemonicKeepItSecret": "Jai généré votre phrase de restauration !\nTâchez de la garder bien secrète, car elle permet à quiconque la connaît daccéder à tous vos portefeuilles.",
"myMnemonic": "Ma phrase de restauration",
"close": "Fermer",
"toRestoreEnterMnemonic": "Pour restaurer vos portefeuilles Gecko, rentrez dans les champs ci-dessous les 12 mots qui constituent votre phrase de restauration :",
"pasteFromClipboard": "Coller depuis le\npresse-papier",
"restoreAChest": "Restaurer un coffre",
"restoreThisChest": "Restaurer ce coffre",
"continue": "Continuer",
"itsTheGoodWord": "C'est le bon mot !",
"nthMnemonicWord": "mot de votre phrase de restauration",
"1th": "Premier",
"2th": "Deuxième",
"3th": "Troisième",
"4th": "Quatrième",
"5th": "Cinquième",
"6th": "Sixième",
"7th": "Septième",
"8th": "Huitième",
"9th": "Neuvième",
"10th": "Dixième",
"11th": "Onzième",
"12th": "Douzième",
"yourPasswordLengthIsX": "Votre code PIN fait {} caractères",
"noIdentity": "Aucune identité",
"identityCreated": "Identité créée",
"identityConfirmed": "Identité confirmée",
"identityExpired": "Identité expirée",
"confirmYourIdentity": "Confirmez votre identité",
"noResult": "Aucun résultat",
"noDuniterNodeAvailableTryLater": "Aucun noeud Duniter disponible, veuillez réessayer ultérieurement",
"youAreConnectedToNode": "Vous êtes connecté au noeud",
"accountActivity": "Activité du compte",
"noNetworkNoHistory": "L'état du réseau ne permet pas\nd'afficher l'historique du compte",
"noDataToDisplay": "Aucune donnée à afficher.",
"noTransactionToDisplay": "Aucune transaction à afficher",
"month1": "Janvier",
"month2": "Février",
"month3": "Mars",
"month4": "Avril",
"month5": "Mai",
"month6": "Juin",
"month7": "Juillet",
"month8": "Aout",
"month9": "Septembre",
"month10": "Octobre",
"month11": "Novembre",
"month12": "Décembre",
"today": "Aujourd'hui",
"yesterday": "Hier",
"thisWeek": "Cette semaine",
"chestNotCompatibleMustReinstallGecko": "La version de vos coffres n'est plus comptabile avec cette version de Ğecko.\nTous vos coffres vont être oubliés, vous devez les importer de nouveau.",
"notConnectedToInternet": "Vous n'êtes pas connecté à internet",
"researchResults": "Résultats de votre recherche",
"resultsFor": "Résultats pour ",
"forgetAllMyChests": "Oublier tous mes coffres",
"transaction": "Transaction",
"certification": "Certification",
"identityConfirm": "Confirmation d'identité",
"revokeAdhesion": "Révocation d'adhésion",
"strangeTransaction": "Transaction étrange",
"sending": "Envoi en cours...",
"propagating": "En cours de propagation...",
"validating": "En cours de validation...",
"anErrorOccurred": "Une erreur s'est produite",
"24hbetweenCerts": "Vous devez attendre 24h entre chaque certification",
"canNotCertifySelf": "Vous ne pouvez pas vous certifier\nvous même ...",
"nameAlreadyExist": "Ce nom est déjà pris",
"2GDtoKeepAlive": "Vous devez garder au moins 2ĞD sur votre compte pour le garder actif",
"youHaveToFeedThisAccountBeforeUsing": "Vous devez alimenter ce compte avant\nde pouvoir l'utiliser",
"execTimeoutOver": "Le délais d'éxecution est dépassé",
"seeAWallet": "Voir un portefeuille",
"mustWaitXBeforeCertify": "Vous devez attendre\n{} avant\nde pouvoir certifier",
"mustConfirmHisIdentity": "Cette personne doit confirmer\nson identité avant de pouvoir\nêtre certifié",
"canRenewCertInX": "Vous pourrez renouveler\ncette certification\ndans {}",
"executeATransfer": "Effectuer un virement",
"executeTheTransfer": "Effectuer le virement",
"doATransfer": "Faire un\nvirement",
"seconds": "{} secondes",
"minutes": "{} minutes",
"hours": "{} heures {}",
"days": "{} jours",
"months": "{} mois",
"certify": "Certifier",
"from": "Depuis:",
"to": "Vers:",
"amount": "Montant:",
"choiceOfSourceWallet": "Choix du portefeuille source",
"extrinsicInProgress": "{} en cours",
"extrinsicValidated": "{} validé !",
"fromMinus": "de",
"toMinus": "vers",
"deleteThisWallet": "Supprimer ce portefeuille",
"cancel": "Annuler",
"inBlockchainResult": "Dans la blockchain {}",
"search": "Rechercher",
"currencyNode": "Noeud {} :",
"contactsManagementWithNbr": "Mes contacts ({})",
"contactsManagement": "Mes contacts",
"noContacts": "Vous n'avez aucun contact",
"addContact": "Ajouter\naux contacts",
"removeContact": "Supprimer\nce contact",
"derivationsScanProgress": "Scan de l'adresse {}/{}",
"youAreOffline": "Vous êtes hors ligne...",
"importG1v1": "Importer un ancien compte G1v1",
"selectDestWallet": "Sélectionnez un portefeuille cible:",
"youMustWaitBeforeCashoutThisAccount": "Vous devez attendre quelques minutes avant de pouvoir migrer ce compte",
"thisAccountIsEmpty": "Ce compte est vide",
"youCannotMigrateIdentityToExistingIdentity": "Vous ne pouvez pas migrer une identité\nvers un compte disposant déjà d'une identité",
"importOldAccount": "Importer son ancien compte",
"enterCesiumId": "Entrez votre identifiant Cesium",
"enterCesiumPassword": "Entrez votre mot de passe Cesium",
"migrateAccount": "Migrer le compte",
"migrateIdentity": "Migrer l'identité",
"identityMigration": "Migration de l'identité",
"areYouSureMigrateIdentity": "Êtes-vous certain de vouloir migrer définitivement l'identité **{}** et son solde de **{}** ?",
"someoneCreatedYourIdentity": "Quelqu'un a créé votre identité {} !",
"confirmMyIdentity": "Confirmer mon identité",
"revokeMyIdentity": "Révoquer mon identité",
"youCannotRevokeThisIdentity": "Vous ne pouvez pas révoquer cette identité tant\nqu'elle fait partie de la toile forgerons",
"showUdAmounts": "Afficher les montants en DU",
"ud": "{}DU",
"chooseATargetWallet": "Choisissez un portefeuille cible"
}

BIN
assets/wallet.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,4 @@
[
"wss://gdev.p2p.legal/ws",
"wss://gdev.librelois.fr/ws"
]

View File

@ -1,5 +0,0 @@
[
"https://g1.librelois.fr/gva",
"https://duniter-gva.axiom-team.fr/gva",
"https://duniter-g1.p2p.legal/gva"
]

View File

@ -0,0 +1,6 @@
[
"https://gdev-indexer.p2p.legal",
"https://idx.gdev.cgeek.fr",
"https://duniter-indexer.coinduf.eu",
"http://192.168.1.72:8080"
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
images/demo-0.0.9+2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

128
integration_test/README.md Normal file
View File

@ -0,0 +1,128 @@
# Context des tests
Chaque test est précédé par le lancement d'un noeud Duniter v2s en docker [dont voici le compose](https://git.duniter.org/clients/gecko/-/blob/end2EndTests/integration_test/duniter/docker-compose.yml).
Voici le yaml de configuration de la monnaie de test éphémère: https://git.duniter.org/clients/gecko/-/blob/end2EndTests/integration_test/duniter/data/gecko_tests.json
Voici le mnemonic de test utilisé:
`pipe paddle ketchup filter life ice feel embody glide quantum ride usage`
Et les 5 premiers portefeuilles Gecko associés:
```
test1: 5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa
test2: 5E4i8vcNjnrDp21Sbnp32WHm2gz8YP3GGFwmdpfg5bHd8Whb
test3: 5FhTLzXLNBPmtXtDBFECmD7fvKmTtTQDtvBTfVr97tachA1p
test4: 5DXJ4CusmCg8S1yF6JGVn4fxgk5oFx42WctXqHZ17mykgje5
test5: 5Dq3giahrBfykJogPetZJ2jjSmhw49Fa7i6qKkseUvRJ2T3R
```
Seul les 4 premiers sont membres au démarrage.
Voici le scénario de test principal que j'ai réalisé pour le moment
Scénario 1
- Changer le noeud Duniter pour se connecter au nœud local (l'ip local est récupéré automatiquement, car le nœud est sur le host (votre pc), alors que l'app est dans son émulateur)
- Importer le coffre de test
- Effectuer une transaction du Portefeuille 1 (test1) vers le portefeuille 5 (test5)
- Vérifier que les frais de créations de compte ont bien été prélevés
- Certifier test5 avec test1, test2 et test3 et vérifier qu'il deviens bien membre
- Créer 10 blocs, puis encore 10, puis 30 de plus et vérifier à chaque fois si le compte génère bien ses DU à la bonne valeur, réévaluation comprise au bloc 50.
Des vérifications sur l'état du texte affiché à l'écran ou des widgets affichés ou non sont fait entre chaque étapes pour vérifier que tout ce passe toujours bien.
Si la moindre erreur intervient, le test s'arrête et vous informe de l'erreur en question.
Voici le code du test contenant ce scénario: https://git.duniter.org/clients/gecko/-/blob/end2EndTests/integration_test/gecko_complete.dart
Ce test dur environ 1 minutes et 15 seconds, compilation et lancement de nœud au démarrage inclus.
Voici le rendu (attention ça va assez vite ^^) :
https://tube.p2p.legal/w/kMc5c8KnLi9BpwJrM4EnKX
On remarque notamment que des blocs sont créés uniquement et directement après un extrinsic lancé depuis l'app
---
# Tuto contributeurs
**Il n'est nécessaire ni de connaître le code de Ğecko, ni de connaître Dart/flutter pour écrire un nouveau scénario de test !**
Il vous suffit de comprendre par exemple cet extrait de code:
```
// Copy test mnemonic in clipboard
await clipCopy(testMnemonic);
// Open screen import chest
await goKey(keyRestoreChest, duration: 0);
// Tap on button to paste mnemonic
await goKey(keyPastMnemonic);
// Tap on next button 4 times to skip 3 screen
await goKey(keyGoNext);
await goKey(keyGoNext);
await goKey(keyGoNext);
await goKey(keyGoNext);
// Check if cached password checkbox is checked
final isCached = await isIconPresent(Icons.check_box);
// If not, tap on to cache password
if (!isCached) await goKey(keyCachePassword, duration: 0);
// Enter password
await enterText(keyPinForm, 'AAAAA', 0);
// Check if string "Accéder à mon coffre" is present in screen
await waitFor('Accéder à mon coffre');
// Go to wallets home
await goKey(keyGoWalletsHome, duration: 0);
// Check if string "ĞD" is present in screen
await waitFor('ĞD');
// Tap on add a new derivation button
await addDerivation();
// Tap on Wallet 5
await goKey(keyOpenWallet(test5.address));
// Copy address of Wallet 5
await goKey(keyCopyAddress);
// Check if string "Cette adresse a été copié" is present in screen
await waitFor('Cette adresse a été copié');
// Pop screen 2 time to go back home
await goBack();
await goBack();
// Create a new bloc (useless here, just to show you the method)
await spawnBlock();
// Check if string "y'a pas de lézard" is present in screen
await waitFor("y'a pas de lézard");
```
Vous avez dans ce bout de code commenté tous ce dont vous avez besoin pour effectuer un test d'intégration dans Ğecko :slight_smile:
Vous trouverez toutes les clés de widgets disponibles dans l'app dans ce fichier: https://git.duniter.org/clients/gecko/-/blob/end2EndTests/lib/models/widgets_keys.dart
Ce sont ces clés qui vous permette dinteragir avec les widgets de l'app depuis votre test.
Pour créer un nouveau test **à partir de zero**, voici la marche à suivre:
- Suivez [le readme](https://git.duniter.org/clients/gecko/-/blob/master/README.md) pour configurer votre environnement de développement et ainsi pouvoir lancer Ğecko en mode debug dans un émulateur.
- Créer un nouveau fichier pour votre test dans le dossier `integration_test` (ici nous lappellerons `mon_test.dart`)
- Prenez exemple sur le fichier `gecko_complete.dart` pour écrire votre test
- Lancer un émulateur android (1 seul)
- Exécutez votre test ainsi: `./integration_test/launch_test.sh mon_test`
Créer toute sorte de tests imaginable dans Ğecko est très utile pour éviter un maximum les régressions de bugs entre les différentes versions.
Si vous avez envie de nous aider, que vous ne savez presque pas coder mais que vous êtes prêt à mettre un peu les mains dans la sauce, et que vous avez une idée de scénario à tester, alors n'hésitez pas, je répondrais à toutes vos questions :slight_smile:
A noter que ces tests permettent de tester Gecko mais aussi partiellement Duniter et l'indexer d'une même pierre.

View File

@ -1,447 +0,0 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:gecko/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
int globalTimeout = 2;
group(
'Gecko end-to-end tests',
() {
// First, define the Finders and use them to locate widgets from the
// test suite. Note: the Strings provided to the `byValueKey` method must
// be the same as the Strings we used for the Keys in step 1.
// final manageWalletsFinder = find.byKey(Key('manageWallets'));
// final buttonFinder = find.byValueKey('increment');
// FlutterDriver driver;
WidgetTester tester;
String pinCode;
// *** Global functions *** //
// Easy get text
Future<String> getText(String text) async {
Text resultText = tester.firstWidget(find.byKey(Key(text)));
// Text pinCodeText = generatedPinFinder.evaluate().single.widget as Text;
return resultText.data;
}
// Function to tap the widget by key
Future tapOn(String key) async {
await tester.tap(find.byKey(Key(key)));
}
// Function to go back to previous screen
Future goBack() async {
await Process.run(
'adb',
<String>['shell', 'input', 'keyevent', 'KEYCODE_BACK'],
runInShell: true,
);
}
// Easy sleep
Future sleep(int _time) async {
await Future.delayed(Duration(milliseconds: _time));
}
// Test if widget exist on screen, return a boolean
Future<bool> isPresent(String text,
{Duration timeout = const Duration(seconds: 1)}) async {
try {
expect(text, findsOneWidget);
return true;
} catch (exception) {
return false;
}
}
// Create a derivation
Future createDerivation(String _name) async {
await tapOn('addDerivation');
await sleep(100);
await tester.enterText(find.byKey(Key('DerivationNameKey')), _name);
await tapOn('validDerivation');
await sleep(300);
}
// Delete a derivation
Future deleteWallet(bool _confirm) async {
await tapOn('deleteWallet');
await sleep(100);
_confirm
? await tapOn('confirmDeleting')
: await tapOn('cancelDeleting');
await sleep(300);
}
// Delete all wallets
Future deleteAllWallets() async {
await tester.tap(find.byKey(Key('drawerMenu')));
await sleep(300);
await tester.tap(find.byKey(Key('parameters')));
await sleep(300);
await tester.tap(find.byKey(Key('deleteAllWallets')));
await sleep(300);
await tester.tap(find.byKey(Key('confirmDeletingAllWallets')));
await sleep(300);
}
// Fast creation of new Keychain
Future<String> createNewKeychain(String name) async {
await tapOn('drawerMenu');
await sleep(300);
await tapOn('parameters');
await sleep(300);
await tapOn('generateKeychain');
expect(find.text(''), findsOneWidget);
pinCode = await getText('generatedPin');
await tapOn('storeKeychain');
await sleep(100);
await tester.enterText(find.byKey(Key('askedWord')), 'triche');
await tapOn('walletName');
await tester.enterText(find.byKey(Key('walletName')), 'name');
await tapOn('confirmStorage');
await sleep(300);
return pinCode;
}
// *** Begin of tests *** //
testWidgets('OnBoarding - Open wallets management', (
WidgetTester tester, {
timeout: Timeout.none,
}) async {
app.main();
await tester.pumpAndSettle();
// expect("y'a pas de lézard !", findsOneWidget);
await tester.tap(find.byKey(Key('manageWallets')));
print(
'####################################################################');
// If a wallet exist, go to delete theme all
await tester.pumpAndSettle();
if (!await isPresent(
"Je ne connais pour linstant aucun de vos portefeuilles.\n\nVous pouvez en créer un nouveau, ou bien importer un portefeuille Cesium existant.")) {
await tester.pumpAndSettle();
// await tester.pageBack();
await goBack();
await sleep(500);
await deleteAllWallets();
await sleep(300);
await tester.tap(find.byKey(Key('manageWallets')));
}
await tester.pumpAndSettle();
// Verify onboarding is starting, with text
expect(
"Je ne connais pour linstant aucun de vos portefeuilles.\n\nVous pouvez en créer un nouveau, ou bien importer un portefeuille Cesium existant.",
findsOneWidget);
});
// test('OnBoarding - Go to create restore sentance', (
// {timeout: Timeout.none}) async {
// await tapOn('goStep1');
// await tapOn('goStep2');
// await tapOn('goStep3');
// await tapOn('goStep4');
// await tapOn('goStep5');
// await tapOn('goStep6');
// expect(
// "Jai généré votre phrase de restauration !\nTâchez de la garder bien secrète, car elle permet à quiconque la connaît daccéder à tous vos portefeuilles.",
// findsOneWidget);
// });
// test('OnBoarding - Generate sentance and confirme it', (
// {timeout: Timeout.none}) async {
// await tapOn('goStep7');
// await tester.pumpAndSettle();
// Future selectWord() async {
// List words = [for (var i = 1; i <= 13; i += 1) i];
// for (var j = 1; j < 13; j++) {
// words[j] = await getText('word$j');
// }
// expect(await getText('step7'),
// "C'est le moment de noter votre phrase !");
// await tapOn('goStep8');
// await sleep(200);
// String goodWord = words[int.parse(
// await getText('askedWord'),
// )];
// // Enter the expected word
// await tester.enterText(find.byKey(Key('inputWord')), goodWord);
// // Check if word is valid
// expect(find.text("C'est le bon mot !"), findsOneWidget);
// // Continue onboarding workflow
// await tapOn('goStep9');
// }
// await selectWord();
// //Go back 2 times to mnemonic generation screen
// await goBack();
// await goBack();
// await sleep(100);
// // Generate 3 times mnemonic
// await tapOn('generateMnemonic');
// await tapOn('generateMnemonic');
// await tapOn('generateMnemonic');
// await sleep(500);
// await selectWord();
// });
// test('OnBoarding - Generate secret code and confirm it', (
// {timeout: Timeout.none}) async {
// expect(await getText('step9'),
// "Super !\n\nJe vais maintenant créer votre code secret. \n\nVotre code secret chiffre votre trousseau de clefs, ce qui le rend inutilisable par dautres, par exemple si vous perdez votre téléphone ou si on vous le vole.");
// await tapOn('goStep10');
// await tapOn('goStep11');
// while (await getText('generatedPin') == '') {
// print('Waiting for pin code generation...');
// await sleep(100);
// }
// // Change secret code 4 times
// for (int i = 0; i < 4; i++) await tapOn('changeSecretCode');
// await sleep(500);
// pinCode = await getText('generatedPin');
// await tapOn('goStep12');
// await sleep(300);
// // //Enter bad secret code
// // await tester.enterText('abcde');
// // await tapOn('formKey');
// // await sleep(1500);
// // await tapOn('formKey2');
// //Enter good secret code
// await tester.enterText(find.byKey(Key('formKey2')), pinCode);
// expect(await getText('step13'),
// "Top !\n\nVotre trousseau de clef et votre portefeuille ont été créés avec un immense succès.\n\nFélicitations !");
// });
// test('My wallets - Rename first derivation', (
// {timeout: const Duration(seconds: 2)}) async {
// await tapOn('goWalletHome');
// expect(await getText('myWallets'), "Mes portefeuilles");
// await sleep(300);
// // Go to first derivation and rename it
// await tester.tap(find.text('Mon portefeuille courant'));
// await sleep(300);
// await tapOn('renameWallet');
// await sleep(100);
// await tapOn('walletName');
// await sleep(100);
// await tester.enterText(
// find.byKey(Key('walletName')), 'Renommage wallet 1');
// await sleep(300);
// await tapOn('renameWallet');
// await sleep(400);
// expect('Renommage wallet 1', findsOneWidget);
// await goBack();
// });
// test('My wallets - Create a derivations, open thems, tap all buttons', (
// {timeout: const Duration(seconds: 2)}) async {
// expect('Renommage wallet 1', findsOneWidget);
// // Add a second derivation
// await createDerivation('Derivation 2');
// // Go to second derivation options
// await tester.tap(find.text('Derivation 2'));
// await sleep(100);
// // Test options
// await tapOn('displayBalance');
// await tapOn('displayHistory');
// await sleep(300);
// await goBack();
// await tapOn('displayBalance');
// await sleep(100);
// await tapOn('displayBalance');
// await sleep(100);
// await tapOn('displayBalance');
// await tapOn('setDefaultWallet');
// await sleep(50);
// await tapOn('copyPubkey');
// expect('Cette clé publique a été copié dans votre presse-papier.',
// findsOneWidget);
// await goBack();
// // Add a third derivation
// await createDerivation('Derivation 3');
// // Add a fourth derivation
// await createDerivation('Derivation 4');
// await sleep(50);
// // Go to third derivation options
// await tester.tap(find.text('Derivation 3'));
// await sleep(100);
// await tapOn('displayBalance');
// // Delete third derivation
// await deleteWallet(true);
// });
// test('My wallets - Extra tests', (
// {timeout: const Duration(seconds: 2)}) async {
// // Add derivation 5,6 and 7
// expect('Derivation 4', findsOneWidget);
// await createDerivation('Derivation 5');
// await createDerivation('Derivation 6');
// await createDerivation('Derivation 7');
// // Go home and come back to my wallets view
// await goBack();
// await sleep(100);
// await tapOn('manageWallets');
// await sleep(200);
// //Enter secret code
// await tester.enterText(find.byKey(Key('formKey')), pinCode);
// await sleep(200);
// // Go to derivation 6 and delete it
// await tester.tap(find.text('Derivation 6'));
// await sleep(100);
// await deleteWallet(true);
// // Go to 2nd derivation and check if it's de default
// await tester.tap(find.text('Derivation 2'));
// expect('Ce portefeuille est celui par defaut', findsOneWidget);
// await tapOn('setDefaultWallet');
// await sleep(100);
// expect('Ce portefeuille est celui par defaut', findsOneWidget);
// await sleep(300);
// // Display history, copy pubkey, go back and rename wallet name
// await tapOn('displayHistory');
// await sleep(400);
// await tapOn('copyPubkey');
// expect('Cette clé publique a été copié dans votre presse-papier.',
// findsOneWidget);
// await sleep(800);
// await goBack();
// await sleep(300);
// await tapOn('renameWallet');
// await sleep(100);
// await tapOn('walletName');
// await sleep(100);
// await tester.enterText(
// find.byKey(Key('walletName')), 'Renommage wallet 2');
// await sleep(300);
// await tapOn('renameWallet');
// await sleep(400);
// await goBack();
// expect('Renommage wallet 2', findsOneWidget);
// await createDerivation('Derivation 8');
// await createDerivation('Derivation 9');
// await createDerivation('Derivation 10');
// await createDerivation('Derivation 11');
// await createDerivation('Derivation 12');
// await createDerivation('Derivation 13');
// await createDerivation('Derivation 14');
// await createDerivation('Derivation 15');
// await createDerivation('Derivation 16');
// await createDerivation('Derivation 17');
// await createDerivation('Derivation 18');
// await createDerivation('Derivation 19');
// await createDerivation('Derivation 20');
// await sleep(400);
// // Scroll the wallet screen until Derivation 20 and open it
// await tester.scrollUntilVisible(find.byKey(Key('listWallets')), -300.0);
// expect('Derivation 20', findsOneWidget);
// await sleep(400);
// await tester.tap(find.text('Derivation 20'));
// await tapOn('copyPubkey');
// });
// test('Search - Search Pi profile, navigate in history transactions', (
// {timeout: const Duration(seconds: 2)}) async {
// expect('Derivation 20', findsOneWidget);
// await goBack();
// await goBack();
// await sleep(200);
// await tapOn('searchIcon');
// await sleep(400);
// await tester.enterText(find.byKey(Key('searchInput')),
// 'D2meevcAHFTS2gQMvmRW5Hzi25jDdikk4nC4u1FkwRaU');
// await sleep(100);
// await tapOn('copyPubkey');
// await sleep(500);
// await tapOn('switchPayHistory');
// await sleep(1200);
// // await tester.scrollIntoView(find.byValueKey('listTransactions'));
// await tester.scrollUntilVisible(
// find.byKey(Key('listTransactions')),
// -600.0,
// );
// await sleep(100);
// await tapOn('transaction33');
// expect('Commentaire:', findsOneWidget);
// // Want to paste pubkey copied, but doesn't work actualy with flutter driver: https://github.com/flutter/flutter/issues/47448
// // final ClipboardData pubkeyCopied =
// // await Clipboard.getData(Clipboard.kTextPlain);
// // await tester.enterText(pubkeyCopied.text);
// await sleep(300);
// }, timeout: Timeout(Duration(minutes: globalTimeout)));
// test('Wallet generation - Fast wallets generations', (
// {timeout: const Duration(seconds: 2)}) async {
// expect('Commentaire:', findsOneWidget);
// await goBack();
// await goBack();
// await deleteAllWallets();
// await sleep(100);
// final String pincode = await createNewKeychain('Fast wallet');
// await sleep(100);
// await tapOn('manageWallets');
// await sleep(200);
// await tester.enterText(find.byKey(Key('formKey')), pinCode);
// await sleep(100);
// await createDerivation('Derivation 2');
// await sleep(100);
// await tester.tap(find.text('Fast wallet'));
// expect('Fast wallet', findsOneWidget);
// // Wait 3 seconds at the end
// await sleep(3000);
// });
},
);
}

View File

@ -0,0 +1,79 @@
{
"first_ud": 10000,
"first_ud_reeval": 50,
"genesis_parameters": {
"genesis_certs_expire_on": 10,
"genesis_certs_min_received": 3,
"genesis_memberships_expire_on": 1051200,
"genesis_smith_certs_expire_on": 2102400,
"genesis_smith_certs_min_received": 3,
"genesis_smith_memberships_expire_on": 1051200
},
"identities": {
"test1": {
"balance": 10000,
"certs": ["test2", "test3", "test4"],
"pubkey": "5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa"
},
"test2": {
"balance": 10000,
"certs": ["test1", "test3", "test4"],
"pubkey": "5E4i8vcNjnrDp21Sbnp32WHm2gz8YP3GGFwmdpfg5bHd8Whb"
},
"test3": {
"balance": 10000,
"certs": ["test1", "test2", "test4"],
"pubkey": "5FhTLzXLNBPmtXtDBFECmD7fvKmTtTQDtvBTfVr97tachA1p"
},
"test4": {
"balance": 10000,
"certs": ["test1", "test2", "test3"],
"pubkey": "5DXJ4CusmCg8S1yF6JGVn4fxgk5oFx42WctXqHZ17mykgje5"
},
"testCesium1": {
"balance": 10000,
"certs": ["test1", "test2", "test3"],
"pubkey": "5GAT6CJW8yVKwUuQc7sM5Kk9GZVTpbZYk9PfjNXtvnNgAJZ1"
}
},
"parameters": {
"babe_epoch_duration": 30,
"cert_period": 15,
"cert_max_by_issuer": 10,
"cert_min_received_cert_to_issue_cert": 2,
"cert_validity_period": 1000,
"idty_confirm_period": 40,
"idty_creation_period": 50,
"membership_period": 1000,
"pending_membership_period": 500,
"ud_creation_period": 10,
"ud_reeval_period": 50,
"smith_cert_period": 15,
"smith_cert_max_by_issuer": 8,
"smith_cert_min_received_cert_to_issue_cert": 2,
"smith_cert_validity_period": 1000,
"smith_membership_period": 1000,
"smith_pending_membership_period": 500,
"smiths_wot_first_cert_issuable_on": 4,
"smiths_wot_min_cert_for_membership": 3,
"wot_first_cert_issuable_on": 0,
"wot_min_cert_for_create_idty_right": 3,
"wot_min_cert_for_membership": 3
},
"smiths": {
"test1": {
"certs": ["test2", "test3", "test4"]
},
"test2": {
"certs": ["test1", "test3", "test4"]
},
"test3": {
"certs": ["test1", "test2", "test4"]
},
"test4": {
"certs": ["test1", "test2", "test3"]
}
},
"sudo_key": "5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa",
"technical_committee": ["test1", "test2", "test3"]
}

View File

@ -0,0 +1,18 @@
version: "3.5"
services:
duniter-v2s-gecko-tests:
container_name: duniter-v2s-gecko-tests
image: duniter/duniter-v2s:debug-latest
command: --sealing=manual
ports:
- "127.0.0.1:9615:9615"
- "127.0.0.1:9933:9933"
- "0.0.0.0:9944:9944"
- "30333:30333"
environment:
DUNITER_INSTANCE_NAME: "gecko_tests"
DUNITER_CHAIN_NAME: "dev"
DUNITER_GENESIS_CONFIG: "/var/lib/duniter/gecko_tests.json"
volumes:
- ./data:/var/lib/duniter

View File

@ -0,0 +1,6 @@
#!/bin/bash
for test_file in $(ls integration_test/scenarios/); do
testName=$(echo $test_file | awk -F '.' '{print $1}')
./integration_test/launch_test.sh $testName || break
done

29
integration_test/launch_test.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
testName=$1
option=$2
[[ ! $testName ]] && testName='gecko_complete'
# Get local IP and set .env
ip_address=$(hostname -I | awk '{print $1}')
echo "ip_address=$ip_address" > .env
[[ $option == 'human' ]] && echo "isHumanReading=true" >> .env
## Start local Duniter node
cd integration_test/duniter
docker compose down
rm -rf data/chains
docker compose up -d
cd ../..
# Start integration test
flutter test integration_test/scenarios/$testName.dart && echo '0' > /tmp/geckoTestResult || echo '1' > /tmp/geckoTestResult
# Reset .env
echo "ip_address=127.0.0.1" > .env
# Stop Duniter
cd integration_test/duniter
docker compose down

View File

@ -0,0 +1,61 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('Certifications state', (testerLoc) async {
tester = testerLoc;
// Connect local node and import test chest in background
await bkFastStart();
// Open chest
await firstOpenChest();
spawnBlock(until: 15);
await goBack();
// Go wallet 5 view
await tapKey(keyOpenSearch);
await enterText(keySearchField, test5.address);
await tapKey(keyConfirmSearch);
await waitFor(test5.shortAddress());
await tapKey(keySearchResult(test5.address));
await waitFor('Certifier');
await waitFor('Vous devez ', reverse: true);
await waitFor('Vous pourrez renouveler ', reverse: true);
// Background pay 25
await bkPay(
fromAddress: test1.address, destAddress: test5.address, amount: 25);
await waitFor('25.0', exactMatch: true);
await spawnBlock();
await waitFor('22.0', exactMatch: true);
await bkCertify(
fromAddress: test1.address,
destAddress: test5.address,
spawnBloc: false);
await bkConfirmIdentity(fromAddress: test5.address, name: test5.name);
await waitFor('1', exactMatch: true);
await bkCertify(
fromAddress: test2.address,
destAddress: test5.address,
spawnBloc: false);
// await waitFor('2', exactMatch: true);
await bkCertify(fromAddress: test3.address, destAddress: test5.address);
await waitFor('3', exactMatch: true);
await bkCertify(fromAddress: test4.address, destAddress: test5.address);
await waitFor('4', exactMatch: true);
// await bkPay(
// fromAddress: test2.address, destAddress: test5.address, amount: 40);
await waitFor('21.99', exactMatch: true);
await spawnBlock(until: 30);
await waitFor('121.99', exactMatch: true);
await spawnBlock(until: 40);
await waitFor('221.99', exactMatch: true);
}, timeout: testTimeout());
}

View File

@ -0,0 +1,140 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('Gecko complete', (testerLoc) async {
// Share WidgetTester to test provider
tester = testerLoc;
// Start app and wait finish starting
await startWait();
// Change Duniter endpoint to local
await changeNode();
// Delete all existing chests is exists
await deleteAllWallets();
// Restore the test chest
await restoreChest();
// Execute a transaction to test5
await payTest2();
// Certify test5 account with 3 accounts to become member
await certifyTest5();
}, timeout: testTimeout());
}
Future payTest2() async {
spawnBlock(until: 13);
await waitFor('Rechercher');
await tapKey(keyOpenSearch);
final addressToSearch = await clipPaste();
final endAddress = addressToSearch.substring(addressToSearch.length - 6);
expect(addressToSearch, test5.address);
await enterText(keySearchField, addressToSearch);
await tapKey(keyConfirmSearch);
await waitFor(endAddress);
await tapKey(keySearchResult(addressToSearch));
await waitFor(endAddress);
await waitFor('0.0', exactMatch: true);
await tapKey(keyPay);
await enterText(keyAmountField, '12.14');
await tapKey(keyConfirmPayment);
spawnBlock(duration: 500);
await waitFor('validé !', timeout: const Duration(seconds: 1));
await tapKey(keyCloseTransactionScreen, duration: 0);
await waitFor('12.14');
spawnBlock(duration: 500);
await waitFor('9.14');
humanRead(2);
}
Future certifyTest5() async {
// Create identity with Test1 account
await tapKey(keyCertify);
await tapKey(keyConfirm);
spawnBlock(duration: 500);
await waitFor('validé !', timeout: const Duration(seconds: 1));
await tapKey(keyCloseTransactionScreen);
await waitFor('Identité créée');
// Confirm Identity Test5
await tapKey(keyAppBarChest, duration: 300);
await tapKey(keyOpenWallet(test5.address));
await tapKey(keyCopyAddress);
humanRead(3);
await tapKey(keyConfirmIdentity);
await enterText(keyEnterIdentityUsername, test5.name);
await tapKey(keyConfirm);
spawnBlock(duration: 500);
await waitFor('validé !', timeout: const Duration(seconds: 1));
await tapKey(keyCloseTransactionScreen);
await waitFor('Identité confirmée');
humanRead(2);
// Set wallet 2 as default wallet
await goBack();
await tapKey(keyOpenWallet(test2.address));
await tapKey(keySetDefaultWallet);
await waitFor('Ce portefeuille est celui par defaut');
// Search Wallet 5 again
await tapKey(keyAppBarSearch);
final addressToSearch = await clipPaste();
final endAddress = addressToSearch.substring(addressToSearch.length - 6);
expect(addressToSearch, test5.address);
await enterText(keySearchField, addressToSearch);
await tapKey(keyConfirmSearch);
await waitFor(endAddress);
await tapKey(keySearchResult(addressToSearch));
await waitFor(endAddress);
await waitFor('1');
// Certify with test2 account
await tapKey(keyCertify);
await tapKey(keyConfirm);
spawnBlock(duration: 500);
await waitFor('validé !', timeout: const Duration(seconds: 1));
await tapKey(keyCloseTransactionScreen);
await waitFor('2');
// Change default wallet to test3
await tapKey(keyPay);
await tapKey(keyChangeChest);
await tapKey(keySelectThisWallet(test3.address));
await tapKey(keyConfirm);
await sleep();
// Certify with test3 account
await tapKey(keyCertify);
await tapKey(keyConfirm);
spawnBlock(duration: 500);
await waitFor('validé !', timeout: const Duration(seconds: 1));
await tapKey(keyCloseTransactionScreen);
await waitFor('Vous devez attendre');
// Check if test5 is member
await tapKey(keyAppBarChest, duration: 300);
await tapKey(keyOpenWallet(test5.address));
await waitFor('Membre validé !');
// spawn 20 blocs and check if ud is creating
await spawnBlock(until: 20);
await waitFor('109.13');
await spawnBlock(until: 30);
await waitFor('209.13');
// Check UD reval
await spawnBlock(until: 60);
await waitFor('509.57');
humanRead(5);
}

View File

@ -0,0 +1,67 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('Identity revocation', (testerLoc) async {
tester = testerLoc;
// Connect local node and import test chest in background
await bkFastStart();
// Open chest
await firstOpenChest();
await spawnBlock(until: 13);
await sleep();
// Create test5 identity
await bkPay(
fromAddress: test1.address, destAddress: test5.address, amount: 30);
sub.reload();
await bkCertify(fromAddress: test1.address, destAddress: test5.address);
sub.reload();
await sleep();
// Certify test5 to become member
await tapKey(keyOpenWallet(test5.address));
await bkConfirmIdentity(fromAddress: test5.address, name: test5.name);
await bkCertify(fromAddress: test2.address, destAddress: test5.address);
await bkCertify(fromAddress: test3.address, destAddress: test5.address);
await waitFor('Membre validé !', exactMatch: true);
// Revoke test5
await tapKey(keyManageMembership, duration: 1000);
await tapKey(keyRevokeIdty);
await tapKey(keyConfirm);
spawnBlock(duration: 2000);
await waitFor('validé !', timeout: const Duration(seconds: 4));
await tapKey(keyCloseTransactionScreen, duration: 0);
await waitFor('Aucune identité', exactMatch: true);
await sleep();
// Check test1 cannot be revoked
await goBack();
await tapKey(keyAddDerivation);
await tapKey(keyOpenWallet(test1.address), duration: 500);
await tapKey(keyManageMembership, duration: 1000);
await waitFor('Vous ne pouvez pas révoquer cette identité');
// // Try migrate test1 identity to test6 address
// await tapKey(keyMigrateIdentity);
// await tapKey(keySelectWallet);
// await tapKey(keySelectThisWallet(test6.address), selectLast: true);
// await spawnBlock(number: 100);
// await waitFor('Vous devez attendre', reverse: true);
// await waitForButtonEnabled(keyConfirm);
// await tapKey(keyConfirm, duration: 500);
// await spawnBlock(duration: 2000);
// await waitFor('validé !');
// await tapKey(keyCloseTransactionScreen, duration: 0);
// await sleep(5000);
}, timeout: testTimeout());
}

View File

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('Migrate Cesium identity and balance', (testerLoc) async {
tester = testerLoc;
// Connect local node and import test chest in background
await bkFastStart();
// Open chest
await firstOpenChest();
// Go to test1 options and check if balance growup with UDs creations
await tapKey(keyAddDerivation);
await waitFor('Portefeuille 6');
await scrollUntil(keyImportG1v1);
await tapKey(keyImportG1v1);
await enterText(keyCesiumId, 'test');
await enterText(keyCesiumPassword, 'test');
await waitFor(cesiumTest1.shortAddress());
await waitFor('100.0');
await waitFor('3', exactMatch: true);
isObscureText();
await tapKey(keyCesiumIdVisible);
await tester.pumpAndSettle();
isObscureText(false);
await tapKey(keyCesiumIdVisible);
await tester.pumpAndSettle();
isObscureText();
await tapKey(keySelectWallet);
await tapKey(keySelectThisWallet(test6.address), selectLast: true);
await waitForButtonEnabled(keyConfirm);
await tapKey(keyConfirm);
spawnBlock(duration: 2000);
await waitFor('validé !');
await tapKey(keyCloseTransactionScreen, duration: 0);
await tapKey(keyOpenWallet(test6.address), duration: 300);
await waitFor('3', exactMatch: true);
await waitFor('Membre validé !');
await waitFor('99.98', exactMatch: true);
}, timeout: testTimeout());
}
isObscureText([bool isObscure = true]) {
final passwordTextFormField = find.descendant(
of: find.byKey(keyCesiumId),
matching: find.byType(EditableText),
);
final input = tester.widget<EditableText>(passwordTextFormField);
expect(input.obscureText, isObscure ? isTrue : isFalse);
}

View File

@ -0,0 +1,16 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('Onboarding and multi chest', (testerLoc) async {
tester = testerLoc;
await bkFastStart(false);
await onboardingNewChest();
}, timeout: testTimeout());
}

View File

@ -0,0 +1,28 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:integration_test/integration_test.dart';
import '../utility/general_actions.dart';
import '../utility/tests_utility.dart';
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
testWidgets('UDs creation state', (testerLoc) async {
tester = testerLoc;
// Connect local node and import test chest in background
await bkFastStart();
// Open chest
await firstOpenChest();
// Go to test1 options and check if balance growup with UDs creations
await tapKey(keyOpenWallet(test1.address));
await waitFor('100.0', exactMatch: true);
await spawnBlock(until: 10);
await waitFor('200.0', exactMatch: true);
await spawnBlock(until: 20);
await waitFor('300.0', exactMatch: true);
}, timeout: testTimeout());
}

View File

@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/generate_wallets.dart';
import 'package:provider/provider.dart';
import 'tests_utility.dart';
// GENERAL ACTIONS
Future changeNode() async {
final ipAddress = dotenv.env['ip_address'] ?? '127.0.0.1';
log.d('ip address: $ipAddress');
await tapKey(keyDrawerMenu);
await tapKey(keyParameters);
await tapKey(keySelectDuniterNodeDropDown, duration: 5);
await tapKey(keySelectDuniterNode('Personnalisé'), selectLast: true);
await enterText(keyCustomDuniterEndpoint, 'ws://$ipAddress:9944');
await tapKey(keyConnectToEndpoint);
await isIconPresent(Icons.add_card_sharp,
timeout: const Duration(seconds: 8));
await goBack();
}
Future deleteAllWallets() async {
if (await isPresent('Rechercher')) {
await tapKey(keyDrawerMenu);
await tapKey(keyParameters);
// Check if ud unit checkbox is checked
final isUdUnit = await isIconPresent(Icons.check_box);
// If yes, tap on to use currency value
if (isUdUnit) await tapKey(keyUdUnit, duration: 0);
await tapKey(keyDeleteAllWallets);
await tapKey(keyConfirm);
await tester.pumpAndSettle();
}
}
Future restoreChest() async {
// Copy test mnemonic in clipboard
await clipCopy(testMnemonic);
// Open screen import chest
await tapKey(keyRestoreChest, duration: 0);
// Tap on button to paste mnemonic
await tapKey(keyPastMnemonic);
// Tap on next button 4 times to skip 3 screen
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
// Check if cached password checkbox is checked
final isCached = await isIconPresent(Icons.check_box);
// If not, tap on to cache password
if (!isCached) await tapKey(keyCachePassword, duration: 0);
// Enter password
await enterText(keyPinForm, 'AAAAA', 0);
// Check if string "Accéder à mon coffre" is present in screen
await waitFor('Accéder à mon coffre');
// Go to wallets home
await tapKey(keyGoWalletsHome, duration: 0);
// Check if string "ĞD" is present in screen
await waitFor('ĞD');
// Tap on add a new derivation button
await addDerivation();
// Tap on Wallet 5
await tapKey(keyOpenWallet(test5.address));
// Copy address of Wallet 5
await tapKey(keyCopyAddress);
// Check if string "Cette adresse a été copié" is present in screen
await waitFor('Cette adresse a été copié');
// Pop screen 2 time to go back home
await goBack();
await goBack();
}
Future onboardingNewChest() async {
final generateWalletProvider =
Provider.of<GenerateWalletsProvider>(homeContext, listen: false);
// Open screen create new wallet
await tapKey(keyOnboardingNewChest);
// Tap on next button 4 times to skip 3 screen
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await waitFor('7', exactMatch: true);
final word41 = getWidgetText(keyMnemonicWord('4'));
// Change 2 times mnemonic
await tapKey(keyGenerateMnemonic);
await tester.pumpAndSettle();
final word42 = getWidgetText(keyMnemonicWord('4'));
expect(word41, isNot(word42));
await tapKey(keyGenerateMnemonic, duration: 500);
await tester.pumpAndSettle();
final word43 = getWidgetText(keyMnemonicWord('4'));
expect(word42, isNot(word43));
// Go next screen
await tapKey(keyGoNext);
await tester.pumpAndSettle();
// Enter asked word
final askedWordNumber = int.parse(getWidgetText(keyAskedWord));
List mnemonic = generateWalletProvider.generatedMnemonic!.split(' ');
final askedWord = mnemonic[askedWordNumber - 1];
await enterText(keyInputWord, askedWord);
await waitFor('Continuer', exactMatch: true);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await tapKey(keyGoNext);
await waitFor('AAAAA', exactMatch: true);
await tapKey(keyGoNext);
// Check if cached password checkbox is checked
final isCached = await isIconPresent(Icons.check_box);
// If not, tap on to cache password
if (!isCached) await tapKey(keyCachePassword, duration: 0);
// Enter password
await enterText(keyPinForm, 'AAAAA', 0);
// Check if string "Accéder à mon coffre" is present in screen
await waitFor('Accéder à mon coffre');
// Go to wallets home
await tapKey(keyGoWalletsHome, duration: 0);
// Check if string "Mon portefeuille co" is present in screen
await waitFor('Mon portefeuille co');
await waitFor('0.0', exactMatch: true);
// await waitFor('Scanner un');
}
Future addDerivation() async {
await tapKey(keyAddDerivation);
await waitFor('Portefeuille 5');
}
Future firstOpenChest() async {
await tapKey(keyOpenWalletsHomme);
sleep(300);
final isCached = await isIconPresent(Icons.check_box);
if (!isCached) await tapKey(keyCachePassword, duration: 0);
await enterText(keyPinForm, 'AAAAA', 0);
await waitFor('100.0', exactMatch: true);
}

View File

@ -0,0 +1,328 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/generate_wallets.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:provider/provider.dart';
import 'dart:io' as io;
import 'package:gecko/main.dart' as app;
final bool isHumanReading =
dotenv.env['isHumanReading'] == 'true' ? true : false;
Timeout testTimeout([int seconds = 120]) =>
Timeout(Duration(seconds: isHumanReading ? 600 : seconds));
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
late WidgetTester tester;
// TEST WALLETS CONSTS
const testMnemonic =
'pipe paddle ketchup filter life ice feel embody glide quantum ride usage';
final test1 =
TestWallet('5FeggKqw2AbnGZF9Y9WPM2QTgzENS3Hit94Ewgmzdg5a3LNa', 'test1');
final test2 =
TestWallet('5E4i8vcNjnrDp21Sbnp32WHm2gz8YP3GGFwmdpfg5bHd8Whb', 'test2');
final test3 =
TestWallet('5FhTLzXLNBPmtXtDBFECmD7fvKmTtTQDtvBTfVr97tachA1p', 'test3');
final test4 =
TestWallet('5DXJ4CusmCg8S1yF6JGVn4fxgk5oFx42WctXqHZ17mykgje5', 'test4');
final test5 =
TestWallet('5Dq3giahrBfykJogPetZJ2jjSmhw49Fa7i6qKkseUvRJ2T3R', 'test5');
final test6 =
TestWallet('5GxEp3do81j97kNaH4JyZgDXuPoKWoTuxXXWGyyNXeKeVLHb', 'test6');
final test7 =
TestWallet('5FZ1sSvREbQLCtSSCvMUx7KCAnpJkB7q5mfz2oixiZq2ChET', 'test7');
final test8 =
TestWallet('5CoKV9EEgwb2NmWamTXUAa6ycfNb2k1iNfVGvJAkg7dLq9RH', 'test8');
final cesiumTest1 = TestWallet(
'5GAT6CJW8yVKwUuQc7sM5Kk9GZVTpbZYk9PfjNXtvnNgAJZ1', 'cesiumTest1');
final cesiumTest2 = TestWallet(
'5DTnny1tTkUs1SXHZTx98RUAj76Z88FfFhsQjd48dXnk8gHR', 'cesiumTest2');
final cesiumTest3 = TestWallet(
'5EJct9jTDNKco4YiYfETAseq1gaduBtsJUcNnFicfvh3bTV6', 'cesiumTest3');
final cesiumTest4 = TestWallet(
'5HD1oSv6A7VNxPYos6F86JFZ3bhz5LnEaWC4hkwLMj84v4ww', 'cesiumTest4');
// CUSTOM FUNCTIONS
Future sleep([int time = 1000]) async {
await Future.delayed(Duration(milliseconds: time));
}
Future<String> clipPaste() async =>
(await Clipboard.getData('text/plain'))?.text ?? '';
clipCopy(String text) async =>
await Clipboard.setData(ClipboardData(text: text));
Future humanRead([int time = 1, bool force = false]) async {
if (isHumanReading || force) io.sleep(Duration(seconds: time));
}
Future tapKey(Key buttonKey,
{Finder? customFinder, int duration = 100, bool selectLast = false}) async {
if (duration != 0) {
await tester.pumpAndSettle(Duration(milliseconds: duration));
}
final Finder finder = customFinder ?? find.byKey(buttonKey);
log.d('INTEGRATION TEST: Tap on ${finder.description}}');
await tester.tap(selectLast ? finder.last : finder);
humanRead();
}
Finder findByKey(Key key) {
return find.byKey(key);
}
bool isButtonEnabled(Key key) {
return tester.widget<ElevatedButton>(findByKey(key)).enabled;
}
Future scrollUntil(Key element) async {
final findList = find.byType(Scrollable);
final findElement = findByKey(element);
await tester.scrollUntilVisible(
findElement,
500.0,
scrollable: findList,
);
}
Future<void> waitForButtonEnabled(Key key,
{Duration timeout = const Duration(seconds: 5),
bool reverse = false}) async {
final end = DateTime.now().add(timeout);
log.d('INTEGRATION TEST: Wait for $key to be enabled');
do {
if (DateTime.now().isAfter(end)) {
throw Exception('Timed out waiting for button enabled: $key');
}
await tester.pumpAndSettle();
await Future.delayed(const Duration(milliseconds: 100));
} while (reverse ? isButtonEnabled(key) : !isButtonEnabled(key));
humanRead();
}
Future goBack() async {
final NavigatorState navigator = tester.state(find.byType(Navigator));
log.d('INTEGRATION TEST: Go back');
navigator.pop();
await tester.pump();
humanRead();
}
Future enterText(Key fieldKey, String textIn, [int duration = 200]) async {
if (duration != 0) {
await tester.pumpAndSettle(Duration(milliseconds: duration));
}
log.d('INTEGRATION TEST: Enter text: $textIn');
await tester.enterText(find.byKey(fieldKey), textIn);
humanRead();
}
Future<void> waitFor(String text,
{Duration timeout = const Duration(seconds: 5),
bool reverse = false,
bool exactMatch = false}) async {
final end = DateTime.now().add(timeout);
Finder finder = exactMatch ? find.text(text) : find.textContaining(text);
log.d('INTEGRATION TEST: Wait for: $text');
final String searchType = reverse ? 'reversed text' : 'text';
do {
if (DateTime.now().isAfter(end)) {
throw Exception('Timed out waiting for $searchType : "$text"');
}
await tester.pumpAndSettle();
await Future.delayed(const Duration(milliseconds: 100));
} while (reverse ? finder.evaluate().isNotEmpty : finder.evaluate().isEmpty);
humanRead();
}
// Test if text is visible on screen, return a boolean
Future<bool> isPresent(String text,
{Duration timeout = const Duration(seconds: 1)}) async {
try {
await waitFor(text, timeout: timeout);
humanRead();
return true;
} catch (exception) {
humanRead();
return false;
}
}
// Test if widget exist on screen, return a boolean
Future<bool> isIconPresent(IconData icon,
{Duration timeout = const Duration(seconds: 1)}) async {
await tester.pumpAndSettle();
final finder = find.byIcon(icon);
log.d('tatatatatatata: ${finder.evaluate()}');
humanRead();
return finder.evaluate().isEmpty ? false : true;
}
Future spawnBlock({int number = 1, int duration = 200, int? until}) async {
if (duration != 0) {
await sleep(duration);
}
if (until != null) {
number = until - sub.blocNumber;
}
await sub.spawnBlock(number);
await sleep(200);
}
// Pay in background
Future bkPay(
{required String fromAddress,
required String destAddress,
required double amount}) async {
sub.pay(
fromAddress: fromAddress,
destAddress: destAddress,
amount: amount,
password: 'AAAAA');
await sleep(500);
await spawnBlock();
await sleep(500);
}
// Certify in background
Future bkCertify(
{required String fromAddress,
required String destAddress,
bool spawnBloc = true}) async {
sub.certify(fromAddress, destAddress, 'AAAAA');
if (spawnBloc) {
await sleep(500);
await spawnBlock();
}
await sleep(500);
}
// Confirm my identity in background
Future bkConfirmIdentity(
{required String fromAddress, required String name}) async {
sub.confirmIdentity(fromAddress, name, 'AAAAA');
await sleep(500);
await spawnBlock();
await sleep(500);
}
// Change node in background
Future bkSetNode([String? endpoint]) async {
if (endpoint == null) {
final ipAddress = dotenv.env['ip_address'] ?? '127.0.0.1';
endpoint = 'ws://$ipAddress:9944';
}
configBox.put('customEndpoint', endpoint);
sub.connectNode(homeContext);
}
// Restore chest in background
Future bkRestoreChest([String mnemonic = testMnemonic]) async {
final myWalletProvider =
Provider.of<MyWalletsProvider>(homeContext, listen: false);
final generateWalletProvider =
Provider.of<GenerateWalletsProvider>(homeContext, listen: false);
await generateWalletProvider.storeHDWChest(homeContext);
for (int number = 0; number <= 4; number++) {
await _addImportAccount(
mnemonic: mnemonic,
chest: 0,
number: number,
name: 'test${number + 1}',
derivation: (number + 1) * 2);
}
myWalletProvider.reload();
}
Future<WalletData> _addImportAccount(
{required String mnemonic,
required int chest,
required int number,
required String name,
required int derivation}) async {
final address = await sub.importAccount(
mnemonic: mnemonic, derivePath: '//$derivation', password: 'AAAAA');
final myWallet = WalletData(
version: dataVersion,
chest: chest,
address: address,
number: number,
name: name,
derivation: derivation,
imageDefaultPath: '${number % 4}.png');
await walletBox.add(myWallet);
return myWallet;
}
// Delete all wallets in background
Future bkDeleteAllWallets() async {
final myWalletProvider =
Provider.of<MyWalletsProvider>(homeContext, listen: false);
final isWalletsPresents =
await isPresent('Scanner un', timeout: const Duration(milliseconds: 300));
if (isWalletsPresents) {
await walletBox.clear();
await chestBox.clear();
await configBox.delete('defaultWallet');
await configBox.delete('isUdUnit');
await sub.deleteAllAccounts();
myWalletProvider.pinCode = '';
myWalletProvider.reload();
}
}
Future bkFastStart([bool restoreChest = true]) async {
// Start app and wait finish starting
await startWait();
// Connect to local endpoint
await bkSetNode();
await sleep();
// Delete all existing chests is exists
await bkDeleteAllWallets();
if (restoreChest) {
// Restore the test chest
await bkRestoreChest();
await waitFor("y'a pas de lézard");
}
}
Future startWait() async {
app.main();
await waitFor('Test starting...', reverse: true);
await tester.pumpAndSettle(const Duration(milliseconds: 300));
await sleep(3000);
}
String getWidgetText(Key key) {
final word4Finder = find.byKey(key);
return (word4Finder.evaluate().single.widget as Text).data!;
}
class TestWallet {
String address;
String name;
TestWallet(this.address, this.name);
endAddress() => address.substring(address.length - 6);
shortAddress() => getShortPubkey(address);
}

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

41
ios/Podfile Normal file
View File

@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
@ -288,6 +288,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 72JY5XXU29;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -299,7 +300,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.gecko;
PRODUCT_BUNDLE_IDENTIFIER = gecko.axiomteam.fr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -420,6 +421,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 72JY5XXU29;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -431,7 +433,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.gecko;
PRODUCT_BUNDLE_IDENTIFIER = gecko.axiomteam.fr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -447,6 +449,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 72JY5XXU29;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -458,7 +461,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.gecko;
PRODUCT_BUNDLE_IDENTIFIER = gecko.axiomteam.fr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -492,4 +495,4 @@
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Camera permission is required for barcode scanning.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to user's is required for profile image upload in wallet management</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
@ -12,6 +16,8 @@
<string>6.0</string>
<key>CFBundleName</key>
<string>gecko</string>
<key>ITSAppUsesNonExemptEncryption</key>
<string>NO</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

View File

@ -3,37 +3,50 @@ import 'package:flutter/material.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:logger/logger.dart';
import 'package:shared_preferences/shared_preferences.dart';
// Files paths
Directory? appPath;
// Version of box data
const int dataVersion = 4;
late String appVersion;
late SharedPreferences prefs;
late String endPointGVA;
const int pinLength = 5;
const String appLang = 'french';
const String appLang = 'english';
late Box<WalletData> walletBox;
late Box<ChestData> chestBox;
late Box configBox;
late Box<G1WalletsList> g1WalletsBox;
late Box<G1WalletsList> contactsBox;
// late Box keystoreBox;
late Directory imageDirectory;
String cesiumPod = "https://g1.data.le-sou.org";
// String cesiumPod = "https://g1.data.le-sou.org";
String cesiumPod = "https://g1.data.presles.fr";
// String cesiumPod = "https://g1.data.e-is.pro";
// Responsive ratios
late bool isTall;
late double ratio;
// Contexts
late BuildContext homeContext;
// Logger
var log = Logger();
final log = Logger();
// Colors
Color orangeC = const Color(0xffd07316);
Color yellowC = const Color(0xffFFD68E);
Color floattingYellow = const Color(0xffEFEFBF);
Color backgroundColor = const Color(0xFFF5F5F5);
const Color orangeC = Color(0xffd07316);
const Color yellowC = Color(0xffFFD68E);
const Color floattingYellow = Color(0xffEFEFBF);
const Color backgroundColor = Color(0xFFF5F5F5);
// Substrate settings
const String currencyName = 'ĞD';
// Debug
const debugPin = true;
String indexerEndpoint = '';
late double balanceRatio;
late int udValue;

View File

@ -13,18 +13,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/providers/cesium_plus.dart';
import 'package:gecko/providers/change_pin.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/providers/chest_provider.dart';
import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/generate_wallets.dart';
import 'package:gecko/providers/settings_provider.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/providers/home.dart';
@ -37,58 +37,53 @@ import 'package:flutter/material.dart';
import 'package:gecko/screens/myWallets/wallets_home.dart';
import 'package:gecko/screens/search.dart';
import 'package:gecko/screens/search_result.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:window_size/window_size.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
const bool enableSentry = true;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
setWindowTitle('Ğecko');
setWindowMinSize(const Size(400, 700));
setWindowMaxSize(const Size(800, 1000));
await EasyLocalization.ensureInitialized();
if (kDebugMode) {
await dotenv.load();
}
HomeProvider _homeProvider = HomeProvider();
await _homeProvider.initHive();
appVersion = await _homeProvider.getAppVersion();
prefs = await SharedPreferences.getInstance();
HomeProvider homeProvider = HomeProvider();
// DuniterIndexer _duniterIndexer = DuniterIndexer();
await initHiveForFlutter();
await homeProvider.initHive();
appVersion = await homeProvider.getAppVersion();
// Reset GraphQL cache
// final cache = HiveStore();
// cache.reset();
// Configure Hive and open boxes
Hive.registerAdapter(WalletDataAdapter());
Hive.registerAdapter(ChestDataAdapter());
Hive.registerAdapter(G1WalletsListAdapter());
Hive.registerAdapter(IdAdapter());
// Hive.registerAdapter(KeyStoreDataAdapter());
walletBox = await Hive.openBox<WalletData>("walletBox");
chestBox = await Hive.openBox<ChestData>("chestBox");
configBox = await Hive.openBox("configBox");
await Hive.deleteBoxFromDisk('g1WalletsBox');
g1WalletsBox = await Hive.openBox<G1WalletsList>("g1WalletsBox");
// keystoreBox = await Hive.openBox("keystoreBox");
contactsBox = await Hive.openBox<G1WalletsList>("contactsBox");
g1WalletsBox.clear();
// final HiveStore _store =
// await HiveStore.open(path: '${appPath.path}/gqlCache');
// Get a valid GVA endpoint
// endPointGVA = 'https://g1.librelois.fr/gva';
endPointGVA = 'https://duniter-g1.p2p.legal/gva';
// await _homeProvider.getValidEndpoint();
// if (endPointGVA == 'HS') {
// _homeProvider.playSound('faché', 0.8);
// } else {
// _homeProvider.playSound('start', 0.2);
// }
await homeProvider.getValidEndpoints();
// await configBox.delete('isCacheChecked');
if (configBox.get('isCacheChecked') == null) {
configBox.put('isCacheChecked', false);
}
// log.d(await configBox.get('endpoint'));
HttpOverrides.global = MyHttpOverrides();
@ -103,102 +98,95 @@ Future<void> main() async {
// // ]);
// Catcher(rootWidget: Gecko(endPointGVA, _store), debugConfig: debugOptions);
await SentryFlutter.init((options) {
options.dsn =
'https://c09587b46eaa42e8b9fda28d838ed180@o496840.ingest.sentry.io/5572110';
}, appRunner: () => runApp(Gecko(endPointGVA)));
// runZoned<Future<void>>(
// () async {
// runApp(Gecko(endPointGVA, _store));
// },
// onError: (dynamic error, StackTrace stackTrace) {
// print("=================== CAUGHT DART ERROR");
// // Sentry.captureException(
// // error,
// // stackTrace: stackTrace,
// // );
// },
// ));
await SentryFlutter.init(
(options) {
options.dsn =
'https://c09587b46eaa42e8b9fda28d838ed180@o496840.ingest.sentry.io/5572110';
},
appRunner: () => runApp(
EasyLocalization(
supportedLocales: const [Locale('en'), Locale('fr'), Locale('es')],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: const Gecko(),
),
),
);
} else {
print('Debug mode enabled: No sentry alerte');
log.i('Debug mode enabled: No sentry alerte');
runApp(Gecko(endPointGVA));
runApp(
EasyLocalization(
supportedLocales: const [Locale('en'), Locale('fr'), Locale('es')],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
child: const Gecko(),
),
);
}
}
class Gecko extends StatelessWidget {
const Gecko(this.randomEndpoint, {Key? key}) : super(key: key);
final String? randomEndpoint;
const Gecko({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
final _httpLink = HttpLink(
randomEndpoint!,
);
final _client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(),
link: _httpLink,
),
);
// To configure multi_endpoints GraphQLProvider: https://stackoverflow.com/q/70656513/8301867
// HistoryProvider _historyProvider = Provider.of<HistoryProvider>(context);
// HistoryProvider('').snackNode(context);
return MultiProvider(
providers: [
// Provider(create: (context) => HistoryProvider()),
ChangeNotifierProvider(create: (_) => HomeProvider()),
ChangeNotifierProvider(create: (_) => WalletsProfilesProvider('')),
ChangeNotifierProvider(create: (_) => MyWalletsProvider()),
ChangeNotifierProvider(create: (_) => ChestProvider()),
ChangeNotifierProvider(create: (_) => GenerateWalletsProvider()),
ChangeNotifierProvider(create: (_) => WalletOptionsProvider()),
ChangeNotifierProvider(create: (_) => ChangePinProvider()),
ChangeNotifierProvider(create: (_) => SearchProvider()),
ChangeNotifierProvider(create: (_) => CesiumPlusProvider()),
ChangeNotifierProvider(create: (_) => SubstrateSdk())
ChangeNotifierProvider(create: (_) => SubstrateSdk()),
ChangeNotifierProvider(create: (_) => DuniterIndexer()),
ChangeNotifierProvider(create: (_) => SettingsProvider())
],
child: GraphQLProvider(
client: _client,
child: MaterialApp(
builder: (context, widget) => ResponsiveWrapper.builder(
BouncingScrollWrapper.builder(context, widget!),
maxWidth: 1200,
minWidth: 480,
defaultScale: true,
breakpoints: [
const ResponsiveBreakpoint.resize(480, name: MOBILE),
const ResponsiveBreakpoint.autoScale(800, name: TABLET),
const ResponsiveBreakpoint.resize(1000, name: DESKTOP),
],
background: Container(color: backgroundColor)),
title: 'Ğecko',
theme: ThemeData(
appBarTheme: const AppBarTheme(
color: Color(0xffFFD58D),
foregroundColor: Color(0xFF000000),
),
primaryColor: const Color(0xffFFD58D),
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 16),
bodyText2: TextStyle(fontSize: 18),
).apply(
bodyColor: const Color(0xFF000000),
),
colorScheme:
ColorScheme.fromSwatch().copyWith(secondary: Colors.grey[850]),
child: MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
builder: (context, widget) => ResponsiveWrapper.builder(
BouncingScrollWrapper.builder(context, widget!),
maxWidth: 1200,
minWidth: 480,
defaultScale: true,
breakpoints: [
const ResponsiveBreakpoint.resize(480, name: MOBILE),
const ResponsiveBreakpoint.autoScale(800, name: TABLET),
const ResponsiveBreakpoint.resize(1000, name: DESKTOP),
],
background: Container(color: backgroundColor)),
title: 'Ğecko',
theme: ThemeData(
appBarTheme: const AppBarTheme(
color: Color(0xffFFD58D),
foregroundColor: Color(0xFF000000),
),
home: const HomeScreen(),
initialRoute: "/",
routes: {
'/mywallets': (context) => const WalletsHome(),
'/search': (context) => const SearchScreen(),
'/searchResult': (context) => const SearchResultScreen(),
},
primaryColor: const Color(0xffFFD58D),
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 16),
bodyText2: TextStyle(fontSize: 18),
).apply(
bodyColor: const Color(0xFF000000),
),
colorScheme:
ColorScheme.fromSwatch().copyWith(secondary: Colors.grey[850]),
),
home: const HomeScreen(),
initialRoute: "/",
routes: {
'/mywallets': (context) => const WalletsHome(),
'/search': (context) => const SearchScreen(),
'/searchResult': (context) => const SearchResultScreen(),
},
),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -7,30 +7,26 @@ part 'chest_data.g.dart';
@HiveType(typeId: 1)
class ChestData extends HiveObject {
@HiveField(0)
String? dewif;
@HiveField(2)
String? name;
@HiveField(3)
@HiveField(1)
int? defaultWallet;
@HiveField(4)
@HiveField(2)
String? imageName;
@HiveField(5)
@HiveField(3)
File? imageFile;
@HiveField(6)
bool? isCesium;
@HiveField(4)
int? memberWallet;
ChestData({
this.dewif,
this.name,
this.defaultWallet,
this.imageName,
this.imageFile,
this.isCesium,
this.memberWallet,
});
@override

View File

@ -17,31 +17,28 @@ class ChestDataAdapter extends TypeAdapter<ChestData> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ChestData(
dewif: fields[0] as String?,
name: fields[2] as String?,
defaultWallet: fields[3] as int?,
imageName: fields[4] as String?,
imageFile: fields[5] as File?,
isCesium: fields[6] as bool?,
name: fields[0] as String?,
defaultWallet: fields[1] as int?,
imageName: fields[2] as String?,
imageFile: fields[3] as File?,
memberWallet: fields[4] as int?,
);
}
@override
void write(BinaryWriter writer, ChestData obj) {
writer
..writeByte(6)
..writeByte(0)
..write(obj.dewif)
..writeByte(2)
..write(obj.name)
..writeByte(3)
..write(obj.defaultWallet)
..writeByte(4)
..write(obj.imageName)
..writeByte(5)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.defaultWallet)
..writeByte(2)
..write(obj.imageName)
..writeByte(3)
..write(obj.imageFile)
..writeByte(6)
..write(obj.isCesium);
..writeByte(4)
..write(obj.memberWallet);
}
@override

View File

@ -6,28 +6,28 @@ part 'g1_wallets_list.g.dart';
@HiveType(typeId: 2)
class G1WalletsList {
@HiveField(0)
String? pubkey;
late String address;
@HiveField(1)
double? balance;
@HiveField(3)
@HiveField(2)
Id? id;
@HiveField(4)
@HiveField(3)
Image? avatar;
@HiveField(5)
@HiveField(4)
String? username;
@HiveField(6)
@HiveField(5)
String? csName;
@HiveField(7)
@HiveField(6)
bool? isMembre;
G1WalletsList({
this.pubkey,
required this.address,
this.balance,
this.id,
this.avatar,
@ -37,14 +37,14 @@ class G1WalletsList {
});
G1WalletsList.fromJson(Map<String, dynamic> json) {
pubkey = json['pubkey'];
address = json['pubkey'];
balance = json['balance'];
id = json['id'] != null ? Id.fromJson(json['id']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['pubkey'] = pubkey;
data['pubkey'] = address;
data['balance'] = balance;
if (id != null) {
data['id'] = id!.toJson();

View File

@ -17,13 +17,13 @@ class G1WalletsListAdapter extends TypeAdapter<G1WalletsList> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return G1WalletsList(
pubkey: fields[0] as String?,
address: fields[0] as String,
balance: fields[1] as double?,
id: fields[3] as Id?,
avatar: fields[4] as Image?,
username: fields[5] as String?,
csName: fields[6] as String?,
isMembre: fields[7] as bool?,
id: fields[2] as Id?,
avatar: fields[3] as Image?,
username: fields[4] as String?,
csName: fields[5] as String?,
isMembre: fields[6] as bool?,
);
}
@ -32,18 +32,18 @@ class G1WalletsListAdapter extends TypeAdapter<G1WalletsList> {
writer
..writeByte(7)
..writeByte(0)
..write(obj.pubkey)
..write(obj.address)
..writeByte(1)
..write(obj.balance)
..writeByte(3)
..writeByte(2)
..write(obj.id)
..writeByte(4)
..writeByte(3)
..write(obj.avatar)
..writeByte(5)
..writeByte(4)
..write(obj.username)
..writeByte(6)
..writeByte(5)
..write(obj.csName)
..writeByte(7)
..writeByte(6)
..write(obj.isMembre);
}

View File

@ -1,95 +0,0 @@
const String getHistory = r'''
query ($pubkey: String!, $number: Int!, $cursor: String) {
txsHistoryBc(
script: $pubkey
pagination: { pageSize: $number, ord: DESC, cursor: $cursor }
) {
both {
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
edges {
direction
node {
currency
issuers
outputs
comment
writtenTime
}
}
}
}
txsHistoryMp(pubkey: $pubkey) {
receiving {
currency
issuers
comment
outputs
}
sending {
currency
issuers
comment
outputs
}
}
currentUd {
amount
base
}
balance(script: $pubkey) {
amount
base
}
}
''';
const String getBalance = r'''
query ($pubkey: String!) {
balance(script: $pubkey) {
amount
base
}
currentUd {
amount
base
}
}
''';
const String getWallets = r'''
query ($number: Int!, $cursor: String) {
wallets(pagination: {ord: ASC, pageSize: $number, cursor: $cursor}) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
script
balance {
amount
base
}
idty {
isMember
username
}
}
}
}
}
''';
const String getId = r'''
query ($pubkey: PubKeyGva!) {
idty(pubkey: $pubkey) {
isMember
username
}
}
''';

View File

@ -0,0 +1,60 @@
const String getNameByAddressQ = r'''
query ($address: String!) {
account_by_pk(pubkey: $address) {
identity {
name
}
pubkey
}
}
''';
const String searchAddressByNameQ = r'''
query ($name: String!) {
search_identity(args: {name: $name}) {
pubkey
name
}
}
''';
const String getHistoryByAddressQ = r'''
query ($address: String!, $number: Int!, $cursor: String) {
transaction_connection(where:
{_or: [
{issuer_pubkey: {_eq: $address}},
{receiver_pubkey: {_eq: $address}}
]},
order_by: {created_at: desc},
first: $number,
after: $cursor) {
edges {
node {
amount
created_at
issuer_pubkey
receiver_pubkey
issuer {
identity {
name
}
}
receiver {
identity {
name
}
}
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
''';
// To parse indexer date format
// log.d(DateTime.parse("2022-06-13T16:51:24.001+00:00").toString());

View File

@ -6,10 +6,10 @@ class StatefulWrapper extends StatefulWidget {
const StatefulWrapper({Key? key, required this.onInit, required this.child})
: super(key: key);
@override
_StatefulWrapperState createState() => _StatefulWrapperState();
StatefulWrapperState createState() => StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
class StatefulWrapperState extends State<StatefulWrapper> {
@override
void initState() {
widget.onInit();

View File

@ -1,34 +1,41 @@
import 'dart:io';
import 'package:hive_flutter/hive_flutter.dart';
part 'wallet_data.g.dart';
@HiveType(typeId: 0)
class WalletData extends HiveObject {
@HiveField(0)
int? chest;
int? version;
@HiveField(1)
int? number;
int? chest;
@HiveField(2)
String? name;
String? address;
@HiveField(3)
int? derivation;
int? number;
@HiveField(4)
String? imageName;
String? name;
@HiveField(5)
File? imageFile;
int? derivation;
@HiveField(6)
String? imageDefaultPath;
@HiveField(7)
String? imageCustomPath;
WalletData(
{this.chest,
{this.version,
this.chest,
this.address,
this.number,
this.name,
this.derivation,
this.imageName,
this.imageFile});
this.imageDefaultPath,
this.imageCustomPath});
// representation of WalletData when debugging
@override
@ -38,7 +45,7 @@ class WalletData extends HiveObject {
// creates the ':'-separated string from the WalletData
String inLine() {
return "$chest:$number:$name:$derivation:$imageName";
return "$chest:$number:$name:$derivation:$imageDefaultPath";
}
// returns only the id part of the ':'-separated string
@ -46,3 +53,10 @@ class WalletData extends HiveObject {
return [chest, number];
}
}
class NewWallet {
final String address;
final String password;
NewWallet._(this.address, this.password);
}

View File

@ -17,31 +17,37 @@ class WalletDataAdapter extends TypeAdapter<WalletData> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return WalletData(
chest: fields[0] as int?,
number: fields[1] as int?,
name: fields[2] as String?,
derivation: fields[3] as int?,
imageName: fields[4] as String?,
imageFile: fields[5] as File?,
version: fields[0] as int?,
chest: fields[1] as int?,
address: fields[2] as String?,
number: fields[3] as int?,
name: fields[4] as String?,
derivation: fields[5] as int?,
imageDefaultPath: fields[6] as String?,
imageCustomPath: fields[7] as String?,
);
}
@override
void write(BinaryWriter writer, WalletData obj) {
writer
..writeByte(6)
..writeByte(8)
..writeByte(0)
..write(obj.chest)
..write(obj.version)
..writeByte(1)
..write(obj.number)
..write(obj.chest)
..writeByte(2)
..write(obj.name)
..write(obj.address)
..writeByte(3)
..write(obj.derivation)
..write(obj.number)
..writeByte(4)
..write(obj.imageName)
..write(obj.name)
..writeByte(5)
..write(obj.imageFile);
..write(obj.derivation)
..writeByte(6)
..write(obj.imageDefaultPath)
..writeByte(7)
..write(obj.imageCustomPath);
}
@override

View File

@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
// General
const keyInfoPopup = Key('keyInfoPopup');
const keyGoNext = Key('keyGoNext');
const keyCancel = Key('keyCancel');
const keyConfirm = Key('keyConfirm');
const keyAppBarSearch = Key('keyAppBarSearch');
const keyAppBarQrcode = Key('keyAppBarQrcode');
const keyAppBarChest = Key('keyAppBarChest');
// Home
const keyParameters = Key('keyParameters');
const keyContacts = Key('keyContacts');
const keyDrawerMenu = Key('keyDrawerMenu');
const keyOpenWalletsHomme = Key('keyOpenWalletsHomme');
const keyOpenSearch = Key('keyOpenSearch');
const keyRestoreChest = Key('keyRestoreChest');
const keyOnboardingNewChest = Key('keyOnboardingNewChest');
// Wallets home
const keyImportG1v1 = Key('keyImportG1v1');
const keyChangeChest = Key('keyChangeChest');
const keyListWallets = Key('keyListWallets');
const keyAddDerivation = Key('keyAddDerivation');
// Wallet options
const keyCopyAddress = Key('keyCopyAddress');
const keyOpenActivity = Key('keyOpenActivity');
const keyManageMembership = Key('keyManageMembership');
const keySetDefaultWallet = Key('keySetDefaultWallet');
const keyDeleteWallet = Key('keyDeleteWallet');
const keyWalletName = Key('keyWalletName');
const keyRenameWallet = Key('keyRenameWallet');
const keyConfirmIdentity = Key('keyConfirmIdentity');
const keyEnterIdentityUsername = Key('keyEnterIdentityUsername');
// Chest options
const keyShowSeed = Key('keyShowSeed');
const keyChangePin = Key('keyChangePin');
const keycreateRootDerivation = Key('keycreateRootDerivation');
const keyDeleteChest = Key('keyDeleteChest');
// Manage membership
const keyMigrateIdentity = Key('keyMigrateIdentity');
const keyRevokeIdty = Key('keyRevokeIdty');
// Choose chest
const keyCreateNewChest = Key('keyCreateNewChest');
const keyImportChest = Key('keyImportChest');
// Profile view
const keyViewActivity = Key('keyViewActivity');
const keyCertify = Key('keyCertify');
const keyPay = Key('keyPay');
const keyAmountField = Key('keyAmountField');
const keyConfirmPayment = Key('keyConfirmPayment');
const keyCloseTransactionScreen = Key('keyCloseTransactionScreen');
// Activity view
const keyListTransactions = Key('keyListTransactions');
const keyActivityScreen = Key('keyActivityScreen');
// Unlock wallet
const keyUnlockWallet = Key('keyUnlockWallet');
const keyPinForm = Key('keyPinForm');
const keyPopButton = Key('keyPopButton');
const keyCachePassword = Key('keyCachePassword');
// Settings
const keyDeleteAllWallets = Key('keyDeleteAllWallets');
const keySelectDuniterNodeDropDown = Key('keySelectDuniterNodeDropDown');
const keyCustomDuniterEndpoint = Key('keyCustomDuniterEndpoint');
const keyConnectToEndpoint = Key('keyConnectToEndpoint');
const keyUdUnit = Key('keyUdUnit');
// Onboarding
const keyPastMnemonic = Key('keyPastMnemonic');
const keyBubbleSpeak = Key('keyBubbleSpeak');
const keyGenerateMnemonic = Key('keyGenerateMnemonic');
const keyAskedWord = Key('keyAskedWord');
const keyInputWord = Key('keyInputWord');
const keyGeneratedPin = Key('keyGeneratedPin');
const keyGoWalletsHome = Key('keyGoWalletsHome');
// Search
const keySearchField = Key('keySearchField');
const keyConfirmSearch = Key('keyConfirmSearch');
// Import Cesium wallet
const keyCesiumId = Key('keyCesiumId');
const keyCesiumPassword = Key('keyCesiumPassword');
const keySelectWallet = Key('keySelectWallet');
const keyCesiumIdVisible = Key('keyCesiumIdVisible');
// Items keys
Key keyTransaction(int keyId) => Key('keyTransaction$keyId');
Key keyMnemonicWord(String word) => Key('keyMnemonicWord$word');
Key keySearchResult(String address) => Key('keySearchResult$address');
Key keySelectDuniterNode(String endpoint) =>
Key('keySelectDuniterNode$endpoint');
Key keyOpenWallet(String address) => Key('keyOpenWallet$address');
Key keySelectThisWallet(String address) => Key('keySelectThisWallet$address');

View File

@ -8,23 +8,23 @@ import 'package:path_provider/path_provider.dart';
class CesiumPlusProvider with ChangeNotifier {
TextEditingController cesiumName = TextEditingController();
Image defaultAvatar(double? size) =>
Image defaultAvatar(double size) =>
Image.asset(('assets/icon_user.png'), height: size);
CancelToken avatarCancelToken = CancelToken();
Future<List> _buildQuery(_pubkey) async {
Future<List> _buildQuery(pubkey) async {
var queryGetAvatar = json.encode({
"query": {
"bool": {
"should": [
{
"match": {
'_id': {"query": _pubkey, "boost": 2}
'_id': {"query": pubkey, "boost": 2}
}
},
{
"prefix": {'_id': _pubkey}
"prefix": {'_id': pubkey}
}
]
}
@ -60,14 +60,14 @@ class CesiumPlusProvider with ChangeNotifier {
return [podRequest, queryGetAvatar, headers];
}
Future<String> getName(String? _pubkey) async {
String? _name;
Future<String> getName(String? pubkey) async {
String? name;
if (g1WalletsBox.get(_pubkey)!.csName != null) {
return g1WalletsBox.get(_pubkey)!.csName!;
if (g1WalletsBox.get(pubkey)?.csName != null) {
return g1WalletsBox.get(pubkey)!.csName!;
}
List queryOptions = await _buildQuery(_pubkey);
List queryOptions = await _buildQuery(pubkey);
var dio = Dio();
late Response response;
@ -90,28 +90,28 @@ class CesiumPlusProvider with ChangeNotifier {
if (response.data['hits']['hits'].toString() == '[]') {
return '';
}
final bool _nameExist =
final bool nameExist =
response.data['hits']['hits'][0]['_source'].containsKey("title");
if (!_nameExist) {
if (!nameExist) {
return '';
}
_name = response.data['hits']['hits'][0]['_source']['title'];
name = response.data['hits']['hits'][0]['_source']['title'];
_name ??= '';
g1WalletsBox.get(_pubkey)!.csName = _name;
name ??= '';
g1WalletsBox.get(pubkey)!.csName = name;
return _name;
return name;
}
Future<Image?> getAvatar(String? _pubkey, double size) async {
if (g1WalletsBox.get(_pubkey)?.avatar != null) {
return g1WalletsBox.get(_pubkey)!.avatar;
Future<Image?> getAvatar(String? pubkey, double size) async {
if (g1WalletsBox.get(pubkey)?.avatar != null) {
return g1WalletsBox.get(pubkey)!.avatar;
}
var dio = Dio();
// log.d(_pubkey);
List queryOptions = await _buildQuery(_pubkey);
List queryOptions = await _buildQuery(pubkey);
late Response response;
try {
@ -138,12 +138,12 @@ class CesiumPlusProvider with ChangeNotifier {
return defaultAvatar(size);
}
final _avatar =
final avatar =
response.data['hits']['hits'][0]['_source']['avatar']['_content'];
var avatarFile =
File('${(await getTemporaryDirectory()).path}/avatar_$_pubkey.png');
await avatarFile.writeAsBytes(base64.decode(_avatar));
File('${(await getTemporaryDirectory()).path}/avatar_$pubkey.png');
await avatarFile.writeAsBytes(base64.decode(avatar));
final finalAvatar = Image.file(
avatarFile,
@ -151,7 +151,7 @@ class CesiumPlusProvider with ChangeNotifier {
fit: BoxFit.fitWidth,
);
g1WalletsBox.get(_pubkey)!.avatar = finalAvatar;
g1WalletsBox.get(pubkey)!.avatar = finalAvatar;
return finalAvatar;
}

View File

@ -1,48 +0,0 @@
import 'package:durt/durt.dart';
import 'package:flutter/material.dart';
import 'package:gecko/globals.dart';
class ChangePinProvider with ChangeNotifier {
bool ischangedPin = false;
TextEditingController newPin = TextEditingController();
String? pinToGive;
NewWallet? get badWallet => null;
Future<NewWallet?> changePin(String _oldPin, {String? newCustomPin}) async {
final NewWallet newWalletFile;
try {
final _chest = chestBox.get(configBox.get('currentChest'))!;
if (_chest.isCesium!) {
newWalletFile = await Dewif().changeCesiumPassword(
dewif: _chest.dewif!,
oldPassword: _oldPin.toUpperCase(),
newPassword: newCustomPin);
} else {
newWalletFile = await Dewif().changePassword(
dewif: _chest.dewif!,
oldPassword: _oldPin.toUpperCase(),
newPassword: newCustomPin);
}
newPin.text = pinToGive = newWalletFile.password;
ischangedPin = true;
// notifyListeners();
return newWalletFile;
} catch (e) {
log.e('Impossible de changer le code PIN: $e');
return badWallet;
}
}
void storeNewPinChest(context, NewWallet _newWalletFile) {
// ChestData currentChest = chestBox.getAt(configBox.get('currentChest'));
// currentChest.dewif = _newWalletFile.dewif;
// await chestBox.add(currentChest);
chestBox.get(configBox.get('currentChest'))!.dewif = _newWalletFile.dewif;
Navigator.pop(context, pinToGive);
pinToGive = '';
}
}

View File

@ -1,18 +1,30 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:provider/provider.dart';
class ChestProvider with ChangeNotifier {
void rebuildWidget() {
void reload() {
notifyListeners();
}
Future deleteChest(context, ChestData _chest) async {
final bool? _answer = await (_confirmDeletingChest(context, _chest.name));
Future deleteChest(context, ChestData chest) async {
final bool? answer = await (_confirmDeletingChest(context, chest.name));
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
if (answer ?? false) {
await sub.deleteAccounts(getChestWallets(chest));
await chestBox.delete(chest.key);
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
myWalletProvider.pinCode = '';
if (_answer!) {
await chestBox.delete(_chest.key);
if (chestBox.isEmpty) {
await configBox.put('currentChest', 0);
} else {
@ -28,23 +40,33 @@ class ChestProvider with ChangeNotifier {
}
}
Future<bool?> _confirmDeletingChest(context, String? _walletName) async {
List<String> getChestWallets(ChestData chest) {
List<String> toDelete = [];
log.d(chest.key);
walletBox.toMap().forEach((key, WalletData value) {
if (value.chest == chest.key) {
toDelete.add(value.address!);
}
});
return toDelete;
}
Future<bool?> _confirmDeletingChest(context, String? walletName) async {
return showDialog<bool>(
context: context,
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'Êtes-vous sûr de vouloir supprimer le coffre "$_walletName" ?'),
title: Text('areYouSureToDeleteWallet'.tr(args: [walletName!])),
actions: <Widget>[
TextButton(
child: const Text("Non", key: Key('cancelDeleting')),
child: Text("no".tr(), key: keyCancel),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
child: const Text("Oui", key: Key('confirmDeleting')),
child: Text("yes".tr(), key: keyConfirm),
onPressed: () {
Navigator.pop(context, true);
},

View File

@ -0,0 +1,449 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/cesium_plus.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/screens/wallet_view.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
import 'package:truncate/truncate.dart';
class DuniterIndexer with ChangeNotifier {
Map<String, String?> walletNameIndexer = {};
String? fetchMoreCursor;
Map? pageInfo;
int nPage = 1;
int nRepositories = 20;
List? transBC;
List listIndexerEndpoints = [];
bool isLoadingIndexer = false;
void reload() {
notifyListeners();
}
Future<bool> checkIndexerEndpoint(String endpoint) async {
isLoadingIndexer = true;
notifyListeners();
final client = HttpClient();
client.connectionTimeout = const Duration(milliseconds: 4000);
try {
final request = await client.postUrl(Uri.parse('$endpoint/v1/graphql'));
final response = await request.close();
if (response.statusCode != 200) {
log.d('INDEXER IS OFFILINE');
indexerEndpoint = '';
isLoadingIndexer = false;
notifyListeners();
return false;
} else {
indexerEndpoint = endpoint;
await configBox.put('indexerEndpoint', endpoint);
// await configBox.put('customEndpoint', endpoint);
isLoadingIndexer = false;
notifyListeners();
final cache = HiveStore();
cache.reset();
return true;
}
} catch (e) {
log.d('INDEXER IS OFFILINE');
indexerEndpoint = '';
isLoadingIndexer = false;
notifyListeners();
return false;
}
}
// Future checkIndexerEndpointBackground() async {
// final oldEndpoint = indexerEndpoint;
// while (true) {
// await Future.delayed(const Duration(seconds: 30));
// final isValid = await checkIndexerEndpoint(oldEndpoint);
// if (!isValid) {
// log.d('INDEXER IS OFFILINE');
// indexerEndpoint = '';
// } else {
// // log.d('Indexer is online');
// indexerEndpoint = oldEndpoint;
// }
// }
// }
Future<String> getValidIndexerEndpoint() async {
// await configBox.delete('indexerEndpoint');
listIndexerEndpoints = await rootBundle
.loadString('config/indexer_endpoints.json')
.then((jsonStr) => jsonDecode(jsonStr));
// _listEndpoints.shuffle();
log.d(listIndexerEndpoints);
listIndexerEndpoints.add('Personnalisé');
if (configBox.containsKey('customIndexer')) {
return configBox.get('customIndexer');
// listIndexerEndpoints.insert(0, configBox.get('customIndexer'));
}
if (configBox.containsKey('indexerEndpoint')) {
if (await checkIndexerEndpoint(configBox.get('indexerEndpoint'))) {
return configBox.get('indexerEndpoint');
}
}
int i = 0;
// String _endpoint = '';
int statusCode = 0;
final client = HttpClient();
client.connectionTimeout = const Duration(milliseconds: 3000);
do {
int listLenght = listIndexerEndpoints.length - 1;
if (i >= listLenght) {
log.e('NO VALID INDEXER ENDPOINT FOUND');
indexerEndpoint = '';
break;
}
log.d('${i + 1}n indexer endpoint try: ${listIndexerEndpoints[i]}');
if (i != 0) {
await Future.delayed(const Duration(milliseconds: 300));
}
try {
String endpointPath = '${listIndexerEndpoints[i]}/v1/graphql';
final request = await client.postUrl(Uri.parse(endpointPath));
final response = await request.close();
indexerEndpoint = listIndexerEndpoints[i];
await configBox.put('indexerEndpoint', listIndexerEndpoints[i]);
statusCode = response.statusCode;
i++;
} on TimeoutException catch (_) {
log.e('This endpoint is timeout, next');
statusCode = 50;
i++;
continue;
} on SocketException catch (_) {
log.e('This endpoint is a bad endpoint, next');
statusCode = 70;
i++;
continue;
} on Exception {
log.e('Unknown error');
statusCode = 60;
i++;
continue;
}
} while (statusCode != 200);
log.i('INDEXER: $indexerEndpoint');
return indexerEndpoint;
}
Widget getNameByAddress(BuildContext context, String address,
[WalletData? wallet,
double size = 20,
bool canEdit = false,
Color color = Colors.black,
FontWeight fontWeight = FontWeight.w400,
FontStyle fontStyle = FontStyle.italic]) {
WalletOptionsProvider walletOptions =
Provider.of<WalletOptionsProvider>(context, listen: false);
if (indexerEndpoint == '') {
if (wallet == null) {
return const SizedBox();
} else {
if (canEdit) {
return walletOptions.walletName(context, wallet, size, color);
} else {
return walletOptions.walletNameController(context, wallet, size);
}
}
}
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: httpLink,
),
);
return GraphQLProvider(
client: client,
child: Query(
options: QueryOptions(
document: gql(
getNameByAddressQ), // this is the query string you just created
variables: {
'address': address,
},
// pollInterval: const Duration(seconds: 10),
),
builder: (QueryResult result,
{VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return const Text('Loading');
}
walletNameIndexer[address] =
result.data?['account_by_pk']?['identity']?['name'];
g1WalletsBox.put(
address,
G1WalletsList(
address: address, username: walletNameIndexer[address]));
// log.d(g1WalletsBox.toMap().values.first.username);
if (walletNameIndexer[address] == null) {
if (wallet == null) {
return const SizedBox();
} else {
if (canEdit) {
return walletOptions.walletName(context, wallet, size, color);
} else {
return walletOptions.walletNameController(
context, wallet, size);
}
}
}
return Text(
color == Colors.grey[700]!
? '(${walletNameIndexer[address]!})'
: truncate(walletNameIndexer[address]!, 20),
style: TextStyle(
fontSize: size,
color: color,
fontWeight: fontWeight,
fontStyle: fontStyle,
),
);
}),
);
}
Widget searchIdentity(BuildContext context, String name) {
// WalletOptionsProvider _walletOptions =
// Provider.of<WalletOptionsProvider>(context, listen: false);
CesiumPlusProvider cesiumPlusProvider =
Provider.of<CesiumPlusProvider>(context, listen: false);
WalletsProfilesProvider walletsProfiles =
Provider.of<WalletsProfilesProvider>(context, listen: false);
if (indexerEndpoint == '') {
return const Text('Aucun résultat');
}
log.d(indexerEndpoint);
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(
store: HiveStore()), // GraphQLCache(store: HiveStore())
link: httpLink,
),
);
return GraphQLProvider(
client: client,
child: Query(
options: QueryOptions(
document: gql(
searchAddressByNameQ), // this is the query string you just created
variables: {
'name': name,
},
// pollInterval: const Duration(seconds: 10),
),
builder: (QueryResult result,
{VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return Text('loading'.tr());
}
final List identities = result.data?['search_identity'] ?? [];
if (identities.isEmpty) {
return Text('noResult'.tr());
}
double avatarSize = 55;
return Expanded(
child: ListView(children: <Widget>[
for (Map profile in identities)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: ListTile(
key: keySearchResult(profile['pubkey']),
horizontalTitleGap: 40,
contentPadding: const EdgeInsets.all(5),
leading: cesiumPlusProvider.defaultAvatar(avatarSize),
title: Row(children: <Widget>[
Text(getShortPubkey(profile['pubkey']),
style: const TextStyle(
fontSize: 18,
fontFamily: 'Monospace',
fontWeight: FontWeight.w500),
textAlign: TextAlign.center),
]),
trailing: SizedBox(
width: 110,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
balance(context, profile['pubkey'], 16),
]),
]),
),
subtitle: Row(children: <Widget>[
Text(profile['name'] ?? '',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
textAlign: TextAlign.center),
]),
dense: false,
isThreeLine: false,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
walletsProfiles.address = profile['pubkey'];
return WalletViewScreen(
address: profile['pubkey'],
username: g1WalletsBox
.get(profile['pubkey'])
?.id
?.username,
avatar:
g1WalletsBox.get(profile['pubkey'])?.avatar,
);
}),
);
}),
),
]),
);
}),
);
}
List parseHistory(blockchainTX, pubkey) {
var transBC = [];
int i = 0;
for (final trans in blockchainTX) {
final transaction = trans['node'];
final direction =
transaction['issuer_pubkey'] != pubkey ? 'RECEIVED' : 'SENT';
transBC.add(i);
transBC[i] = [];
transBC[i].add(DateTime.parse(transaction['created_at']));
final int amountBrut = transaction['amount'];
final double amount = removeDecimalZero(amountBrut / 100);
if (direction == "RECEIVED") {
transBC[i].add(transaction['issuer_pubkey']);
transBC[i].add(transaction['issuer']['identity']?['name'] ?? '');
} else if (direction == "SENT") {
transBC[i].add(transaction['receiver_pubkey']);
transBC[i].add(transaction['receiver']['identity']?['name'] ?? '');
}
transBC[i].add(amount);
transBC[i].add(direction);
// transBC[i].add(''); //transaction comment
i++;
}
return transBC;
}
FetchMoreOptions? checkQueryResult(result, opts, pubkey) {
final List<dynamic>? blockchainTX =
(result.data['transaction_connection']['edges'] as List<dynamic>?);
// final List<dynamic> mempoolTX =
// (result.data['txsHistoryMp']['receiving'] as List<dynamic>);
pageInfo = result.data['transaction_connection']['pageInfo'];
fetchMoreCursor = pageInfo!['endCursor'];
if (fetchMoreCursor == null) nPage = 1;
log.d(fetchMoreCursor);
if (nPage == 1) {
nRepositories = 40;
} else if (nPage == 2) {
nRepositories = 100;
}
// nRepositories = 10;
nPage++;
if (fetchMoreCursor != null) {
opts = FetchMoreOptions(
variables: {'cursor': fetchMoreCursor, 'number': nRepositories},
updateQuery: (previousResultData, fetchMoreResultData) {
final List<dynamic> repos = [
...previousResultData!['transaction_connection']['edges']
as List<dynamic>,
...fetchMoreResultData!['transaction_connection']['edges']
as List<dynamic>
];
log.d('repos: $previousResultData');
log.d('repos: $fetchMoreResultData');
log.d('repos: $repos');
fetchMoreResultData['transaction_connection']['edges'] = repos;
return fetchMoreResultData;
},
);
}
log.d(
"###### DEBUG H Parse blockchainTX list. Cursor: $fetchMoreCursor ######");
if (fetchMoreCursor != null) {
transBC = parseHistory(blockchainTX, pubkey);
} else {
log.i("###### DEBUG H - Début de l'historique");
}
return opts;
}
double removeDecimalZero(double n) {
String result = n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2);
return double.parse(result);
}
// checkHistoryResult(
// QueryResult<Object?> result, FetchMoreOptions options, String address) {}
}

View File

@ -1,25 +1,30 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:durt/durt.dart';
import 'package:durt/durt.dart' as durt;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/bip39_words.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:polkawallet_sdk/api/apiKeyring.dart';
import 'package:provider/provider.dart';
import "package:unorm_dart/unorm_dart.dart" as unorm;
class GenerateWalletsProvider with ChangeNotifier {
GenerateWalletsProvider();
// NewWallet generatedWallet;
NewWallet? actualWallet;
durt.NewWallet? actualWallet;
FocusNode walletNameFocus = FocusNode();
Color? askedWordColor = Colors.black;
bool isAskedWordValid = false;
int scanedValidWalletNumber = -1;
int scanedWalletNumber = -1;
int numberScan = 20;
late int nbrWord;
String? nbrWordAlpha;
@ -37,7 +42,7 @@ class GenerateWalletsProvider with ChangeNotifier {
bool isCesiumIDVisible = false;
bool isCesiumPWDVisible = false;
bool canImport = false;
late CesiumWallet cesiumWallet;
late durt.CesiumWallet cesiumWallet;
// Import Chest
TextEditingController cellController0 = TextEditingController();
@ -54,45 +59,31 @@ class GenerateWalletsProvider with ChangeNotifier {
TextEditingController cellController11 = TextEditingController();
bool isFirstTimeSentenceComplete = true;
Future storeHDWChest(
NewWallet _wallet, String _name, BuildContext context) async {
int chestNumber = 0;
chestBox.toMap().forEach((key, value) {
if (!value.isCesium!) {
chestNumber++;
}
});
Future storeHDWChest(BuildContext context) async {
int chestNumber = chestBox.isEmpty ? 0 : chestBox.keys.last + 1;
String chestName;
if (chestNumber == 0) {
chestName = 'Coffre à Ğecko';
chestName = 'geckoChest'.tr();
} else {
chestName = 'Coffre à Ğecko ${chestNumber + 1}';
chestName = '${'geckoChest'.tr()}${chestNumber + 1}';
}
await configBox.put('currentChest', chestNumber);
ChestData thisChest = ChestData(
dewif: _wallet.dewif,
name: chestName,
defaultWallet: 0,
imageName: '${chestNumber % 8}.png',
isCesium: false,
);
await chestBox.add(thisChest);
int? chestKey = chestBox.keys.last;
WalletData myWallet = WalletData(
chest: chestKey,
number: 0,
name: _name,
derivation: 3,
imageName: '0.png');
await walletBox.add(myWallet);
await configBox.put('currentChest', chestKey);
notifyListeners();
}
void checkAskedWord(String inputWord, String _mnemo) {
final expectedWord = _mnemo.split(' ')[nbrWord];
void checkAskedWord(String inputWord, String mnemo) {
final expectedWord = mnemo.split(' ')[nbrWord];
final normInputWord = unorm.nfkd(inputWord);
log.i("Is $expectedWord equal to input $normInputWord ?");
@ -128,22 +119,22 @@ class GenerateWalletsProvider with ChangeNotifier {
return rng.nextInt(12);
}
String? intToString(int _nbr) {
String? intToString(int nbr) {
Map nbrToString = {};
nbrToString[1] = 'Premier';
nbrToString[2] = 'Deuxième';
nbrToString[3] = 'Troisième';
nbrToString[4] = 'Quatrième';
nbrToString[5] = 'Cinquième';
nbrToString[6] = 'Sixième';
nbrToString[7] = 'Septième';
nbrToString[8] = 'Huitième';
nbrToString[9] = 'Neuvième';
nbrToString[10] = 'Dixième';
nbrToString[11] = 'Onzième';
nbrToString[12] = 'Douzième';
nbrToString[1] = '1th'.tr();
nbrToString[2] = '2th'.tr();
nbrToString[3] = '3th'.tr();
nbrToString[4] = '4th'.tr();
nbrToString[5] = '5th'.tr();
nbrToString[6] = '6th'.tr();
nbrToString[7] = '7th'.tr();
nbrToString[8] = '8th'.tr();
nbrToString[9] = '9th'.tr();
nbrToString[10] = '10th'.tr();
nbrToString[11] = '11th'.tr();
nbrToString[12] = '12th'.tr();
nbrWordAlpha = nbrToString[_nbr];
nbrWordAlpha = nbrToString[nbr];
return nbrWordAlpha;
}
@ -152,64 +143,78 @@ class GenerateWalletsProvider with ChangeNotifier {
notifyListeners();
}
Future<NewWallet?> generateWallet(String generatedMnemonic,
{required bool isImport}) async {
try {
actualWallet = await Dewif().generateDewif(
generatedMnemonic, randomSecretCode(pinLength),
lang: appLang);
} catch (e) {
log.e(e);
}
if (!isImport) {
mnemonicController.text = generatedMnemonic;
pin.text = actualWallet!.password;
}
// notifyListeners();
return actualWallet;
}
String changePinCode({required bool reload}) {
pin.text = randomSecretCode(pinLength);
pin.text = durt.randomSecretCode(pinLength);
if (reload) {
notifyListeners();
}
return pin.text;
}
Future<Uint8List> printWallet(String? _title) async {
Future<Uint8List> printWallet(AsyncSnapshot<List>? mnemoList) async {
final ByteData fontData =
await rootBundle.load("assets/OpenSans-Regular.ttf");
final pw.Font ttf = pw.Font.ttf(fontData.buffer.asByteData());
final pdf = pw.Document();
const imageProvider = AssetImage('assets/icon/gecko_final.png');
final geckoLogo = await flutterImageProvider(imageProvider);
// const imageProvider = AssetImage('assets/icon/gecko_final.png');
// final geckoLogo = await flutterImageProvider(imageProvider);
pw.Widget arrayCell(dataWord) {
return pw.SizedBox(
width: 120,
child: pw.Column(children: <pw.Widget>[
pw.Text(
dataWord.split(':')[0],
style: pw.TextStyle(
fontSize: 15, color: const PdfColor(0.5, 0, 0), font: ttf),
),
pw.Text(
dataWord.split(':')[1],
style: pw.TextStyle(
fontSize: 20, color: const PdfColor(0, 0, 0), font: ttf),
),
pw.SizedBox(height: 10)
]),
);
}
pdf.addPage(
pw.Page(
pageFormat: PdfPageFormat.a4,
build: (context) {
return pw.Column(children: <pw.Widget>[
pw.SizedBox(height: 20),
pw.Text("Phrase de restauration:",
style: pw.TextStyle(fontSize: 20, font: ttf)),
pw.SizedBox(height: 10),
pw.Text(_title!,
style: pw.TextStyle(fontSize: 15, font: ttf),
textAlign: pw.TextAlign.center),
pw.Expanded(
child: pw.Align(
alignment: pw.Alignment.bottomCenter,
child: pw.Text(
"Gardez cette feuille en lieu sûr, à l'abris des regards indiscrets.",
style: pw.TextStyle(fontSize: 10, font: ttf),
))),
pw.SizedBox(height: 15),
pw.Image(geckoLogo, height: 50)
]);
return pw.Column(
// mainAxisAlignment: pw.MainAxisAlignment.center,
// mainAxisSize: pw.MainAxisSize.max,
// crossAxisAlignment: pw.CrossAxisAlignment.center,
children: <pw.Widget>[
pw.Row(children: <pw.Widget>[
arrayCell(mnemoList!.data![0]),
arrayCell(mnemoList.data![1]),
arrayCell(mnemoList.data![2]),
arrayCell(mnemoList.data![3]),
]),
pw.Row(children: <pw.Widget>[
arrayCell(mnemoList.data![4]),
arrayCell(mnemoList.data![5]),
arrayCell(mnemoList.data![6]),
arrayCell(mnemoList.data![7]),
]),
pw.Row(children: <pw.Widget>[
arrayCell(mnemoList.data![8]),
arrayCell(mnemoList.data![9]),
arrayCell(mnemoList.data![10]),
arrayCell(mnemoList.data![11])
]),
pw.Expanded(
child: pw.Align(
alignment: pw.Alignment.bottomCenter,
child: pw.Text(
"Gardez cette feuille préciseusement, à labri des lézards indiscrets.",
style: pw.TextStyle(fontSize: 15, font: ttf),
)))
],
);
},
),
);
@ -218,62 +223,12 @@ class GenerateWalletsProvider with ChangeNotifier {
}
Future<void> generateCesiumWalletPubkey(
String _cesiumID, String _cesiumPWD) async {
cesiumWallet = CesiumWallet(_cesiumID, _cesiumPWD);
String _walletPubkey = cesiumWallet.pubkey;
String cesiumID, String cesiumPWD) async {
cesiumWallet = durt.CesiumWallet(cesiumID, cesiumPWD);
String walletPubkey = cesiumWallet.pubkey;
cesiumPubkey.text = _walletPubkey;
log.d(_walletPubkey);
}
Future<int?> importCesiumWallet() async {
// String _walletPubkey = await DubpRust.getLegacyPublicKey(
// salt: _cesiumID, password: _cesiumPWD);
// String shortPubkey = truncate(_walletPubkey, 9,
// omission: "...", position: TruncatePosition.end);
// await storeWallet(
// actualWallet, 'Portefeuille Cesium - $shortPubkey', context);
// NewWallet myCesiumWallet = await DubpRust.genWalletFromDeprecatedSaltPassword(salt: _cesiumID, password: _cesiumPWD);
cesiumID.text = '';
cesiumPWD.text = '';
cesiumPubkey.text = '';
canImport = false;
isCesiumIDVisible = false;
isCesiumPWDVisible = false;
int chestNumber = 0;
chestBox.toMap().forEach((key, value) {
if (value.isCesium!) {
chestNumber++;
}
});
String chestName;
if (chestNumber == 0) {
chestName = 'Coffre à Césium';
} else {
chestName = 'Coffre à Césium ${chestNumber + 1}';
}
log.d(pin.text);
NewWallet cesiumDewif =
await Dewif().generateCesiumDewif(cesiumWallet.seed, pin.text);
ChestData cesiumChest = ChestData(
dewif: cesiumDewif.dewif,
name: chestName,
imageName: 'cesium.png',
defaultWallet: 0,
isCesium: true);
await chestBox.add(cesiumChest).then((value) => null);
int? chestKey = await chestBox.toMap().keys.last;
// chestBox.toMap().
await configBox.put('currentChest', chestKey);
pin.text = '';
return chestKey;
cesiumPubkey.text = walletPubkey;
log.d(walletPubkey);
}
void cesiumIDisVisible() {
@ -293,27 +248,41 @@ class GenerateWalletsProvider with ChangeNotifier {
notifyListeners();
}
List<String> generateWordList() {
generatedMnemonic = generateMnemonic(lang: appLang);
List<String> _wordsList = [];
Future<List<String>> generateWordList(BuildContext context) async {
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
generatedMnemonic = await sub.generateMnemonic(lang: appLang);
List<String> wordsList = [];
String word;
int _nbr = 1;
int nbr = 1;
for (word in generatedMnemonic!.split(' ')) {
_wordsList.add("$_nbr:$word");
_nbr++;
wordsList.add("$nbr:$word");
nbr++;
}
return _wordsList;
return wordsList;
}
bool isBipWord(String word) {
bool isBipWord(String word, [bool checkRedondance = true]) {
bool isValid = false;
notifyListeners();
// Needed for bad encoding of UTF-8
word = word.replaceAll('é', '');
word = word.replaceAll('è', '');
return bip39Words.contains(word);
int nbrMatch = 0;
if (bip39Words(appLang).contains(word.toLowerCase())) {
for (var bipWord in bip39Words(appLang)) {
if (bipWord.startsWith(word)) {
isValid = nbrMatch == 0 ? true : false;
if (checkRedondance) nbrMatch = nbrMatch + 1;
}
}
}
return isValid;
}
bool isBipWordsList(List<String> words) {
@ -322,7 +291,7 @@ class GenerateWalletsProvider with ChangeNotifier {
// Needed for bad encoding of UTF-8
word = word.replaceAll('é', '');
word = word.replaceAll('è', '');
if (!bip39Words.contains(word)) {
if (!bip39Words(appLang).contains(word.toLowerCase())) {
isValid = false;
}
}
@ -333,8 +302,8 @@ class GenerateWalletsProvider with ChangeNotifier {
cellController0.text = cellController1.text = cellController2.text =
cellController3.text = cellController4.text = cellController5.text =
cellController6.text = cellController7.text = cellController8.text =
cellController9.text = cellController10.text =
cellController11.text = '';
cellController9.text =
cellController10.text = cellController11.text = '';
isFirstTimeSentenceComplete = true;
notifyListeners();
}
@ -366,26 +335,134 @@ class GenerateWalletsProvider with ChangeNotifier {
}
}
Future<bool> isSentenceValid() async {
String inputMnemonic =
'${cellController0.text} ${cellController1.text} ${cellController2.text} ${cellController3.text} ${cellController4.text} ${cellController5.text} ${cellController6.text} ${cellController7.text} ${cellController8.text} ${cellController9.text} ${cellController10.text} ${cellController11.text}';
Future pasteMnemonic(BuildContext context) async {
final sentence = await Clipboard.getData('text/plain');
int nbr = 0;
// Needed for bad encoding of UTF-8
inputMnemonic = inputMnemonic.replaceAll('é', '');
inputMnemonic = inputMnemonic.replaceAll('è', '');
List cells = [
cellController0,
cellController1,
cellController2,
cellController3,
cellController4,
cellController5,
cellController6,
cellController7,
cellController8,
cellController9,
cellController10,
cellController11
];
if (sentence?.text == null) return;
for (var word in sentence!.text!.split(' ')) {
bool isValid = isBipWord(word, false);
NewWallet? generatedWallet =
await generateWallet(inputMnemonic, isImport: true);
if (generatedWallet == null) {
return false;
} else {
generatedMnemonic = inputMnemonic;
return true;
if (isValid) {
cells[nbr].text = word;
}
nbr++;
}
}
void reloadBuild() {
notifyListeners();
}
Future<bool> scanDerivations(BuildContext context) async {
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
final currentChestNumber = configBox.get('currentChest');
bool isAlive = false;
scanedValidWalletNumber = 0;
scanedWalletNumber = 0;
notifyListeners();
if (!sub.nodeConnected) {
return false;
}
final hasRoot = await scanRootBalance(sub, currentChestNumber);
scanedWalletNumber = 1;
notifyListeners();
if (hasRoot) {
scanedValidWalletNumber = 1;
isAlive = true;
}
for (var derivationNbr in [for (var i = 0; i < numberScan; i += 1) i]) {
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: generatedMnemonic!,
derivePath: '//$derivationNbr');
final Map balance = await sub.getBalance(addressData.address!).timeout(
const Duration(seconds: 1),
onTimeout: () => {'transferableBalance': 0},
);
// const balance = 0;
log.d(
"${addressData.address!}: ${balance['transferableBalance']} $currencyName");
if (balance['transferableBalance'] != 0) {
isAlive = true;
String walletName = scanedValidWalletNumber == 0
? 'currentWallet'.tr()
: '${'wallet'.tr()} ${scanedValidWalletNumber + 1}';
await sub.importAccount(
mnemonic: generatedMnemonic!,
derivePath: '//$derivationNbr',
password: pin.text);
WalletData myWallet = WalletData(
version: dataVersion,
chest: currentChestNumber,
address: addressData.address!,
number: scanedValidWalletNumber,
name: walletName,
derivation: derivationNbr,
imageDefaultPath: '${scanedValidWalletNumber % 4}.png');
await walletBox.add(myWallet);
scanedValidWalletNumber = scanedValidWalletNumber + 1;
}
scanedWalletNumber = scanedWalletNumber + 1;
notifyListeners();
}
log.d(scanedWalletNumber);
scanedWalletNumber = -1;
scanedValidWalletNumber = -1;
notifyListeners();
return isAlive;
}
Future<bool> scanRootBalance(SubstrateSdk sub, int currentChestNumber) async {
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: generatedMnemonic!);
final balance = await sub.getBalance(addressData.address!).timeout(
const Duration(seconds: 1),
onTimeout: () => {},
);
log.d(
"${addressData.address!}: ${balance['transferableBalance']} $currencyName");
if (balance['transferableBalance'] != 0) {
String walletName = 'myRootWallet'.tr();
await sub.importAccount(mnemonic: generatedMnemonic!, password: pin.text);
WalletData myWallet = WalletData(
version: dataVersion,
chest: currentChestNumber,
address: addressData.address!,
number: 0,
name: walletName,
derivation: -1,
imageDefaultPath: '0.png');
await walletBox.add(myWallet);
return true;
} else {
return false;
}
}
}

View File

@ -1,28 +1,38 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:convert';
import 'dart:io';
import 'dart:math';
// import 'package:audioplayers/audio_cache.dart';
// import 'package:audioplayers/audioplayers.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:gecko/globals.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/screens/myWallets/unlocking_wallet.dart';
import 'package:gecko/screens/myWallets/wallets_home.dart';
import 'package:gecko/screens/search.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb;
import 'package:path_provider/path_provider.dart' as pp;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
class HomeProvider with ChangeNotifier {
bool? isSearching;
Icon searchIcon = const Icon(Icons.search);
final TextEditingController searchQuery = TextEditingController();
Widget appBarTitle = Text('Ğecko', style: TextStyle(color: Colors.grey[850]));
Widget appBarExplorer =
Text('Explorateur', style: TextStyle(color: Colors.grey[850]));
bool isFirstBuild = true;
// AudioCache player = AudioCache(prefix: 'sounds/');
String homeMessage = "loading".tr();
String defaultMessage = "noLizard".tr();
Future<void> initHive() async {
late Directory hivePath;
@ -45,6 +55,23 @@ class HomeProvider with ChangeNotifier {
} else {
await Hive.initFlutter();
}
// Init app folders
final documentDir = await getApplicationDocumentsDirectory();
imageDirectory = Directory('${documentDir.path}/images');
if (!await imageDirectory.exists()) {
await imageDirectory.create();
}
}
Future changeCurrencyUnit(BuildContext context) async {
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
await configBox.put('isUdUnit', !isUdUnit);
balanceCache = {};
sub.getBalanceRatio();
notifyListeners();
}
Future<String> getAppVersion() async {
@ -52,62 +79,41 @@ class HomeProvider with ChangeNotifier {
String buildNumber;
PackageInfo packageInfo = await PackageInfo.fromPlatform();
version = packageInfo.version;
buildNumber = packageInfo.buildNumber;
buildNumber = kDebugMode
? packageInfo.buildNumber
: (int.parse(packageInfo.buildNumber) - 1000).toString();
notifyListeners();
return version + '+' + buildNumber;
return '$version+$buildNumber';
}
Future<String?> getValidEndpoint() async {
List _listEndpoints = await rootBundle
.loadString('config/gva_endpoints.json')
.then((jsonStr) => jsonDecode(jsonStr));
_listEndpoints.shuffle();
Future changeMessage(String newMessage, int seconds) async {
homeMessage = newMessage;
notifyListeners();
await Future.delayed(Duration(seconds: seconds));
if (seconds != 0) homeMessage = defaultMessage;
notifyListeners();
}
int i = 0;
String? _endpoint;
int _statusCode = 0;
Future<List?> getValidEndpoints() async {
await configBox.delete('endpoint');
if (!configBox.containsKey('autoEndpoint')) {
configBox.put('autoEndpoint', true);
}
final _client = HttpClient();
_client.connectionTimeout = const Duration(milliseconds: 1000);
List listEndpoints = [];
if (!configBox.containsKey('endpoint') ||
configBox.get('endpoint') == [] ||
configBox.get('endpoint') == '') {
listEndpoints = await rootBundle
.loadString('config/gdev_endpoints.json')
.then((jsonStr) => jsonDecode(jsonStr));
listEndpoints.shuffle();
configBox.put('endpoint', listEndpoints);
}
do {
i++;
log.d(i.toString() + ' ème essai de recherche de endpoint GVA.');
log.d('Try GVA endpoint: ${_listEndpoints[i - 1]}');
int listLenght = _listEndpoints.length - 1;
if (i > listLenght) {
log.e('NO VALID GVA ENDPOINT FOUND');
_endpoint = 'HS';
break;
}
if (i != 0) {
await Future.delayed(const Duration(milliseconds: 300));
}
try {
final request = await _client.postUrl(Uri.parse(_listEndpoints[i]));
final response = await request.close();
_endpoint = _listEndpoints[i];
_statusCode = response.statusCode;
} on TimeoutException catch (_) {
log.e('This endpoint is timeout, next');
_statusCode = 50;
continue;
} on SocketException catch (_) {
log.e('This endpoint is a bad endpoint, next');
_statusCode = 70;
continue;
} on Exception {
log.e('Unknown error');
_statusCode = 60;
continue;
}
} while (_statusCode != 400);
log.i('ENDPOINT: ' + _endpoint!);
return _endpoint;
log.i('ENDPOINT: $listEndpoints');
return listEndpoints;
}
T getRandomElement<T>(List<T> list) {
@ -126,37 +132,105 @@ class HomeProvider with ChangeNotifier {
// volume: volume, mode: PlayerMode.LOW_LATENCY, stayAwake: false);
// }
void handleSearchEnd() {
searchIcon = Icon(
Icons.search,
color: Colors.grey[850],
Widget bottomAppBar(BuildContext context) {
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
WalletsProfilesProvider historyProvider =
Provider.of<WalletsProfilesProvider>(context, listen: false);
final size = MediaQuery.of(context).size;
const bool showBottomBar = true;
return Visibility(
visible: showBottomBar,
child: Container(
color: yellowC,
width: size.width,
height: 80,
child:
// Stack(
// children: [
// // CustomPaint(
// // size: Size(size.width, 110),
// // painter: CustomRoundedButton(),
// // ),
Row(mainAxisAlignment: MainAxisAlignment.start, children: [
// SizedBox(width: 0),
const Spacer(),
const SizedBox(width: 11),
IconButton(
key: keyAppBarSearch,
iconSize: 40,
icon: const Image(image: AssetImage('assets/loupe-noire.png')),
onPressed: () {
Navigator.popUntil(
context,
ModalRoute.withName('/'),
);
Navigator.push(
context,
MaterialPageRoute(builder: (homeContext) {
return const SearchScreen();
}),
);
},
),
const SizedBox(width: 22),
const Spacer(),
IconButton(
key: keyAppBarQrcode,
iconSize: 70,
icon: const Image(image: AssetImage('assets/qrcode-scan.png')),
onPressed: () async {
Navigator.popUntil(
context,
ModalRoute.withName('/'),
);
historyProvider.scan(homeContext);
},
),
const Spacer(),
const SizedBox(width: 15),
IconButton(
key: keyAppBarChest,
iconSize: 60,
icon: const Image(image: AssetImage('assets/wallet.png')),
onPressed: () async {
WalletData? defaultWallet = myWalletProvider.getDefaultWallet();
String? pin;
if (myWalletProvider.pinCode == '') {
pin = await Navigator.push(
context,
MaterialPageRoute(
builder: (homeContext) {
return UnlockingWallet(wallet: defaultWallet);
},
),
);
}
if (pin != null || myWalletProvider.pinCode != '') {
Navigator.popUntil(
context,
ModalRoute.withName('/'),
);
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return const WalletsHome();
}),
);
}
},
),
const Spacer(),
]),
),
);
appBarTitle = Text('Ğecko', style: TextStyle(color: Colors.grey[850]));
appBarExplorer =
Text('Explorateur', style: TextStyle(color: Colors.grey[850]));
isSearching = false;
searchQuery.clear();
notifyListeners();
}
void snackNode(context) {
if (isFirstBuild) {
String _message;
if (endPointGVA == 'HS') {
_message =
"Aucun noeud Duniter disponible, veuillez réessayer ultérieurement";
} else {
_message = "Vous êtes connecté au noeud\n${SubstrateSdk().subNode}";
}
final snackBar = SnackBar(
content: Text(_message), duration: const Duration(seconds: 2));
isFirstBuild = false;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
void rebuildWidget() {
void reload() {
notifyListeners();
}
}

View File

@ -1,19 +1,22 @@
import 'dart:typed_data';
import 'package:durt/durt.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:gecko/globals.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/screens/common_elements.dart';
import 'package:provider/provider.dart';
class MyWalletsProvider with ChangeNotifier {
List<WalletData> listWallets = [];
late String pinCode;
String pinCode = '';
late String mnemonic;
late Uint8List cesiumSeed;
int? pinLenght;
bool isNewDerivationLoading = false;
String lastFlyBy = '';
String dragAddress = '';
int? getCurrentChest() {
int getCurrentChest() {
if (configBox.get('currentChest') == null) {
configBox.put('currentChest', 0);
}
@ -21,24 +24,6 @@ class MyWalletsProvider with ChangeNotifier {
return configBox.get('currentChest');
}
String dewifToMnemonic(context, WalletData _wallet, String _pin) {
String _mnemonic;
try {
String _localDewif = chestBox.get(_wallet.chest)!.dewif!;
_mnemonic = Dewif()
.mnemonicFromDewif(_localDewif, _pin.toUpperCase(), lang: appLang);
} on ChecksumException catch (e) {
log.e(e.cause);
return 'bad';
} catch (e) {
// _homeProvider.playSound('non', 0.6);
log.e('ERROR READING FILE: $e');
return 'bad';
}
return _mnemonic;
}
bool checkIfWalletExist() {
if (chestBox.isEmpty) {
log.i('No wallets detected');
@ -48,10 +33,11 @@ class MyWalletsProvider with ChangeNotifier {
}
}
List<WalletData> readAllWallets(int? _chest) {
List<WalletData> readAllWallets([int? chest]) {
chest = chest ?? configBox.get('currentChest') ?? 0;
listWallets.clear();
walletBox.toMap().forEach((key, value) {
if (value.chest == _chest) {
if (value.chest == chest) {
listWallets.add(value);
}
});
@ -59,42 +45,62 @@ class MyWalletsProvider with ChangeNotifier {
return listWallets;
}
WalletData? getWalletData(List<int?> _id) {
if (_id.isEmpty) return WalletData();
int? _chest = _id[0];
int? _nbr = _id[1];
WalletData? _targetedWallet;
WalletData? getWalletDataById(List<int?> id) {
if (id.isEmpty) return WalletData();
int? chest = id[0];
int? nbr = id[1];
WalletData? targetedWallet;
walletBox.toMap().forEach((key, value) {
if (value.chest == _chest && value.number == _nbr) {
_targetedWallet = value;
if (value.chest == chest && value.number == nbr) {
targetedWallet = value;
return;
}
});
return _targetedWallet;
return targetedWallet;
}
WalletData? getDefaultWallet(int? chest) {
WalletData? getWalletDataByAddress(String address) {
WalletData? targetedWallet;
walletBox.toMap().forEach((key, value) {
if (value.address == address) {
targetedWallet = value;
return;
}
});
return targetedWallet;
}
WalletData getDefaultWallet([int? chest]) {
if (chestBox.isEmpty) {
return WalletData(chest: 0, number: 0);
} else {
chest ??= getCurrentChest();
int? defaultWalletNumber = chestBox.get(chest)!.defaultWallet;
return getWalletData([chest, defaultWalletNumber]);
return getWalletDataById([chest, defaultWalletNumber]) ??
WalletData(chest: chest, number: 0);
}
}
Future<int> deleteAllWallet(context) async {
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
try {
log.w('DELETE ALL WALLETS ?');
final bool? _answer = await (_confirmDeletingAllWallets(context));
if (_answer!) {
final bool? answer =
await (confirmPopup(context, 'areYouSureForgetAllChests'.tr()));
if (answer!) {
await walletBox.clear();
await chestBox.clear();
await configBox.delete('defaultWallet');
// await Future.delayed(const Duration(milliseconds: 50));
// notifyListeners();
await sub.deleteAllAccounts();
myWalletProvider.pinCode = '';
await Navigator.of(context).pushNamedAndRemoveUntil(
'/',
@ -107,62 +113,115 @@ class MyWalletsProvider with ChangeNotifier {
}
}
Future<bool?> _confirmDeletingAllWallets(context) async {
return showDialog<bool>(
context: context,
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
'Êtes-vous sûr de vouloir supprimer tous vos trousseaux ?'),
content: const SingleChildScrollView(child: Text('')),
actions: <Widget>[
TextButton(
child: const Text("Non"),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
key: const Key('confirmDeletingAllWallets'),
child: const Text("Oui"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
},
);
}
Future<void> generateNewDerivation(context, String name,
[int? number]) async {
isNewDerivationLoading = true;
notifyListeners();
Future<void> generateNewDerivation(context, String _name) async {
int _newDerivationNbr;
int _newWalletNbr;
int? _chest = getCurrentChest();
List<WalletData> _walletConfig = readAllWallets(_chest);
final List idList = getNextWalletNumberAndDerivation();
int newWalletNbr = idList[0];
int newDerivationNbr = number ?? idList[1];
if (_walletConfig.isEmpty) {
_newDerivationNbr = 3;
_newWalletNbr = 0;
} else {
_newDerivationNbr = _walletConfig.last.derivation! + 3;
_newWalletNbr = _walletConfig.last.number! + 1;
}
int? chest = getCurrentChest();
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
WalletData defaultWallet = getDefaultWallet();
final address = await sub.derive(
context, defaultWallet.address!, newDerivationNbr, pinCode);
WalletData newWallet = WalletData(
chest: _chest,
number: _newWalletNbr,
name: _name,
derivation: _newDerivationNbr,
imageName: '${_newWalletNbr % 4}.png');
version: dataVersion,
chest: chest,
address: address,
number: newWalletNbr,
name: name,
derivation: newDerivationNbr,
imageDefaultPath: '${newWalletNbr % 4}.png');
await walletBox.add(newWallet);
isNewDerivationLoading = false;
notifyListeners();
}
void rebuildWidget() {
Future<void> generateRootWallet(context, String name) async {
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
isNewDerivationLoading = true;
notifyListeners();
int newWalletNbr;
int? chest = getCurrentChest();
List<WalletData> walletConfig = readAllWallets(chest);
if (walletConfig.isEmpty) {
newWalletNbr = 0;
} else {
newWalletNbr = walletConfig.last.number! + 1;
}
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
WalletData defaultWallet = myWalletProvider.getDefaultWallet();
final address =
await sub.generateRootKeypair(defaultWallet.address!, pinCode);
WalletData newWallet = WalletData(
version: dataVersion,
chest: chest,
address: address,
number: newWalletNbr,
name: name,
derivation: -1,
imageDefaultPath: '${newWalletNbr % 4}.png');
await walletBox.add(newWallet);
isNewDerivationLoading = false;
notifyListeners();
}
List<int> getNextWalletNumberAndDerivation(
{int? chestNumber, bool isOneshoot = false}) {
int newDerivationNbr = 0;
int newWalletNbr = 0;
chestNumber ??= getCurrentChest();
List<WalletData> walletConfig = readAllWallets(chestNumber);
if (walletConfig.isEmpty) {
newDerivationNbr = 2;
} else {
WalletData lastWallet = walletConfig.reduce(
(curr, next) => curr.derivation! > next.derivation! ? curr : next);
if (lastWallet.derivation == -1) {
newDerivationNbr = 2;
} else {
newDerivationNbr = lastWallet.derivation! + (isOneshoot ? 1 : 2);
}
newWalletNbr = walletConfig.last.number! + 1;
}
return [newWalletNbr, newDerivationNbr];
}
int lockPin = 0;
Future resetPinCode([int minutes = 15]) async {
lockPin++;
final actualLock = lockPin;
await Future.delayed(
Duration(seconds: configBox.get('isCacheChecked') ? minutes * 60 : 1));
log.i('reset pin code, lock $actualLock ...');
if (actualLock == lockPin) pinCode = '';
}
void reload() {
notifyListeners();
}
}

View File

@ -1,6 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/providers/wallets_profiles.dart';
@ -10,70 +8,82 @@ class SearchProvider with ChangeNotifier {
final cacheDuring = 20 * 60 * 1000; //First number is minutes
int cacheTime = 0;
void rebuildWidget() {
void reload() {
notifyListeners();
}
Future<List> searchBlockchain() async {
searchResult.clear();
int searchTime = DateTime.now().millisecondsSinceEpoch;
WalletsProfilesProvider _walletProfiles = WalletsProfilesProvider('pubkey');
// Future<List> searchBlockchain() async {
// searchResult.clear();
// int searchTime = DateTime.now().millisecondsSinceEpoch;
// WalletsProfilesProvider _walletProfiles = WalletsProfilesProvider('pubkey');
if (cacheTime + cacheDuring <= searchTime) {
g1WalletsBox.clear();
// final url = Uri.parse('https://g1-stats.axiom-team.fr/data/forbes.json');
// final response = await http.get(url);
// if (cacheTime + cacheDuring <= searchTime) {
// g1WalletsBox.clear();
// // final url = Uri.parse('https://g1-stats.axiom-team.fr/data/forbes.json');
// // final response = await http.get(url);
var dio = Dio();
late Response response;
try {
response = await dio.get(
'https://g1-stats.axiom-team.fr/data/forbes.json',
options: Options(
sendTimeout: 5000,
receiveTimeout: 10000,
),
);
// response = await http.post((Uri.parse(queryOptions[0])),
// body: queryOptions[1], headers: queryOptions[2]);
} catch (e) {
log.e(e);
}
// var dio = Dio();
// late Response response;
// try {
// response = await dio.get(
// 'https://g1-stats.axiom-team.fr/data/forbes.json',
// options: Options(
// sendTimeout: 5000,
// receiveTimeout: 10000,
// ),
// );
// // response = await http.post((Uri.parse(queryOptions[0])),
// // body: queryOptions[1], headers: queryOptions[2]);
// } catch (e) {
// log.e(e);
// }
List<G1WalletsList> _listWallets = _parseG1Wallets(response.data)!;
Map<String?, G1WalletsList> _mapWallets = {
for (var e in _listWallets) e.pubkey: e
};
// List<G1WalletsList> _listWallets = _parseG1Wallets(response.data)!;
// Map<String?, G1WalletsList> _mapWallets = {
// for (var e in _listWallets) e.pubkey: e
// };
await g1WalletsBox.putAll(_mapWallets);
cacheTime = DateTime.now().millisecondsSinceEpoch;
// await g1WalletsBox.putAll(_mapWallets);
// cacheTime = DateTime.now().millisecondsSinceEpoch;
// }
// g1WalletsBox.toMap().forEach((key, value) {
// if ((value.id != null &&
// value.id!.username != null &&
// value.id!.username!
// .toLowerCase()
// .contains(searchController.text)) ||
// value.pubkey!.contains(searchController.text)) {
// searchResult.add(value);
// return;
// }
// });
// if (searchResult.isEmpty &&
// _walletProfiles.isPubkey(searchController.text)) {
// searchResult = [G1WalletsList(pubkey: searchController.text)];
// }
// return searchResult;
// }
Future<List<G1WalletsList>> searchAddress() async {
final WalletsProfilesProvider walletProfiles =
WalletsProfilesProvider('pubkey');
if (walletProfiles.isAddress(searchController.text)) {
G1WalletsList wallet = G1WalletsList(address: searchController.text);
return [wallet];
} else {
return [];
}
g1WalletsBox.toMap().forEach((key, value) {
if ((value.id != null &&
value.id!.username != null &&
value.id!.username!
.toLowerCase()
.contains(searchController.text)) ||
value.pubkey!.contains(searchController.text)) {
searchResult.add(value);
return;
}
});
if (searchResult.isEmpty &&
_walletProfiles.isPubkey(searchController.text)) {
searchResult = [G1WalletsList(pubkey: searchController.text)];
}
return searchResult;
}
}
List<G1WalletsList>? _parseG1Wallets(var responseBody) {
final parsed = responseBody.cast<Map<String, dynamic>>();
// List<G1WalletsList>? _parseG1Wallets(var responseBody) {
// final parsed = responseBody.cast<Map<String, dynamic>>();
return parsed
.map<G1WalletsList>((json) => G1WalletsList.fromJson(json))
.toList();
}
// return parsed
// .map<G1WalletsList>((json) => G1WalletsList.fromJson(json))
// .toList();
// }

View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
class SettingsProvider with ChangeNotifier {
void reload() {
notifyListeners();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,255 +1,639 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:durt/durt.dart';
import 'package:fast_base58/fast_base58.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'package:gecko/globals.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/screens/animated_text.dart';
import 'package:gecko/screens/common_elements.dart';
import 'package:gecko/screens/myWallets/unlocking_wallet.dart';
import 'package:gecko/screens/transaction_in_progress.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:truncate/truncate.dart';
class WalletOptionsProvider with ChangeNotifier {
TextEditingController pubkey = TextEditingController();
TextEditingController address = TextEditingController();
final TextEditingController _newWalletName = TextEditingController();
bool isWalletUnlock = false;
bool ischangedPin = false;
TextEditingController newPin = TextEditingController();
bool isEditing = false;
bool isBalanceBlur = true;
bool isBalanceBlur = false;
FocusNode walletNameFocus = FocusNode();
TextEditingController nameController = TextEditingController();
late bool isDefaultWallet;
bool canValidateNameBool = false;
Map<String, String> idtyStatusCache = {};
Future<NewWallet>? get badWallet => null;
String _getPubkeyFromDewif(
String? _dewif, _pin, int _pinLenght, int? derivation) {
RegExp regExp = RegExp(
r'^[A-Z0-9]+$',
caseSensitive: false,
multiLine: false,
);
if (regExp.hasMatch(_pin) == true && _pin.length == _pinLenght) {
} else {
return 'false';
}
if (derivation != -1) {
try {
final _wallet = HdWallet.fromDewif(_dewif!, _pin, lang: appLang);
pubkey.text = _wallet.getPubkey(derivation!);
log.d(pubkey.text);
notifyListeners();
return pubkey.text;
} catch (e) {
log.w('Bad PIN code !\n' + e.toString());
notifyListeners();
return 'false';
}
} else {
try {
pubkey.text = CesiumWallet.fromDewif(_dewif!, _pin).pubkey;
notifyListeners();
return pubkey.text;
} catch (e) {
log.w('Bad PIN code !\n' + e.toString());
notifyListeners();
return 'false';
}
}
}
String? readLocalWallet(
context, WalletData _wallet, String _pin, int _pinLenght,
{String? mnemonic}) {
isWalletUnlock = false;
final String _localPubkey;
try {
String? _localDewif = chestBox.get(_wallet.chest)!.dewif;
if (mnemonic == null) {
_localPubkey = _getPubkeyFromDewif(
_localDewif, _pin.toUpperCase(), _pinLenght, _wallet.derivation);
} else {
final _hdwallet = HdWallet.fromMnemonic(mnemonic);
_localPubkey = _hdwallet.getPubkey(_wallet.derivation!);
}
if (_localPubkey != 'false') {
pubkey.text = _localPubkey;
isWalletUnlock = true;
log.d(pubkey.text);
return _localDewif;
} else {
throw 'Bad pubkey';
}
} on ChecksumException catch (e) {
log.e(e.cause);
return 'bad';
} catch (e) {
// _homeProvider.playSound('non', 0.6);
log.e('ERROR READING FILE: $e');
pubkey.clear();
return 'bad';
}
}
int getPinLenght(_walletNbr) {
// TODOo: Get real Dewif lenght
// String _localDewif;
// if (_walletNbr is int || _walletNbr == null) {
// _localDewif = chestBox.get(configBox.get('currentChest')).dewif;
// } else {
// _localDewif = _walletNbr;
// }
// final int _pinLenght = DubpRust.getDewifSecretCodeLen(
// dewif: _localDewif, secretCodeType: SecretCodeType.letters);
int getPinLenght(walletNbr) {
return pinLength;
}
Future<double> getBalance(String pubkey, {bool isUd = false}) async {
final node = Gva(node: endPointGVA);
return await node.balance(pubkey, ud: isUd);
}
void _renameWallet(List<int?> _walletID, _newName,
void _renameWallet(List<int?> walletID, String newName,
{required bool isCesium}) async {
if (isCesium) {
ChestData _chestTarget = chestBox.get(_walletID[0])!;
_chestTarget.name = _newName;
await chestBox.put(_chestTarget.key, _chestTarget);
} else {
MyWalletsProvider myWalletClass = MyWalletsProvider();
MyWalletsProvider myWalletClass = MyWalletsProvider();
WalletData _walletTarget = myWalletClass.getWalletData(_walletID)!;
_walletTarget.name = _newName;
await walletBox.put(_walletTarget.key, _walletTarget);
}
WalletData walletTarget = myWalletClass.getWalletDataById(walletID)!;
walletTarget.name = newName;
await walletBox.put(walletTarget.key, walletTarget);
_newWalletName.text = '';
}
bool editWalletName(List<int?> _wID, {bool? isCesium}) {
bool nameState;
if (isEditing) {
if (!nameController.text.contains(':') &&
nameController.text.length <= 39) {
_renameWallet(_wID, nameController.text, isCesium: isCesium!);
nameState = true;
} else {
nameState = false;
}
} else {
nameState = true;
}
isEditing ? isEditing = false : isEditing = true;
notifyListeners();
return nameState;
}
Future<int> deleteWallet(context, WalletData wallet) async {
final bool? _answer = await (_confirmDeletingWallet(context, wallet.name));
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
final bool? answer = await (confirmPopup(
context, 'areYouSureToForgetWallet'.tr(args: [wallet.name!])));
if (_answer!) {
walletBox.delete(wallet.key);
if (answer ?? false) {
//Check if balance is null
final balance = await sub.getBalance(wallet.address!);
if (balance != {}) {
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
final defaultWallet = myWalletProvider.getDefaultWallet();
log.d(defaultWallet.address);
sub.pay(
fromAddress: wallet.address!,
destAddress: defaultWallet.address!,
amount: -1,
password: myWalletProvider.pinCode);
}
Navigator.popUntil(
context,
ModalRoute.withName('/mywallets'),
);
await walletBox.delete(wallet.key);
await sub.deleteAccounts([wallet.address!]);
Navigator.pop(context);
}
return 0;
}
Future<bool?> _confirmDeletingWallet(context, _walletName) async {
return showDialog<bool>(
context: context,
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'Êtes-vous sûr de vouloir supprimer le portefeuille "$_walletName" ?'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text('Vous pourrez restaurer ce portefeuille plus tard.'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text("Non", key: Key('cancelDeleting')),
onPressed: () {
Navigator.pop(context, false);
},
),
TextButton(
child: const Text("Oui", key: Key('confirmDeleting')),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
},
);
}
snackCopyKey(context) {
const snackBar = SnackBar(
content:
Text("Cette clé publique a été copié dans votre presse-papier."),
duration: Duration(seconds: 2));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
String getShortPubkey(String pubkey) {
List<int> pubkeyByte = Base58Decode(pubkey);
Digest pubkeyS256 = sha256.convert(sha256.convert(pubkeyByte).bytes);
String pubkeyCheksum = Base58Encode(pubkeyS256.bytes);
String pubkeyChecksumShort = truncate(pubkeyCheksum, 3,
omission: "", position: TruncatePosition.end);
String pubkeyShort = truncate(pubkey, 5,
omission: String.fromCharCode(0x2026),
position: TruncatePosition.end) +
truncate(pubkey, 4, omission: "", position: TruncatePosition.start) +
':$pubkeyChecksumShort';
return pubkeyShort;
}
void bluringBalance() {
isBalanceBlur = !isBalanceBlur;
notifyListeners();
}
Future changeAvatar() async {
File _image;
Future<String> changeAvatar() async {
// File _image;
final picker = ImagePicker();
XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
_image = File(pickedFile.path);
log.i(pickedFile.path);
return _image;
File imageFile = File(pickedFile.path);
if (!await imageDirectory.exists()) {
log.e("Image folder doesn't exist");
return '';
}
CroppedFile? croppedFile = await ImageCropper().cropImage(
sourcePath: imageFile.path,
aspectRatioPresets: [CropAspectRatioPreset.square],
cropStyle: CropStyle.circle,
uiSettings: [
AndroidUiSettings(
hideBottomControls: true,
toolbarTitle: 'Personnalisation',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: true),
IOSUiSettings(
title: 'Cropper',
),
],
);
final newPath = "${imageDirectory.path}/${pickedFile.name}";
if (croppedFile != null) {
await File(croppedFile.path).rename(newPath);
} else {
log.w('No image selected.');
return '';
}
// await imageFile.copy(newPath);
log.i(newPath);
return newPath;
} else {
log.w('No image selected.');
return '';
}
}
void reloadBuild() {
Widget idtyStatus(BuildContext context, String address,
{bool isOwner = false, Color color = Colors.black}) {
DuniterIndexer duniterIndexer =
Provider.of<DuniterIndexer>(context, listen: false);
showText(String text,
[double size = 18, bool bold = false, bool smooth = true]) {
log.d('$address $text');
return AnimatedFadeOutIn<String>(
data: text,
duration: Duration(milliseconds: smooth ? 200 : 0),
builder: (value) => Text(
value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: size,
color: bold ? color : Colors.black,
fontWeight: bold ? FontWeight.w500 : FontWeight.w400),
),
);
}
return Consumer<SubstrateSdk>(builder: (context, sub, _) {
return FutureBuilder(
future: sub.idtyStatus(address),
initialData: '',
builder: (context, snapshot) {
idtyStatusCache[address] = snapshot.data.toString();
switch (snapshot.data.toString()) {
case 'noid':
{
return showText('noIdentity'.tr());
}
case 'Created':
{
return showText('identityCreated'.tr());
}
case 'ConfirmedByOwner':
{
return isOwner
? showText('identityConfirmed'.tr())
: duniterIndexer.getNameByAddress(
context,
address,
null,
20,
true,
Colors.grey[700]!,
FontWeight.w500,
FontStyle.italic);
}
case 'Validated':
{
return isOwner
? showText('memberValidated'.tr(), 18, true)
: duniterIndexer.getNameByAddress(
context,
address,
null,
20,
true,
Colors.black,
FontWeight.w600,
FontStyle.normal);
}
case 'expired':
{
return showText('identityExpired'.tr());
}
}
return SizedBox(
child: showText('', 18, false, false),
);
});
});
}
Future<bool> isMember(BuildContext context, String address) async {
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
return await sub.idtyStatus(address) == 'Validated';
}
Future<String?> confirmIdentityPopup(BuildContext context) async {
TextEditingController idtyName = TextEditingController();
SubstrateSdk sub = Provider.of<SubstrateSdk>(context, listen: false);
WalletOptionsProvider walletOptions =
Provider.of<WalletOptionsProvider>(context, listen: false);
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
return showDialog<String>(
context: context,
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'confirmYourIdentity'.tr(),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
content: SizedBox(
height: 100,
child: Column(children: [
const SizedBox(height: 20),
TextField(
key: keyEnterIdentityUsername,
onChanged: (_) => notifyListeners(),
inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(RegExp("[0-9a-zA-Z]")),
FilteringTextInputFormatter.deny(RegExp(r'^ ')),
// FilteringTextInputFormatter.deny(RegExp(r' $')),
],
textAlign: TextAlign.center,
autofocus: true,
controller: idtyName,
style: const TextStyle(fontSize: 19),
)
]),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<WalletOptionsProvider>(
builder: (context, wOptions, _) {
return TextButton(
key: keyConfirm,
child: Text(
"validate".tr(),
style: TextStyle(
fontSize: 21,
color: idtyName.text.length.clamp(3, 64) ==
idtyName.text.length
? const Color(0xffD80000)
: Colors.grey,
),
),
onPressed: () async {
idtyName.text = idtyName.text.trim().replaceAll(' ', '');
if (idtyName.text.length.clamp(3, 64) ==
idtyName.text.length) {
WalletData? defaultWallet =
myWalletProvider.getDefaultWallet();
String? pin;
if (myWalletProvider.pinCode == '') {
pin = await Navigator.push(
context,
MaterialPageRoute(
builder: (homeContext) {
return UnlockingWallet(wallet: defaultWallet);
},
),
);
}
if (pin != null || myWalletProvider.pinCode != '') {
final wallet = myWalletProvider
.getWalletDataByAddress(address.text);
await sub.setCurrentWallet(wallet!);
sub.confirmIdentity(walletOptions.address.text,
idtyName.text, myWalletProvider.pinCode);
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return TransactionInProgress(
transType: 'comfirmIdty',
fromAddress: getShortPubkey(wallet.address!),
toAddress: getShortPubkey(wallet.address!),
);
}),
);
}
}
},
);
})
],
),
const SizedBox(height: 20)
],
);
},
);
}
Future<String?> editWalletName(BuildContext context, List<int?> wID) async {
TextEditingController walletName = TextEditingController();
canValidateNameBool = false;
return showDialog<String>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'chooseWalletName'.tr(),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
content: SizedBox(
height: 100,
child: Column(children: [
const SizedBox(height: 20),
TextField(
onChanged: (_) => canValidateName(context, walletName),
textAlign: TextAlign.center,
autofocus: true,
controller: walletName,
style: const TextStyle(fontSize: 19),
)
]),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<WalletOptionsProvider>(
builder: (context, wOptions, _) {
return TextButton(
key: keyInfoPopup,
child: Text(
"validate".tr(),
style: TextStyle(
fontSize: 21,
color: canValidateNameBool
? const Color(0xffD80000)
: Colors.grey,
fontWeight: FontWeight.w600,
),
),
onPressed: () async {
if (canValidateNameBool) {
nameController.text = walletName.text;
_renameWallet(wID, walletName.text, isCesium: false);
// notifyListeners();
Navigator.pop(context);
}
},
);
})
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
key: keyCancel,
child: Text(
"cancel".tr(),
style: TextStyle(
fontSize: 18,
color: Colors.grey[800],
fontWeight: FontWeight.w300),
),
onPressed: () async {
Navigator.pop(context);
},
)
],
),
const SizedBox(height: 20)
],
);
},
);
}
bool canValidateName(BuildContext context, TextEditingController walletName) {
MyWalletsProvider myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
bool isNameValid = walletName.text.length >= 2 &&
!walletName.text.contains(':') &&
walletName.text.length <= 39;
if (isNameValid) {
for (var wallet in myWalletProvider.listWallets) {
if (walletName.text == wallet.name!) {
canValidateNameBool = false;
break;
}
canValidateNameBool = true;
}
} else {
canValidateNameBool = false;
}
notifyListeners();
return canValidateNameBool;
}
void reload() {
notifyListeners();
}
Future changePinCacheChoice() async {
bool isCacheChecked = configBox.get('isCacheChecked') ?? false;
await configBox.put('isCacheChecked', !isCacheChecked);
notifyListeners();
}
String? getAddress(int chest, int derivation) {
String? addressGet;
walletBox.toMap().forEach((key, value) {
if (value.chest == chest && value.derivation == derivation) {
addressGet = value.address!;
return;
}
});
address.text = addressGet ?? '';
return addressGet;
}
Widget walletNameController(BuildContext context, WalletData wallet,
[double size = 20]) {
nameController.text = wallet.name!;
return SizedBox(
width: 260,
child: Stack(children: <Widget>[
TextField(
key: keyWalletName,
autofocus: false,
focusNode: walletNameFocus,
enabled: isEditing,
controller: nameController,
minLines: 1,
maxLines: 3,
textAlign: TextAlign.center,
decoration: const InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: EdgeInsets.all(15.0),
),
style: TextStyle(
fontSize: isTall ? size : size * 0.9,
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
Positioned(
right: 0,
child: InkWell(
key: keyRenameWallet,
onTap: () async {
// _isNewNameValid =
// walletProvider.editWalletName(wallet.id(), isCesium: false);
await editWalletName(context, wallet.id());
await Future.delayed(const Duration(milliseconds: 30));
walletNameFocus.requestFocus();
},
child: ClipRRect(
child: Image.asset(
isEditing
? 'assets/walletOptions/android-checkmark.png'
: 'assets/walletOptions/edit.png',
width: 25,
height: 25),
),
),
),
]),
);
}
Widget walletName(BuildContext context, WalletData wallet,
[double size = 20, Color color = Colors.black]) {
double newSize = wallet.name!.length <= 15 ? size : size - 2;
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Text(
truncate(wallet.name!, 20),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: isTall ? newSize : newSize * 0.9,
color: color,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.italic,
),
softWrap: false,
overflow: TextOverflow.ellipsis,
),
]);
}
}
Map<String, double> balanceCache = {};
Widget balance(BuildContext context, String address, double size,
[Color color = Colors.black,
Color loadingColor = const Color(0xffd07316)]) {
return Column(children: <Widget>[
Consumer<SubstrateSdk>(builder: (context, sdk, _) {
return FutureBuilder(
future: sdk.getBalance(address),
builder: (BuildContext context,
AsyncSnapshot<Map<String, double>> globalBalance) {
if (globalBalance.connectionState != ConnectionState.done ||
globalBalance.hasError) {
if (balanceCache[address] != null &&
balanceCache[address] != -1) {
return Row(children: [
Text(balanceCache[address]!.toString(),
style: TextStyle(
fontSize: isTall ? size : size * 0.9, color: color)),
const SizedBox(width: 5),
udUnitDisplay(size, color),
]);
} else {
return SizedBox(
height: 15,
width: 15,
child: CircularProgressIndicator(
color: loadingColor,
strokeWidth: 2,
),
);
}
}
balanceCache[address] = globalBalance.data!['transferableBalance']!;
if (balanceCache[address] != -1) {
return Row(children: [
Text(
balanceCache[address]!.toString(),
style: TextStyle(
fontSize: isTall ? size : size * 0.9,
color: color,
),
),
const SizedBox(width: 5),
udUnitDisplay(size, color),
]);
} else {
return const Text('');
}
});
}),
]);
}
Widget getCerts(BuildContext context, String address, double size,
[Color color = Colors.black]) {
return Column(children: <Widget>[
Consumer<SubstrateSdk>(builder: (context, sdk, _) {
return FutureBuilder(
future: sdk.getCerts(address),
builder: (BuildContext context, AsyncSnapshot<List<int>> certs) {
// log.d(_certs.data);
return certs.data?[0] != 0 && certs.data != null
? Row(
children: [
Image.asset('assets/medal.png', height: 20),
const SizedBox(width: 1),
Text(certs.data?[0].toString() ?? '0',
style: const TextStyle(fontSize: 20)),
const SizedBox(width: 5),
Text(
"(${certs.data?[1].toString() ?? '0'})",
style: const TextStyle(fontSize: 14),
)
],
)
: const Text('');
});
}),
]);
}
Widget udUnitDisplay(double size, [Color color = Colors.black]) {
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
return isUdUnit
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'ud'.tr(args: ['']),
style:
TextStyle(fontSize: isTall ? size : size * 0.9, color: color),
),
Column(
children: [
Text(
currencyName,
style: TextStyle(
fontSize: (isTall ? size : size * 0.9) * 0.7,
fontWeight: FontWeight.w500,
color: color),
),
const SizedBox(height: 15)
],
)
],
)
: Text(currencyName,
style: TextStyle(fontSize: isTall ? size : size * 0.9, color: color));
}

View File

@ -1,117 +1,90 @@
import 'dart:io';
import 'package:durt/durt.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/cesium_plus.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/screens/common_elements.dart';
import 'package:gecko/screens/wallet_view.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:jdenticon_dart/jdenticon_dart.dart';
import 'package:permission_handler/permission_handler.dart';
// import 'package:qrscan/qrscan.dart' as scanner;
import 'package:barcode_scan2/barcode_scan2.dart';
import 'package:provider/provider.dart';
import 'package:qrscan/qrscan.dart' as scanner;
import 'dart:math';
import 'package:intl/intl.dart';
import 'package:truncate/truncate.dart';
import 'package:crypto/crypto.dart';
import 'package:fast_base58/fast_base58.dart';
class WalletsProfilesProvider with ChangeNotifier {
WalletsProfilesProvider(this.pubkey);
WalletsProfilesProvider(this.address);
String? pubkey = '';
String address = '';
String pubkeyShort = '';
final TextEditingController outputPubkey = TextEditingController();
List? transBC;
String? fetchMoreCursor;
Map? pageInfo;
bool isHistoryScreen = false;
String historySwitchButtun = "Voir l'historique";
String? rawSvg;
TextEditingController payAmount = TextEditingController();
TextEditingController payComment = TextEditingController();
num? balance;
int nRepositories = 20;
int nPage = 1;
num? _balance;
Future scan(context) async {
Future<String> scan(context) async {
if (Platform.isAndroid || Platform.isIOS) {
await Permission.camera.request();
}
String? barcode;
ScanResult? barcode;
try {
barcode = await scanner.scan();
barcode = await BarcodeScanner.scan();
} catch (e) {
log.e(e);
return 'false';
}
if (barcode != null && isPubkey(barcode)) {
outputPubkey.text = barcode;
if (isAddress(barcode.rawContent)) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return WalletViewScreen(pubkey: pubkey);
return WalletViewScreen(address: barcode!.rawContent);
}),
);
} else {
return 'false';
}
return barcode;
return barcode.rawContent;
}
Future<String> pay(BuildContext context, {int? derivation}) async {
MyWalletsProvider _myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
int? currentChest = configBox.get('currentChest');
String result;
// Future<String> pay(BuildContext context, {int? derivation}) async {
// MyWalletsProvider _myWalletProvider =
// Provider.of<MyWalletsProvider>(context, listen: false);
// int? currentChest = configBox.get('currentChest');
// String result;
if (chestBox.get(currentChest)!.isCesium!) {
result = await Gva(node: endPointGVA).pay(
recipient: pubkey!,
amount: double.parse(payAmount.text),
cesiumSeed: _myWalletProvider.cesiumSeed,
comment: payComment.text,
derivation: -1,
lang: appLang);
} else {
derivation ??=
_myWalletProvider.getDefaultWallet(currentChest)!.derivation!;
result = await Gva(node: endPointGVA).pay(
recipient: pubkey!,
amount: double.parse(payAmount.text),
mnemonic: _myWalletProvider.mnemonic,
comment: payComment.text,
derivation: derivation,
lang: appLang);
}
// derivation ??=
// _myWalletProvider.getDefaultWallet(currentChest)!.derivation!;
// result = await Gva(node: endPointGVA).pay(
// recipient: pubkey!,
// amount: double.parse(payAmount.text),
// mnemonic: _myWalletProvider.mnemonic,
// comment: payComment.text,
// derivation: derivation,
// lang: appLang);
return result;
}
// return result;
// }
bool isPubkey(pubkey) {
bool isAddress(address) {
final RegExp regExp = RegExp(
r'^[a-zA-Z0-9]+$',
caseSensitive: false,
multiLine: false,
);
if (regExp.hasMatch(pubkey) == true &&
pubkey.length > 42 &&
pubkey.length < 45) {
log.d("C'est une pubkey !");
if (regExp.hasMatch(address) == true &&
address.length > 45 &&
address.length < 52) {
log.d("C'est une adresse !");
this.pubkey = pubkey;
// getShortPubkey(pubkey);
// outputPubkey.text = pubkey;
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) {
// return const WalletViewScreen();
// }),
// );
// notifyListeners();
this.address = address;
return true;
} else {
@ -119,161 +92,18 @@ class WalletsProfilesProvider with ChangeNotifier {
}
}
String getShortPubkey(String pubkey) {
List<int> pubkeyByte = Base58Decode(pubkey);
Digest pubkeyS256 = sha256.convert(sha256.convert(pubkeyByte).bytes);
String pubkeyCheksum = Base58Encode(pubkeyS256.bytes);
String pubkeyChecksumShort = truncate(pubkeyCheksum, 3,
omission: "", position: TruncatePosition.end);
pubkeyShort = truncate(pubkey, 5,
omission: String.fromCharCode(0x2026),
position: TruncatePosition.end) +
truncate(pubkey, 4, omission: "", position: TruncatePosition.start) +
':$pubkeyChecksumShort';
return pubkeyShort;
}
// poka: Do99s6wQR2JLfhirPdpAERSjNbmjjECzGxHNJMiNKT3P
// Pi: D2meevcAHFTS2gQMvmRW5Hzi25jDdikk4nC4u1FkwRaU // For debug
// Boris: JE6mkuzSpT3ePciCPRTpuMT9fqPUVVLJz2618d33p7tn
// Matograine portefeuille: 9p5nHsES6xujFR7pw2yGy4PLKKHgWsMvsDHaHF64Uj25.
// Lion simone: 78jhpprYkMNF6i5kQPXfkAVBpd2aqcpieNsXTSW4c21f
List parseHistory(txs, _pubkey) {
var transBC = [];
int i = 0;
const currentBase = 0;
double currentUD = 10.54;
for (final trans in txs) {
var direction = trans['direction'];
final transaction = trans['node'];
String? output;
if (direction == "RECEIVED") {
for (String line in transaction['outputs']) {
if (line.contains(_pubkey)) {
output = line;
}
}
} else {
output = transaction['outputs'][0];
}
if (output == null) {
continue;
}
transBC.add(i);
transBC[i] = [];
final dateBrut = DateTime.fromMillisecondsSinceEpoch(
transaction['writtenTime'] * 1000);
final DateFormat formatter = DateFormat('dd-MM-yy\nHH:mm');
final date = formatter.format(dateBrut);
transBC[i].add(transaction['writtenTime']);
transBC[i].add(date);
final int amountBrut = int.parse(output.split(':')[0]);
final base = int.parse(output.split(':')[1]);
final int applyBase = base - currentBase;
final num amount =
removeDecimalZero(amountBrut * pow(10, applyBase) / 100);
num amountUD = amount / currentUD;
if (direction == "RECEIVED") {
transBC[i].add(transaction['issuers'][0]);
transBC[i].add(getShortPubkey(transaction['issuers'][0]));
transBC[i].add(amount.toString());
transBC[i].add(amountUD.toStringAsFixed(2));
} else if (direction == "SENT") {
final outPubkey = output.split("SIG(")[1].replaceAll(')', '');
transBC[i].add(outPubkey);
transBC[i].add(getShortPubkey(outPubkey));
transBC[i].add('- ' + amount.toString());
transBC[i].add(amountUD.toStringAsFixed(2));
}
transBC[i].add(transaction['comment']);
i++;
}
return transBC;
}
FetchMoreOptions? checkQueryResult(result, opts, _pubkey) {
final List<dynamic>? blockchainTX =
(result.data['txsHistoryBc']['both']['edges'] as List<dynamic>?);
// final List<dynamic> mempoolTX =
// (result.data['txsHistoryMp']['receiving'] as List<dynamic>);
pageInfo = result.data['txsHistoryBc']['both']['pageInfo'];
fetchMoreCursor = pageInfo!['endCursor'];
if (fetchMoreCursor == null) nPage = 1;
if (nPage == 1) {
nRepositories = 40;
} else if (nPage == 2) {
nRepositories = 100;
}
nPage++;
if (fetchMoreCursor != null) {
opts = FetchMoreOptions(
variables: {'cursor': fetchMoreCursor, 'number': nRepositories},
updateQuery: (previousResultData, fetchMoreResultData) {
final List<dynamic> repos = [
...previousResultData!['txsHistoryBc']['both']['edges']
as List<dynamic>,
...fetchMoreResultData!['txsHistoryBc']['both']['edges']
as List<dynamic>
];
fetchMoreResultData['txsHistoryBc']['both']['edges'] = repos;
return fetchMoreResultData;
},
);
}
log.d(
"###### DEBUG H Parse blockchainTX list. Cursor: $fetchMoreCursor ######");
if (fetchMoreCursor != null) {
transBC = parseHistory(blockchainTX, _pubkey);
} else {
log.i("###### DEBUG H - Début de l'historique");
}
return opts;
}
void resetdHistory() {
outputPubkey.text = '';
notifyListeners();
}
num removeDecimalZero(double n) {
String result = n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2);
return num.parse(result);
}
snackCopyKey(context) {
const snackBar = SnackBar(
padding: EdgeInsets.all(20),
content:
Text("Cette clé publique a été copié dans votre presse-papier."),
duration: Duration(seconds: 2));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void switchProfileView() {
isHistoryScreen = !isHistoryScreen;
if (isHistoryScreen) {
historySwitchButtun = "Payer";
} else {
historySwitchButtun = "Voir l'historique";
}
notifyListeners();
}
String generateIdenticon(String _pubkey) {
return Jdenticon.toSvg(_pubkey);
String generateIdenticon(String pubkey) {
return Jdenticon.toSvg(pubkey);
}
// Future<num> getBalance(String _pubkey) async {
@ -291,11 +121,134 @@ class WalletsProfilesProvider with ChangeNotifier {
// return balance;
// }
Future<num?> getBalance(String? _pubkey) async {
while (balance == null) {
Future<num?> getBalance(String? pubkey) async {
while (_balance == null) {
await Future.delayed(const Duration(milliseconds: 50));
}
return balance;
return _balance;
}
Widget headerProfileView(
BuildContext context, String address, String? username) {
const double avatarSize = 140;
WalletOptionsProvider walletOptions =
Provider.of<WalletOptionsProvider>(context, listen: false);
CesiumPlusProvider cesiumPlusProvider =
Provider.of<CesiumPlusProvider>(context, listen: false);
// SubstrateSdk _sub = Provider.of<SubstrateSdk>(context, listen: false);
return Stack(children: <Widget>[
Consumer<SubstrateSdk>(builder: (context, sub, _) {
bool isAccountExist = balanceCache[address] != 0;
return Container(
height: 180,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
isAccountExist ? yellowC : Colors.grey[400]!,
isAccountExist ? const Color(0xFFE7811A) : Colors.grey[600]!,
],
),
));
}),
Padding(
padding: const EdgeInsets.only(left: 30, right: 40),
child: Row(children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
height: 10,
color: yellowC, // Colors.grey[400],
),
Row(children: [
GestureDetector(
key: keyCopyAddress,
onTap: () {
Clipboard.setData(ClipboardData(text: address));
snackCopyKey(context);
},
child: Text(
getShortPubkey(address),
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.w800,
),
),
),
]),
const SizedBox(height: 25),
balance(context, address, 22),
const SizedBox(height: 10),
walletOptions.idtyStatus(context, address,
isOwner: false, color: Colors.black),
getCerts(context, address, 14),
// if (username == null &&
// g1WalletsBox.get(address)?.username != null)
// SizedBox(
// width: 230,
// child: Text(
// g1WalletsBox.get(address)?.username ?? '',
// style: const TextStyle(
// fontSize: 27,
// color: Color(0xff814C00),
// ),
// ),
// ),
// if (username != null)
// SizedBox(
// width: 230,
// child: Text(
// username,
// style: const TextStyle(
// fontSize: 27,
// color: Color(0xff814C00),
// ),
// ),
// ),
const SizedBox(height: 55),
]),
const Spacer(),
Column(children: <Widget>[
ClipOval(
child: cesiumPlusProvider.defaultAvatar(avatarSize),
),
const SizedBox(height: 25),
]),
]),
),
CommonElements().offlineInfo(context),
]);
}
bool isContact(String address) {
return contactsBox.containsKey(address);
}
Future addContact(G1WalletsList profile) async {
// log.d(profile.username);
if (isContact(profile.address)) {
await contactsBox.delete(profile.address);
} else {
await contactsBox.put(profile.address, profile);
}
notifyListeners();
}
void reload() {
notifyListeners();
}
}
snackCopyKey(context) {
final snackBar = SnackBar(
padding: const EdgeInsets.all(20),
content: Text("thisAddressHasBeenCopiedToClipboard".tr(),
style: const TextStyle(fontSize: 16)),
duration: const Duration(seconds: 2));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}

365
lib/screens/activity.dart Normal file
View File

@ -0,0 +1,365 @@
// ignore_for_file: must_be_immutable
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/cesium_plus.dart';
import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/home.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallets_profiles.dart';
import 'package:flutter/material.dart';
import 'package:gecko/screens/wallet_view.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
class ActivityScreen extends StatelessWidget with ChangeNotifier {
ActivityScreen({required this.address, required this.avatar, this.username})
: super(key: keyActivityScreen);
final ScrollController scrollController = ScrollController();
final double avatarsSize = 80;
final String address;
final String? username;
final Image avatar;
FetchMore? fetchMore;
FetchMoreOptions? opts;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
WalletsProfilesProvider walletProfile =
Provider.of<WalletsProfilesProvider>(context, listen: false);
HomeProvider homeProvider =
Provider.of<HomeProvider>(context, listen: false);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
toolbarHeight: 60 * ratio,
title: SizedBox(
height: 22,
child: Text('accountActivity'.tr()),
),
),
bottomNavigationBar: homeProvider.bottomAppBar(context),
body: Column(children: <Widget>[
walletProfile.headerProfileView(context, address, username),
historyQuery(context),
]));
}
Widget historyQuery(context) {
DuniterIndexer duniterIndexer =
Provider.of<DuniterIndexer>(context, listen: false);
if (indexerEndpoint == '') {
Column(children: <Widget>[
const SizedBox(height: 50),
Text(
"noNetworkNoHistory".tr(),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18),
)
]);
}
final httpLink = HttpLink(
'$indexerEndpoint/v1beta1/relay',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
),
);
return GraphQLProvider(
client: client,
child: Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Query(
options: QueryOptions(
document: gql(getHistoryByAddressQ),
variables: <String, dynamic>{
'address': address,
'number': 20,
'cursor': null
},
),
builder: (QueryResult result, {fetchMore, refetch}) {
if (result.isLoading && result.data == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (result.hasException) {
log.e('Error Indexer: ${result.exception}');
return Column(children: <Widget>[
const SizedBox(height: 50),
Text(
"noNetworkNoHistory".tr(),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18),
)
]);
} else if (result
.data?['transaction_connection']?['edges'].isEmpty) {
return Column(children: <Widget>[
const SizedBox(height: 50),
Text(
"noDataToDisplay".tr(),
style: const TextStyle(fontSize: 18),
)
]);
}
if (result.isNotLoading) {
// log.d(result.data);
opts = duniterIndexer.checkQueryResult(result, opts, address);
}
// Build history list
return NotificationListener(
child: Builder(
builder: (context) => Expanded(
child: ListView(
key: keyListTransactions,
controller: scrollController,
children: <Widget>[historyView(context, result)],
),
),
),
onNotification: (dynamic t) {
if (t is ScrollEndNotification &&
scrollController.position.pixels >=
scrollController.position.maxScrollExtent * 0.7 &&
duniterIndexer.pageInfo!['hasNextPage'] &&
result.isNotLoading) {
fetchMore!(opts!);
}
return true;
});
},
),
],
)),
);
}
Widget historyView(context, result) {
DuniterIndexer duniterIndexer =
Provider.of<DuniterIndexer>(context, listen: false);
return duniterIndexer.transBC == null
? Column(children: <Widget>[
const SizedBox(height: 50),
Text(
"noTransactionToDisplay".tr(),
style: const TextStyle(fontSize: 18),
)
])
: Column(children: <Widget>[
getTransactionTile(context, duniterIndexer),
if (result.isLoading && duniterIndexer.pageInfo!['hasPreviousPage'])
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
CircularProgressIndicator(),
],
),
if (!duniterIndexer.pageInfo!['hasNextPage'])
Column(
children: const <Widget>[
SizedBox(height: 15),
Text("Début de l'historique.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 20)),
SizedBox(height: 15)
],
)
]);
}
Widget getTransactionTile(
BuildContext context, DuniterIndexer duniterIndexer) {
CesiumPlusProvider cesiumPlusProvider =
Provider.of<CesiumPlusProvider>(context, listen: false);
int keyID = 0;
String? dateDelimiter;
String? lastDateDelimiter;
const double avatarSize = 200;
bool isTody = false;
bool isYesterday = false;
bool isThisWeek = false;
final Map<int, String> monthsInYear = {
1: "month1".tr(),
2: "month2".tr(),
3: "month3".tr(),
4: "month4".tr(),
5: "month5".tr(),
6: "month6".tr(),
7: "month7".tr(),
8: "month8".tr(),
9: "month9".tr(),
10: "month10".tr(),
11: "month11".tr(),
12: "month12".tr()
};
return Column(
children: duniterIndexer.transBC!.map((repository) {
// log.d('bbbbbbbbbbbbbbbbbbbbbb: ' + repository.toString());
DateTime now = DateTime.now();
DateTime date = repository[0];
String dateForm;
if ({4, 10, 11, 12}.contains(date.month)) {
dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 3)}.";
} else if ({1, 2, 7, 9}.contains(date.month)) {
dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 4)}.";
} else {
dateForm = "${date.day} ${monthsInYear[date.month]}";
}
int weekNumber(DateTime date) {
int dayOfYear = int.parse(DateFormat("D").format(date));
return ((dayOfYear - date.weekday + 10) / 7).floor();
}
final transactionDate = DateTime(date.year, date.month, date.day);
final todayDate = DateTime(now.year, now.month, now.day);
final yesterdayDate = DateTime(now.year, now.month, now.day - 1);
if (transactionDate == todayDate && !isTody) {
dateDelimiter = lastDateDelimiter = "today".tr();
isTody = true;
} else if (transactionDate == yesterdayDate && !isYesterday) {
dateDelimiter = lastDateDelimiter = "yesterday".tr();
isYesterday = true;
} else if (weekNumber(date) == weekNumber(now) &&
date.year == now.year &&
lastDateDelimiter != "thisWeek".tr() &&
transactionDate != yesterdayDate &&
transactionDate != todayDate &&
!isThisWeek) {
dateDelimiter = lastDateDelimiter = "thisWeek".tr();
isThisWeek = true;
} else if (lastDateDelimiter != monthsInYear[date.month] &&
lastDateDelimiter != "${monthsInYear[date.month]} ${date.year}" &&
transactionDate != todayDate &&
transactionDate != yesterdayDate &&
!(weekNumber(date) == weekNumber(now) && date.year == now.year)) {
if (date.year == now.year) {
dateDelimiter = lastDateDelimiter = monthsInYear[date.month];
} else {
dateDelimiter =
lastDateDelimiter = "${monthsInYear[date.month]} ${date.year}";
}
} else {
dateDelimiter = null;
}
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
late double amount;
late String finalAmount;
amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1;
if (isUdUnit) {
amount = round(amount / balanceRatio);
finalAmount = 'ud'.tr(args: ['$amount ']);
} else {
finalAmount = '$amount $currencyName';
}
return Column(children: <Widget>[
if (dateDelimiter != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 30),
child: Text(
dateDelimiter!,
style: const TextStyle(
fontSize: 23, color: orangeC, fontWeight: FontWeight.w300),
),
),
Padding(
padding: const EdgeInsets.only(right: 0),
child:
// Row(children: [Column(children: [],)],)
ListTile(
key: keyTransaction(keyID++),
contentPadding: const EdgeInsets.only(
left: 20, right: 30, top: 15, bottom: 15),
leading: ClipOval(
child: cesiumPlusProvider.defaultAvatar(avatarSize),
),
title: Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Text(getShortPubkey(repository[1]),
style: const TextStyle(
fontSize: 18, fontFamily: 'Monospace')),
),
subtitle: RichText(
text: TextSpan(
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
),
children: <TextSpan>[
TextSpan(
text: dateForm,
),
if (repository[2] != '')
TextSpan(
text: ' · ',
style: TextStyle(
fontSize: 20,
color: Colors.grey[550],
),
),
TextSpan(
text: repository[2],
style: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.grey[600],
),
),
],
),
),
trailing: Text(finalAmount,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
textAlign: TextAlign.justify),
dense: false,
isThreeLine: false,
onTap: () {
duniterIndexer.nPage = 1;
// _cesiumPlusProvider.avatarCancelToken.cancel('cancelled');
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return WalletViewScreen(address: repository[1]);
}),
);
// Navigator.pop(context);
}),
),
]);
}).toList());
}
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
typedef DataDidChangeCallback<T> = bool Function(T o, T n);
///Fades out the old data and back in with the new
class AnimatedFadeOutIn<T> extends StatefulWidget {
///If [initialData] is not null on first build it will be shown.
///Directly after that the animation changes the value to [data].
final T? initialData;
///The data to show
final T data;
final Duration duration;
final Widget Function(T data) builder;
final VoidCallback? onFadeComplete;
/// Compare the values to determine if the data has changed.
/// If [null] the data is compared using `!=` expression
final DataDidChangeCallback<T>? dataDidChange;
const AnimatedFadeOutIn({
Key? key,
this.initialData,
required this.data,
required this.builder,
this.duration = const Duration(milliseconds: 300),
this.onFadeComplete,
this.dataDidChange,
}) : super(key: key);
@override
AnimatedFadeOutInState<T> createState() => AnimatedFadeOutInState<T>();
}
class AnimatedFadeOutInState<T> extends State<AnimatedFadeOutIn<T>>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
late T dataToShow;
@override
void initState() {
super.initState();
dataToShow = widget.initialData ?? widget.data;
controller = AnimationController(vsync: this, duration: widget.duration)
..addListener(() => setState(() {}))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
dataToShow = widget.data;
controller.reverse(from: 1.0);
} else if (status == AnimationStatus.dismissed) {
widget.onFadeComplete?.call();
}
});
animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeIn,
reverseCurve: Curves.easeOut,
),
);
if (widget.dataDidChange?.call(dataToShow, widget.data) ??
widget.data != dataToShow) {
controller.forward(from: 0.0);
}
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(AnimatedFadeOutIn<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.dataDidChange?.call(oldWidget.data, widget.data) ??
widget.data != oldWidget.data) {
dataToShow = oldWidget.data;
controller.forward(from: 0.0);
}
}
@override
Widget build(BuildContext context) => Opacity(
opacity: 1.0 - animation.value,
child: widget.builder(dataToShow),
);
}

Some files were not shown because too many files have changed in this diff Show More