diff --git a/PRIVACY.md b/PRIVACY.md
index 76cfcc4d3..a5c8eddfb 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -5,7 +5,7 @@ Last modified: January 24, 2024
Introduction
============
- Cake Labs LLC ("Cake Labs", "Company", or "We") respect your privacy and are committed to protecting it through our compliance with this policy.
+ Cake Labs LLC ("Cake Labs", "Company", or "We") respects your privacy and are committed to protecting it through our compliance with this policy.
This policy describes the types of information we may collect from you or that you may provide when you use the App (our "App") and our practices for collecting, using, maintaining, protecting, and disclosing that information.
@@ -13,7 +13,7 @@ Introduction
- On this App.
- In email, text, and other electronic messages between you and this App.
It does not apply to information collected by:
- - Us offline or through any other means, including on any other App operated by Company or any third party (including our affiliates and subsidiaries); or
+ - Us offline or through any other means, including on any other App operated by the Company or any third party (including our affiliates and subsidiaries); or
- Any third party (including our affiliates and subsidiaries), including through any application or content (including advertising) that may link to or be accessible from or on the App.
Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, you have the choice to not use the App. By accessing or using this App, you agree to this privacy policy. This policy may change from time to time. Your continued use of this App after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates.
diff --git a/README.md b/README.md
index 1c28f92a2..078c4437e 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
# Cake Wallet
-Cake Wallet is an open source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
+[Cake Wallet](https://cakewallet.com) is an open-source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
Cake Wallet includes support for several cryptocurrencies, including:
* Monero (XMR)
@@ -26,7 +26,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Ethereum (ETH)
* Litecoin (LTC)
* Bitcoin Cash (BCH)
-* Polygon (MATIC)
+* Polygon (Pol)
* Solana (SOL)
* Nano (XNO)
* Haven (XHV)
@@ -44,7 +44,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Create several wallets
* Select your own custom nodes/servers
* Address book
-* Backup to external location or iCloud
+* Backup to an external location or iCloud
* Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles
* Set desired network fee level
* Store local transaction notes
@@ -161,7 +161,7 @@ The only parts to be translated, if needed, are the values m and s after the var
4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language.
-5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop.
+5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make it transparent. Or you can use another program like Photoshop.
6. Add the new language code to `tool/utils/translation/translation_constants.dart`
diff --git a/SECURITY.md b/SECURITY.md
index a1b489b76..e7c6baa02 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,4 +9,4 @@ If you need to report a vulnerability, please either:
## Supported Versions
-As we don't maintain prevoius versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
+As we don't maintain previous versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 98bbb1236..5b080e3ec 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -14,7 +14,8 @@
-
+
+
diff --git a/assets/images/flags/abw.png b/assets/images/flags/abw.png
new file mode 100644
index 000000000..d049d3a43
Binary files /dev/null and b/assets/images/flags/abw.png differ
diff --git a/assets/images/flags/afg.png b/assets/images/flags/afg.png
new file mode 100644
index 000000000..f2ea25144
Binary files /dev/null and b/assets/images/flags/afg.png differ
diff --git a/assets/images/flags/ago.png b/assets/images/flags/ago.png
new file mode 100644
index 000000000..b04d7dfa6
Binary files /dev/null and b/assets/images/flags/ago.png differ
diff --git a/assets/images/flags/aia.png b/assets/images/flags/aia.png
new file mode 100644
index 000000000..193f0ff41
Binary files /dev/null and b/assets/images/flags/aia.png differ
diff --git a/assets/images/flags/and.png b/assets/images/flags/and.png
new file mode 100644
index 000000000..fc5d9a89b
Binary files /dev/null and b/assets/images/flags/and.png differ
diff --git a/assets/images/flags/asm.png b/assets/images/flags/asm.png
new file mode 100644
index 000000000..fd3818eda
Binary files /dev/null and b/assets/images/flags/asm.png differ
diff --git a/assets/images/flags/atf.png b/assets/images/flags/atf.png
new file mode 100644
index 000000000..af77e45d5
Binary files /dev/null and b/assets/images/flags/atf.png differ
diff --git a/assets/images/flags/atg.png b/assets/images/flags/atg.png
new file mode 100644
index 000000000..d9a3d9f9e
Binary files /dev/null and b/assets/images/flags/atg.png differ
diff --git a/assets/images/flags/aut.png b/assets/images/flags/aut.png
new file mode 100644
index 000000000..1be1ff483
Binary files /dev/null and b/assets/images/flags/aut.png differ
diff --git a/assets/images/flags/aze.png b/assets/images/flags/aze.png
new file mode 100644
index 000000000..834b1e696
Binary files /dev/null and b/assets/images/flags/aze.png differ
diff --git a/assets/images/flags/bel.png b/assets/images/flags/bel.png
new file mode 100644
index 000000000..1c06c5fa7
Binary files /dev/null and b/assets/images/flags/bel.png differ
diff --git a/assets/images/flags/bes.png b/assets/images/flags/bes.png
new file mode 100644
index 000000000..b00bfb1f5
Binary files /dev/null and b/assets/images/flags/bes.png differ
diff --git a/assets/images/flags/bhr.png b/assets/images/flags/bhr.png
new file mode 100644
index 000000000..135c254cb
Binary files /dev/null and b/assets/images/flags/bhr.png differ
diff --git a/assets/images/flags/blz.png b/assets/images/flags/blz.png
new file mode 100644
index 000000000..06b23f161
Binary files /dev/null and b/assets/images/flags/blz.png differ
diff --git a/assets/images/flags/bmu.png b/assets/images/flags/bmu.png
new file mode 100644
index 000000000..6e253a8e6
Binary files /dev/null and b/assets/images/flags/bmu.png differ
diff --git a/assets/images/flags/bol.png b/assets/images/flags/bol.png
new file mode 100644
index 000000000..4996ddbcc
Binary files /dev/null and b/assets/images/flags/bol.png differ
diff --git a/assets/images/flags/brn.png b/assets/images/flags/brn.png
new file mode 100644
index 000000000..bd1d1cc9a
Binary files /dev/null and b/assets/images/flags/brn.png differ
diff --git a/assets/images/flags/btn.png b/assets/images/flags/btn.png
new file mode 100644
index 000000000..962e5e5bb
Binary files /dev/null and b/assets/images/flags/btn.png differ
diff --git a/assets/images/flags/bvt.png b/assets/images/flags/bvt.png
new file mode 100644
index 000000000..4acde7fbf
Binary files /dev/null and b/assets/images/flags/bvt.png differ
diff --git a/assets/images/flags/bwa.png b/assets/images/flags/bwa.png
new file mode 100644
index 000000000..5b7eff92a
Binary files /dev/null and b/assets/images/flags/bwa.png differ
diff --git a/assets/images/flags/cck.png b/assets/images/flags/cck.png
new file mode 100644
index 000000000..d255ab91a
Binary files /dev/null and b/assets/images/flags/cck.png differ
diff --git a/assets/images/flags/cmr.png b/assets/images/flags/cmr.png
new file mode 100644
index 000000000..2bc6ad13c
Binary files /dev/null and b/assets/images/flags/cmr.png differ
diff --git a/assets/images/flags/cok.png b/assets/images/flags/cok.png
new file mode 100644
index 000000000..49386516d
Binary files /dev/null and b/assets/images/flags/cok.png differ
diff --git a/assets/images/flags/cpv.png b/assets/images/flags/cpv.png
new file mode 100644
index 000000000..0683d931f
Binary files /dev/null and b/assets/images/flags/cpv.png differ
diff --git a/assets/images/flags/cri.png b/assets/images/flags/cri.png
new file mode 100644
index 000000000..029bbfc49
Binary files /dev/null and b/assets/images/flags/cri.png differ
diff --git a/assets/images/flags/cuw.png b/assets/images/flags/cuw.png
new file mode 100644
index 000000000..92a36b728
Binary files /dev/null and b/assets/images/flags/cuw.png differ
diff --git a/assets/images/flags/cxr.png b/assets/images/flags/cxr.png
new file mode 100644
index 000000000..e644a49ea
Binary files /dev/null and b/assets/images/flags/cxr.png differ
diff --git a/assets/images/flags/cyp.png b/assets/images/flags/cyp.png
new file mode 100644
index 000000000..ba5246809
Binary files /dev/null and b/assets/images/flags/cyp.png differ
diff --git a/assets/images/flags/dji.png b/assets/images/flags/dji.png
new file mode 100644
index 000000000..185c5322b
Binary files /dev/null and b/assets/images/flags/dji.png differ
diff --git a/assets/images/flags/dma.png b/assets/images/flags/dma.png
new file mode 100644
index 000000000..7f61af95e
Binary files /dev/null and b/assets/images/flags/dma.png differ
diff --git a/assets/images/flags/dza.png b/assets/images/flags/dza.png
new file mode 100644
index 000000000..342284223
Binary files /dev/null and b/assets/images/flags/dza.png differ
diff --git a/assets/images/flags/ecu.png b/assets/images/flags/ecu.png
new file mode 100644
index 000000000..efb3acb43
Binary files /dev/null and b/assets/images/flags/ecu.png differ
diff --git a/assets/images/flags/est.png b/assets/images/flags/est.png
new file mode 100644
index 000000000..c2e417b93
Binary files /dev/null and b/assets/images/flags/est.png differ
diff --git a/assets/images/flags/eth.png b/assets/images/flags/eth.png
new file mode 100644
index 000000000..5b4970a67
Binary files /dev/null and b/assets/images/flags/eth.png differ
diff --git a/assets/images/flags/fin.png b/assets/images/flags/fin.png
new file mode 100644
index 000000000..b6bc2f9a9
Binary files /dev/null and b/assets/images/flags/fin.png differ
diff --git a/assets/images/flags/fji.png b/assets/images/flags/fji.png
new file mode 100644
index 000000000..4700ad579
Binary files /dev/null and b/assets/images/flags/fji.png differ
diff --git a/assets/images/flags/flk.png b/assets/images/flags/flk.png
new file mode 100644
index 000000000..66ff172c2
Binary files /dev/null and b/assets/images/flags/flk.png differ
diff --git a/assets/images/flags/fro.png b/assets/images/flags/fro.png
new file mode 100644
index 000000000..2c3ed5f6b
Binary files /dev/null and b/assets/images/flags/fro.png differ
diff --git a/assets/images/flags/fsm.png b/assets/images/flags/fsm.png
new file mode 100644
index 000000000..b8aedd34e
Binary files /dev/null and b/assets/images/flags/fsm.png differ
diff --git a/assets/images/flags/gab.png b/assets/images/flags/gab.png
new file mode 100644
index 000000000..c8e1cbd1f
Binary files /dev/null and b/assets/images/flags/gab.png differ
diff --git a/assets/images/flags/geo.png b/assets/images/flags/geo.png
new file mode 100644
index 000000000..46c83a589
Binary files /dev/null and b/assets/images/flags/geo.png differ
diff --git a/assets/images/flags/ggi.png b/assets/images/flags/ggi.png
new file mode 100644
index 000000000..fbc403f16
Binary files /dev/null and b/assets/images/flags/ggi.png differ
diff --git a/assets/images/flags/ggy.png b/assets/images/flags/ggy.png
new file mode 100644
index 000000000..a882b4a59
Binary files /dev/null and b/assets/images/flags/ggy.png differ
diff --git a/assets/images/flags/glp.png b/assets/images/flags/glp.png
new file mode 100644
index 000000000..8bd0a69bf
Binary files /dev/null and b/assets/images/flags/glp.png differ
diff --git a/assets/images/flags/gmb.png b/assets/images/flags/gmb.png
new file mode 100644
index 000000000..fa641ca1a
Binary files /dev/null and b/assets/images/flags/gmb.png differ
diff --git a/assets/images/flags/grc.png b/assets/images/flags/grc.png
new file mode 100644
index 000000000..d7b37b0c7
Binary files /dev/null and b/assets/images/flags/grc.png differ
diff --git a/assets/images/flags/grd.png b/assets/images/flags/grd.png
new file mode 100644
index 000000000..7138a28d7
Binary files /dev/null and b/assets/images/flags/grd.png differ
diff --git a/assets/images/flags/grl.png b/assets/images/flags/grl.png
new file mode 100644
index 000000000..53e45988b
Binary files /dev/null and b/assets/images/flags/grl.png differ
diff --git a/assets/images/flags/guf.png b/assets/images/flags/guf.png
new file mode 100644
index 000000000..07a2d5070
Binary files /dev/null and b/assets/images/flags/guf.png differ
diff --git a/assets/images/flags/gum.png b/assets/images/flags/gum.png
new file mode 100644
index 000000000..828c5f3d9
Binary files /dev/null and b/assets/images/flags/gum.png differ
diff --git a/assets/images/flags/guy.png b/assets/images/flags/guy.png
new file mode 100644
index 000000000..5845c6db9
Binary files /dev/null and b/assets/images/flags/guy.png differ
diff --git a/assets/images/flags/hmd.png b/assets/images/flags/hmd.png
new file mode 100644
index 000000000..8c2931c4e
Binary files /dev/null and b/assets/images/flags/hmd.png differ
diff --git a/assets/images/flags/iot.png b/assets/images/flags/iot.png
new file mode 100644
index 000000000..2863320f5
Binary files /dev/null and b/assets/images/flags/iot.png differ
diff --git a/assets/images/flags/irl.png b/assets/images/flags/irl.png
new file mode 100644
index 000000000..2126054d3
Binary files /dev/null and b/assets/images/flags/irl.png differ
diff --git a/assets/images/flags/jam.png b/assets/images/flags/jam.png
new file mode 100644
index 000000000..97bce2de3
Binary files /dev/null and b/assets/images/flags/jam.png differ
diff --git a/assets/images/flags/jey.png b/assets/images/flags/jey.png
new file mode 100644
index 000000000..e144d060e
Binary files /dev/null and b/assets/images/flags/jey.png differ
diff --git a/assets/images/flags/jor.png b/assets/images/flags/jor.png
new file mode 100644
index 000000000..6e5d2bbb8
Binary files /dev/null and b/assets/images/flags/jor.png differ
diff --git a/assets/images/flags/kaz.png b/assets/images/flags/kaz.png
new file mode 100644
index 000000000..db52cf078
Binary files /dev/null and b/assets/images/flags/kaz.png differ
diff --git a/assets/images/flags/ken.png b/assets/images/flags/ken.png
new file mode 100644
index 000000000..2570c185d
Binary files /dev/null and b/assets/images/flags/ken.png differ
diff --git a/assets/images/flags/kir.png b/assets/images/flags/kir.png
new file mode 100644
index 000000000..c8b69d702
Binary files /dev/null and b/assets/images/flags/kir.png differ
diff --git a/assets/images/flags/kwt.png b/assets/images/flags/kwt.png
new file mode 100644
index 000000000..f21563b5f
Binary files /dev/null and b/assets/images/flags/kwt.png differ
diff --git a/assets/images/flags/lbn.png b/assets/images/flags/lbn.png
new file mode 100644
index 000000000..2a9ae1dd0
Binary files /dev/null and b/assets/images/flags/lbn.png differ
diff --git a/assets/images/flags/lie.png b/assets/images/flags/lie.png
new file mode 100644
index 000000000..0b8c77442
Binary files /dev/null and b/assets/images/flags/lie.png differ
diff --git a/assets/images/flags/lka.png b/assets/images/flags/lka.png
new file mode 100644
index 000000000..2b6492daa
Binary files /dev/null and b/assets/images/flags/lka.png differ
diff --git a/assets/images/flags/ltu.png b/assets/images/flags/ltu.png
new file mode 100644
index 000000000..dec6babea
Binary files /dev/null and b/assets/images/flags/ltu.png differ
diff --git a/assets/images/flags/lux.png b/assets/images/flags/lux.png
new file mode 100644
index 000000000..9cc1c65b5
Binary files /dev/null and b/assets/images/flags/lux.png differ
diff --git a/assets/images/flags/lva.png b/assets/images/flags/lva.png
new file mode 100644
index 000000000..b3312700d
Binary files /dev/null and b/assets/images/flags/lva.png differ
diff --git a/assets/images/flags/mco.png b/assets/images/flags/mco.png
new file mode 100644
index 000000000..6c12bd624
Binary files /dev/null and b/assets/images/flags/mco.png differ
diff --git a/assets/images/flags/mlt.png b/assets/images/flags/mlt.png
new file mode 100644
index 000000000..12809815f
Binary files /dev/null and b/assets/images/flags/mlt.png differ
diff --git a/assets/images/flags/mnp.png b/assets/images/flags/mnp.png
new file mode 100644
index 000000000..3e6c538e4
Binary files /dev/null and b/assets/images/flags/mnp.png differ
diff --git a/assets/images/flags/mrt.png b/assets/images/flags/mrt.png
new file mode 100644
index 000000000..0fd8d757e
Binary files /dev/null and b/assets/images/flags/mrt.png differ
diff --git a/assets/images/flags/msr.png b/assets/images/flags/msr.png
new file mode 100644
index 000000000..2d2af3aef
Binary files /dev/null and b/assets/images/flags/msr.png differ
diff --git a/assets/images/flags/mtq.png b/assets/images/flags/mtq.png
new file mode 100644
index 000000000..1897c94e7
Binary files /dev/null and b/assets/images/flags/mtq.png differ
diff --git a/assets/images/flags/mwi.png b/assets/images/flags/mwi.png
new file mode 100644
index 000000000..7ddfbb17b
Binary files /dev/null and b/assets/images/flags/mwi.png differ
diff --git a/assets/images/flags/myt.png b/assets/images/flags/myt.png
new file mode 100644
index 000000000..c149a2a79
Binary files /dev/null and b/assets/images/flags/myt.png differ
diff --git a/assets/images/flags/ner.png b/assets/images/flags/ner.png
new file mode 100644
index 000000000..87bb8211f
Binary files /dev/null and b/assets/images/flags/ner.png differ
diff --git a/assets/images/flags/nfk.png b/assets/images/flags/nfk.png
new file mode 100644
index 000000000..6e68aee4f
Binary files /dev/null and b/assets/images/flags/nfk.png differ
diff --git a/assets/images/flags/niu.png b/assets/images/flags/niu.png
new file mode 100644
index 000000000..acb36780d
Binary files /dev/null and b/assets/images/flags/niu.png differ
diff --git a/assets/images/flags/omn.png b/assets/images/flags/omn.png
new file mode 100644
index 000000000..6b6cd8b3b
Binary files /dev/null and b/assets/images/flags/omn.png differ
diff --git a/assets/images/flags/per.png b/assets/images/flags/per.png
new file mode 100644
index 000000000..490a26441
Binary files /dev/null and b/assets/images/flags/per.png differ
diff --git a/assets/images/flags/plw.png b/assets/images/flags/plw.png
new file mode 100644
index 000000000..6f6ff993a
Binary files /dev/null and b/assets/images/flags/plw.png differ
diff --git a/assets/images/flags/pri.png b/assets/images/flags/pri.png
new file mode 100644
index 000000000..cb0c54cd6
Binary files /dev/null and b/assets/images/flags/pri.png differ
diff --git a/assets/images/flags/pyf.png b/assets/images/flags/pyf.png
new file mode 100644
index 000000000..66a5da6b8
Binary files /dev/null and b/assets/images/flags/pyf.png differ
diff --git a/assets/images/flags/qat.png b/assets/images/flags/qat.png
new file mode 100644
index 000000000..1e8461e91
Binary files /dev/null and b/assets/images/flags/qat.png differ
diff --git a/assets/images/flags/slb.png b/assets/images/flags/slb.png
new file mode 100644
index 000000000..d63061a0b
Binary files /dev/null and b/assets/images/flags/slb.png differ
diff --git a/assets/images/flags/slv.png b/assets/images/flags/slv.png
new file mode 100644
index 000000000..e597e45b3
Binary files /dev/null and b/assets/images/flags/slv.png differ
diff --git a/assets/images/flags/svk.png b/assets/images/flags/svk.png
new file mode 100644
index 000000000..06bed756e
Binary files /dev/null and b/assets/images/flags/svk.png differ
diff --git a/assets/images/flags/svn.png b/assets/images/flags/svn.png
new file mode 100644
index 000000000..a791163bd
Binary files /dev/null and b/assets/images/flags/svn.png differ
diff --git a/assets/images/flags/tkm.png b/assets/images/flags/tkm.png
new file mode 100644
index 000000000..0c3ff8755
Binary files /dev/null and b/assets/images/flags/tkm.png differ
diff --git a/assets/images/flags/ton.png b/assets/images/flags/ton.png
new file mode 100644
index 000000000..84cf20ef5
Binary files /dev/null and b/assets/images/flags/ton.png differ
diff --git a/assets/images/flags/tuv.png b/assets/images/flags/tuv.png
new file mode 100644
index 000000000..15478f191
Binary files /dev/null and b/assets/images/flags/tuv.png differ
diff --git a/assets/images/flags/ury.png b/assets/images/flags/ury.png
new file mode 100644
index 000000000..c41e2780a
Binary files /dev/null and b/assets/images/flags/ury.png differ
diff --git a/assets/images/flags/vat.png b/assets/images/flags/vat.png
new file mode 100644
index 000000000..d6c99cc1f
Binary files /dev/null and b/assets/images/flags/vat.png differ
diff --git a/assets/images/flags/vir.png b/assets/images/flags/vir.png
new file mode 100644
index 000000000..a57f924b2
Binary files /dev/null and b/assets/images/flags/vir.png differ
diff --git a/assets/images/flags/vut.png b/assets/images/flags/vut.png
new file mode 100644
index 000000000..3c4d6e429
Binary files /dev/null and b/assets/images/flags/vut.png differ
diff --git a/build-guide-linux.md b/build-guide-linux.md
index 50ecc76fe..55264fbfa 100644
--- a/build-guide-linux.md
+++ b/build-guide-linux.md
@@ -55,7 +55,7 @@ Need to install flutter. For this please check section [How to install flutter o
### 3. Verify Installations
-Verify that the Flutter have been correctly installed on your system with the following command:
+Verify that the Flutter has been correctly installed on your system with the following command:
`$ flutter doctor`
@@ -163,7 +163,7 @@ And then export bundle:
`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet`
-Result file: `cake_wallet.flatpak` should be generated in current directory.
+Result file: `cake_wallet.flatpak` should be generated in the current directory.
For install generated flatpak file use:
diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index a18c038fa..df8e14119 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -124,6 +124,7 @@ class ElectrumClient {
final errorMsg = error.toString();
print(errorMsg);
unterminatedString = '';
+ socket = null;
},
onDone: () {
print("SOCKET CLOSED!!!!!");
@@ -132,6 +133,7 @@ class ElectrumClient {
if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy();
+ socket = null;
}
} catch (e) {
print("onDone: $e");
diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart
index acf4c4277..31ba653cb 100644
--- a/cw_bitcoin/lib/electrum_balance.dart
+++ b/cw_bitcoin/lib/electrum_balance.dart
@@ -24,9 +24,12 @@ class ElectrumBalance extends Balance {
final decoded = json.decode(jsonSource) as Map;
return ElectrumBalance(
- confirmed: decoded['confirmed'] as int? ?? 0,
- unconfirmed: decoded['unconfirmed'] as int? ?? 0,
- frozen: decoded['frozen'] as int? ?? 0);
+ confirmed: decoded['confirmed'] as int? ?? 0,
+ unconfirmed: decoded['unconfirmed'] as int? ?? 0,
+ frozen: decoded['frozen'] as int? ?? 0,
+ secondConfirmed: decoded['secondConfirmed'] as int? ?? 0,
+ secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0,
+ );
}
int confirmed;
diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart
index 1ab7799e3..7a8b3b951 100644
--- a/cw_bitcoin/lib/electrum_transaction_info.dart
+++ b/cw_bitcoin/lib/electrum_transaction_info.dart
@@ -41,6 +41,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
String? to,
this.unspents,
this.isReceivedSilentPayment = false,
+ Map? additionalInfo,
}) {
this.id = id;
this.height = height;
@@ -54,6 +55,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.isReplaced = isReplaced;
this.confirmations = confirmations;
this.to = to;
+ this.additionalInfo = additionalInfo ?? {};
}
factory ElectrumTransactionInfo.fromElectrumVerbose(Map obj, WalletType type,
@@ -212,6 +214,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map))
.toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
+ additionalInfo: data['additionalInfo'] as Map?,
);
}
@@ -246,7 +249,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
isReplaced: isReplaced ?? false,
inputAddresses: inputAddresses,
outputAddresses: outputAddresses,
- confirmations: info.confirmations);
+ confirmations: info.confirmations,
+ additionalInfo: additionalInfo);
}
Map toJson() {
@@ -265,10 +269,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses;
m['isReceivedSilentPayment'] = isReceivedSilentPayment;
+ m['additionalInfo'] = additionalInfo;
return m;
}
String toString() {
- return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses)';
+ return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
}
}
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 2773475ee..f56b7c66a 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -5,6 +5,7 @@ import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
+import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart';
@@ -52,10 +53,9 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
-abstract class ElectrumWalletBase extends WalletBase<
- ElectrumBalance,
- ElectrumTransactionHistory,
- ElectrumTransactionInfo> with Store, WalletKeysFile {
+abstract class ElectrumWalletBase
+ extends WalletBase
+ with Store, WalletKeysFile {
ElectrumWalletBase({
required String password,
required WalletInfo walletInfo,
@@ -71,8 +71,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
this.alwaysScan,
- }) : accountHD = getAccountHDWallet(
- currency, network, seedBytes, xpub, walletInfo.derivationInfo),
+ }) : accountHD =
+ getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = [],
@@ -107,12 +107,8 @@ abstract class ElectrumWalletBase extends WalletBase<
sharedPrefs.complete(SharedPreferences.getInstance());
}
- static Bip32Slip10Secp256k1 getAccountHDWallet(
- CryptoCurrency? currency,
- BasedUtxoNetwork network,
- Uint8List? seedBytes,
- String? xpub,
- DerivationInfo? derivationInfo) {
+ static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
+ Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) {
throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen");
@@ -123,9 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase<
case CryptoCurrency.btc:
case CryptoCurrency.ltc:
case CryptoCurrency.tbtc:
- return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network))
- .derivePath(_hardenedDerivationPath(
- derivationInfo?.derivationPath ?? electrum_path))
+ return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
+ _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1;
case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes);
@@ -134,13 +129,11 @@ abstract class ElectrumWalletBase extends WalletBase<
}
}
- return Bip32Slip10Secp256k1.fromExtendedKey(
- xpub!, getKeyNetVersion(network));
+ return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
- Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'")
- as Bip32Slip10Secp256k1;
+ Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
@@ -250,7 +243,7 @@ abstract class ElectrumWalletBase extends WalletBase<
}
if (tip > walletInfo.restoreHeight) {
- _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
+ _setListeners(walletInfo.restoreHeight, chainTipParam: currentChainTip);
}
} else {
alwaysScan = false;
@@ -265,23 +258,23 @@ abstract class ElectrumWalletBase extends WalletBase<
}
}
- int? _currentChainTip;
+ int? currentChainTip;
Future getCurrentChainTip() async {
- if ((_currentChainTip ?? 0) > 0) {
- return _currentChainTip!;
+ if ((currentChainTip ?? 0) > 0) {
+ return currentChainTip!;
}
- _currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
+ currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
- return _currentChainTip!;
+ return currentChainTip!;
}
Future getUpdatedChainTip() async {
final newTip = await electrumClient.getCurrentBlockChainTip();
- if (newTip != null && newTip > (_currentChainTip ?? 0)) {
- _currentChainTip = newTip;
+ if (newTip != null && newTip > (currentChainTip ?? 0)) {
+ currentChainTip = newTip;
}
- return _currentChainTip ?? 0;
+ return currentChainTip ?? 0;
}
@override
@@ -357,7 +350,7 @@ abstract class ElectrumWalletBase extends WalletBase<
isSingleScan: doSingleScan ?? false,
));
- _receiveStream?.cancel();
+ await _receiveStream?.cancel();
_receiveStream = receivePort.listen((var message) async {
if (message is Map) {
for (final map in message.entries) {
@@ -618,7 +611,7 @@ abstract class ElectrumWalletBase extends WalletBase<
bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount;
- final availableInputs = unspentCoins.where((utx) {
+ var availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) {
return false;
}
@@ -634,6 +627,9 @@ abstract class ElectrumWalletBase extends WalletBase<
}).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
+ // sort the unconfirmed coins so that mweb coins are first:
+ availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
+
for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i];
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
@@ -652,9 +648,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ECPrivate? privkey;
bool? isSilentPayment = false;
- final hd = utx.bitcoinAddressRecord.isHidden
- ? walletAddresses.sideHd
- : walletAddresses.mainHd;
+ final hd =
+ utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@@ -1233,8 +1228,7 @@ abstract class ElectrumWalletBase extends WalletBase<
}
}
- void setLedgerConnection(ledger.LedgerConnection connection) =>
- throw UnimplementedError();
+ void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
Future buildHardwareWalletTransaction({
required List outputs,
@@ -1593,9 +1587,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate(
- hd: addressRecord.isHidden
- ? walletAddresses.sideHd
- : walletAddresses.mainHd,
+ hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: addressRecord.index,
network: network);
@@ -1777,8 +1769,7 @@ abstract class ElectrumWalletBase extends WalletBase<
if (height != null) {
if (time == null && height > 0) {
- time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000)
- .round();
+ time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
}
if (confirmations == null) {
@@ -1847,6 +1838,7 @@ abstract class ElectrumWalletBase extends WalletBase<
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES
+ .where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
}
@@ -1958,6 +1950,20 @@ abstract class ElectrumWalletBase extends WalletBase<
// Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster
+
+ if (this is LitecoinWallet) {
+ // if we have a peg out transaction with the same value
+ // that matches this received transaction, mark it as being from a peg out:
+ for (final tx2 in transactionHistory.transactions.values) {
+ final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs();
+ // this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other
+ if (tx2.additionalInfo["isPegOut"] == true &&
+ tx2.amount == tx.amount &&
+ heightDiff <= 5) {
+ tx.additionalInfo["fromPegOut"] = true;
+ }
+ }
+ }
transactionHistory.addOne(tx);
await transactionHistory.save();
}
@@ -1984,18 +1990,28 @@ abstract class ElectrumWalletBase extends WalletBase<
if (_isTransactionUpdating) {
return;
}
- await getCurrentChainTip();
+ currentChainTip = await getUpdatedChainTip();
+ bool updated = false;
transactionHistory.transactions.values.forEach((tx) {
- if (tx.unspents != null &&
- tx.unspents!.isNotEmpty &&
- tx.height != null &&
- tx.height! > 0 &&
- (_currentChainTip ?? 0) > 0) {
- tx.confirmations = _currentChainTip! - tx.height! + 1;
+ if ((tx.height ?? 0) > 0 && (currentChainTip ?? 0) > 0) {
+ var confirmations = currentChainTip! - tx.height! + 1;
+ if (confirmations < 0) {
+ // if our chain tip is outdated then it could lead to negative confirmations so this is just a failsafe:
+ confirmations = 0;
+ }
+ if (confirmations != tx.confirmations) {
+ updated = true;
+ tx.confirmations = confirmations;
+ transactionHistory.addOne(tx);
+ }
}
});
+ if (updated) {
+ await transactionHistory.save();
+ }
+
_isTransactionUpdating = true;
await fetchTransactions();
walletAddresses.updateReceiveAddresses();
@@ -2043,6 +2059,8 @@ abstract class ElectrumWalletBase extends WalletBase<
library: this.runtimeType.toString(),
));
}
+ }, onError: (e, s) {
+ print("sub_listen error: $e $s");
});
}));
}
@@ -2106,6 +2124,13 @@ abstract class ElectrumWalletBase extends WalletBase<
final balances = await Future.wait(balanceFutures);
+ if (balances.isNotEmpty && balances.first['confirmed'] == null) {
+ // if we got null balance responses from the server, set our connection status to lost and return our last known balance:
+ print("got null balance responses from the server, setting connection status to lost");
+ syncStatus = LostConnectionSyncStatus();
+ return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
+ }
+
for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i];
final balance = balances[i];
@@ -2211,10 +2236,10 @@ abstract class ElectrumWalletBase extends WalletBase<
Future _setInitialHeight() async {
if (_chainTipUpdateSubject != null) return;
- _currentChainTip = await getUpdatedChainTip();
+ currentChainTip = await getUpdatedChainTip();
- if ((_currentChainTip == null || _currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
- await walletInfo.updateRestoreHeight(_currentChainTip!);
+ if ((currentChainTip == null || currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
+ await walletInfo.updateRestoreHeight(currentChainTip!);
}
_chainTipUpdateSubject = electrumClient.chainTipSubscribe();
@@ -2223,7 +2248,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final height = int.tryParse(event['height'].toString());
if (height != null) {
- _currentChainTip = height;
+ currentChainTip = height;
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
_setListeners(walletInfo.restoreHeight);
@@ -2237,7 +2262,6 @@ abstract class ElectrumWalletBase extends WalletBase<
@action
void _onConnectionStatusChange(ConnectionStatus status) {
-
switch (status) {
case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus ||
diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart
index 2686d12cc..c29579436 100644
--- a/cw_bitcoin/lib/electrum_wallet_addresses.dart
+++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart
@@ -331,7 +331,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
);
silentAddresses.add(address);
- updateAddressesByMatch();
+ Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
@@ -348,7 +348,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
network: network,
);
_addresses.add(address);
- updateAddressesByMatch();
+ Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 1fb39c878..86228fc83 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -9,6 +9,7 @@ import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart';
+import 'package:cw_core/node.dart';
import 'package:cw_mweb/mwebd.pbgrpc.dart';
import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39;
@@ -47,6 +48,7 @@ import 'package:cw_mweb/cw_mweb.dart';
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart';
+import 'package:shared_preferences/shared_preferences.dart';
part 'litecoin_wallet.g.dart';
@@ -85,8 +87,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
alwaysScan: alwaysScan,
) {
if (seedBytes != null) {
- mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
- "m/1000'") as Bip32Slip10Secp256k1;
+ mwebHd =
+ Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false;
} else {
mwebHd = null;
@@ -287,6 +289,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
}
+ @action
+ @override
+ Future connectToNode({required Node node}) async {
+ await super.connectToNode(node: node);
+
+ final prefs = await SharedPreferences.getInstance();
+ final mwebNodeUri = prefs.getString("mwebNodeUri") ?? "ltc-electrum.cakewallet.com:9333";
+ await CwMweb.setNodeUriOverride(mwebNodeUri);
+ }
+
@action
@override
Future startSync() async {
@@ -349,6 +361,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return;
}
+ // update the current chain tip so that confirmation calculations are accurate:
+ currentChainTip = nodeHeight;
+
final resp = await CwMweb.status(StatusRequest());
try {
@@ -361,22 +376,46 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} else if (resp.mwebUtxosHeight < nodeHeight) {
mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else {
+ bool confirmationsUpdated = false;
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
await checkMwebUtxosSpent();
// update the confirmations for each transaction:
- for (final transaction in transactionHistory.transactions.values) {
- if (transaction.isPending) continue;
- int txHeight = transaction.height ?? resp.mwebUtxosHeight;
- final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
- if (transaction.confirmations == confirmations) continue;
- if (transaction.confirmations == 0) {
- updateBalance();
+ for (final tx in transactionHistory.transactions.values) {
+ if (tx.height == null || tx.height == 0) {
+ // update with first confirmation on next block since it hasn't been confirmed yet:
+ tx.height = resp.mwebUtxosHeight;
+ continue;
}
- transaction.confirmations = confirmations;
- transactionHistory.addOne(transaction);
+
+ final confirmations = (resp.mwebUtxosHeight - tx.height!) + 1;
+
+ // if the confirmations haven't changed, skip updating:
+ if (tx.confirmations == confirmations) continue;
+
+
+ // if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin):
+ if (confirmations >= 2 &&
+ tx.direction == TransactionDirection.outgoing &&
+ tx.unspents != null) {
+ for (var coin in tx.unspents!) {
+ final utxo = mwebUtxosBox.get(coin.address);
+ if (utxo != null) {
+ print("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ await mwebUtxosBox.delete(coin.address);
+ }
+ }
+ }
+
+ tx.confirmations = confirmations;
+ tx.isPending = false;
+ transactionHistory.addOne(tx);
+ confirmationsUpdated = true;
+ }
+ if (confirmationsUpdated) {
+ await transactionHistory.save();
+ await updateTransactions();
}
- await transactionHistory.save();
}
// prevent unnecessary reaction triggers:
@@ -501,13 +540,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
outputAddresses: [utxo.outputId],
isReplaced: false,
);
- }
-
- // don't update the confirmations if the tx is updated by electrum:
- if (tx.confirmations == 0 || utxo.height != 0) {
- tx.height = utxo.height;
- tx.isPending = utxo.height == 0;
- tx.confirmations = confirmations;
+ } else {
+ if (tx.confirmations != confirmations || tx.height != utxo.height) {
+ tx.height = utxo.height;
+ tx.confirmations = confirmations;
+ tx.isPending = utxo.height == 0;
+ }
}
bool isNew = transactionHistory.transactions[tx.id] == null;
@@ -557,56 +595,88 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (responseStream == null) {
throw Exception("failed to get utxos stream!");
}
- _utxoStream = responseStream.listen((Utxo sUtxo) async {
- // we're processing utxos, so our balance could still be innacurate:
- if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
- mwebSyncStatus = SyncronizingSyncStatus();
- processingUtxos = true;
- _processingTimer?.cancel();
- _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
- processingUtxos = false;
- timer.cancel();
- });
- }
-
- final utxo = MwebUtxo(
- address: sUtxo.address,
- blockTime: sUtxo.blockTime,
- height: sUtxo.height,
- outputId: sUtxo.outputId,
- value: sUtxo.value.toInt(),
- );
-
- if (mwebUtxosBox.containsKey(utxo.outputId)) {
- // we've already stored this utxo, skip it:
- // but do update the utxo height if it's somehow different:
- final existingUtxo = mwebUtxosBox.get(utxo.outputId);
- if (existingUtxo!.height != utxo.height) {
- print(
- "updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
- existingUtxo.height = utxo.height;
- await mwebUtxosBox.put(utxo.outputId, existingUtxo);
+ _utxoStream = responseStream.listen(
+ (Utxo sUtxo) async {
+ // we're processing utxos, so our balance could still be innacurate:
+ if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
+ mwebSyncStatus = SyncronizingSyncStatus();
+ processingUtxos = true;
+ _processingTimer?.cancel();
+ _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
+ processingUtxos = false;
+ timer.cancel();
+ });
}
- return;
- }
- await updateUnspent();
- await updateBalance();
+ final utxo = MwebUtxo(
+ address: sUtxo.address,
+ blockTime: sUtxo.blockTime,
+ height: sUtxo.height,
+ outputId: sUtxo.outputId,
+ value: sUtxo.value.toInt(),
+ );
- final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
+ if (mwebUtxosBox.containsKey(utxo.outputId)) {
+ // we've already stored this utxo, skip it:
+ // but do update the utxo height if it's somehow different:
+ final existingUtxo = mwebUtxosBox.get(utxo.outputId);
+ if (existingUtxo!.height != utxo.height) {
+ print(
+ "updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
+ existingUtxo.height = utxo.height;
+ await mwebUtxosBox.put(utxo.outputId, existingUtxo);
+ }
+ return;
+ }
- // don't process utxos with addresses that are not in the mwebAddrs list:
- if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
- return;
- }
+ await updateUnspent();
+ await updateBalance();
- await mwebUtxosBox.put(utxo.outputId, utxo);
+ final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
- await handleIncoming(utxo);
- });
+ // don't process utxos with addresses that are not in the mwebAddrs list:
+ if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
+ return;
+ }
+
+ await mwebUtxosBox.put(utxo.outputId, utxo);
+
+ await handleIncoming(utxo);
+ },
+ onError: (error) {
+ print("error in utxo stream: $error");
+ mwebSyncStatus = FailedSyncStatus(error: error.toString());
+ },
+ cancelOnError: true,
+ );
+ }
+
+ Future deleteSpentUtxos() async {
+ print("deleteSpentUtxos() called!");
+ final chainHeight = await electrumClient.getCurrentBlockChainTip();
+ final status = await CwMweb.status(StatusRequest());
+ if (chainHeight == null || status.blockHeaderHeight != chainHeight) return;
+ if (status.mwebUtxosHeight != chainHeight) return; // we aren't synced
+
+ // delete any spent utxos with >= 2 confirmations:
+ final spentOutputIds = mwebUtxosBox.values
+ .where((utxo) => utxo.spent && (chainHeight - utxo.height) >= 2)
+ .map((utxo) => utxo.outputId)
+ .toList();
+
+ if (spentOutputIds.isEmpty) return;
+
+ final resp = await CwMweb.spent(SpentRequest(outputId: spentOutputIds));
+ final spent = resp.outputId;
+ if (spent.isEmpty) return;
+
+ for (final outputId in spent) {
+ await mwebUtxosBox.delete(outputId);
+ }
}
Future checkMwebUtxosSpent() async {
+ print("checkMwebUtxosSpent() called!");
if (!mwebEnabled) {
return;
}
@@ -620,15 +690,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
updatedAny = await isConfirmed(tx) || updatedAny;
}
+ await deleteSpentUtxos();
+
// get output ids of all the mweb utxos that have > 0 height:
- final outputIds =
- mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList();
+ final outputIds = mwebUtxosBox.values
+ .where((utxo) => utxo.height > 0 && !utxo.spent)
+ .map((utxo) => utxo.outputId)
+ .toList();
final resp = await CwMweb.spent(SpentRequest(outputId: outputIds));
final spent = resp.outputId;
- if (spent.isEmpty) {
- return;
- }
+ if (spent.isEmpty) return;
final status = await CwMweb.status(StatusRequest());
final height = await electrumClient.getCurrentBlockChainTip();
@@ -739,7 +811,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mwebUtxosBox.keys.forEach((dynamic oId) {
final String outputId = oId as String;
final utxo = mwebUtxosBox.get(outputId);
- if (utxo == null) {
+ if (utxo == null || utxo.spent) {
return;
}
if (utxo.address.isEmpty) {
@@ -789,15 +861,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int unconfirmedMweb = 0;
try {
mwebUtxosBox.values.forEach((utxo) {
- if (utxo.height > 0) {
+ bool isConfirmed = utxo.height > 0;
+
+ print(
+ "utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}");
+
+ if (isConfirmed) {
confirmedMweb += utxo.value.toInt();
- } else {
+ }
+
+ if (isConfirmed && utxo.spent) {
+ unconfirmedMweb -= utxo.value.toInt();
+ }
+
+ if (!isConfirmed && !utxo.spent) {
unconfirmedMweb += utxo.value.toInt();
}
});
- if (unconfirmedMweb > 0) {
- unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
- }
} catch (_) {}
for (var addressRecord in walletAddresses.allAddresses) {
@@ -829,7 +909,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// update the txCount for each address using the tx history, since we can't rely on mwebd
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
for (final tx in transactionHistory.transactions.values) {
- // if (tx.isPending) continue;
if (tx.inputAddresses == null || tx.outputAddresses == null) {
continue;
}
@@ -908,7 +987,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
final preOutputSum =
outputs.fold(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
- final fee = utxos.sumOfUtxosValue() - preOutputSum;
+ var fee = utxos.sumOfUtxosValue() - preOutputSum;
+
+ // determines if the fee is correct:
+ BigInt _sumOutputAmounts(List outputs) {
+ BigInt sum = BigInt.zero;
+ for (final e in outputs) {
+ sum += e.amount;
+ }
+ return sum;
+ }
+
+ final sum1 = _sumOutputAmounts(outputs.map((e) => e.toOutput).toList()) + fee;
+ final sum2 = utxos.sumOfUtxosValue();
+ if (sum1 != sum2) {
+ print("@@@@@ WE HAD TO ADJUST THE FEE! @@@@@@@@");
+ final diff = sum2 - sum1;
+ // add the difference to the fee (abs value):
+ fee += diff.abs();
+ }
+
final txb =
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
final resp = await CwMweb.create(CreateRequest(
@@ -949,8 +1047,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) {
tx.changeAddressOverride =
- (await (walletAddresses as LitecoinWalletAddresses)
- .getChangeAddress(isPegIn: false))
+ (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
.address;
return tx;
}
@@ -969,15 +1066,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool hasMwebInput = false;
bool hasMwebOutput = false;
+ bool hasRegularOutput = false;
for (final output in transactionCredentials.outputs) {
- if (output.extractedAddress?.toLowerCase().contains("mweb") ?? false) {
+ final address = output.address.toLowerCase();
+ final extractedAddress = output.extractedAddress?.toLowerCase();
+
+ if (address.contains("mweb")) {
hasMwebOutput = true;
- break;
}
- if (output.address.toLowerCase().contains("mweb")) {
- hasMwebOutput = true;
- break;
+ if (!address.contains("mweb")) {
+ hasRegularOutput = true;
+ }
+ if (extractedAddress != null && extractedAddress.isNotEmpty) {
+ if (extractedAddress.contains("mweb")) {
+ hasMwebOutput = true;
+ }
+ if (!extractedAddress.contains("mweb")) {
+ hasRegularOutput = true;
+ }
}
}
@@ -989,11 +1096,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
bool isPegIn = !hasMwebInput && hasMwebOutput;
+ bool isPegOut = hasMwebInput && hasRegularOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput;
- tx.changeAddressOverride =
- (await (walletAddresses as LitecoinWalletAddresses)
- .getChangeAddress(isPegIn: isPegIn || isRegular))
- .address;
+ tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
+ .getChangeAddress(isPegIn: isPegIn || isRegular))
+ .address;
if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx;
@@ -1046,8 +1153,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = {};
transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id);
- await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
+ // await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return;
+ // mark utxo as spent so we add it to the unconfirmed balance (as negative):
+ utxo.spent = true;
+ await mwebUtxosBox.put(id, utxo);
final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
if (!addresses.contains(utxo.address)) {
@@ -1056,7 +1166,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
addressRecord.balance -= utxo.value.toInt();
});
transaction.inputAddresses?.addAll(addresses);
-
+ print("isPegIn: $isPegIn, isPegOut: $isPegOut");
+ transaction.additionalInfo["isPegIn"] = isPegIn;
+ transaction.additionalInfo["isPegOut"] = isPegOut;
transactionHistory.addOne(transaction);
await updateUnspent();
await updateBalance();
@@ -1240,8 +1352,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection;
- _litecoinLedgerApp =
- LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
+ _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
+ derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@@ -1277,19 +1389,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
}
-
final rawHex = await _litecoinLedgerApp!.createTransaction(
- inputs: readyInputs,
- outputs: outputs
- .map((e) => TransactionOutput.fromBigInt(
- (e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
- .toList(),
- changePath: changePath,
- sigHashType: 0x01,
- additionals: ["bech32"],
- isSegWit: true,
- useTrustedInputForSegwit: true
- );
+ inputs: readyInputs,
+ outputs: outputs
+ .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
+ Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
+ .toList(),
+ changePath: changePath,
+ sigHashType: 0x01,
+ additionals: ["bech32"],
+ isSegWit: true,
+ useTrustedInputForSegwit: true);
return BtcTransaction.fromRaw(rawHex);
}
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index c55f5fc76..062c590ba 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -16,11 +16,9 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
-class LitecoinWalletAddresses = LitecoinWalletAddressesBase
- with _$LitecoinWalletAddresses;
+class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
-abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
- with Store {
+abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.mainHd,
@@ -46,8 +44,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
List mwebAddrs = [];
bool generating = false;
- List get scanSecret =>
- mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
+ List get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List get spendPubkey =>
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@@ -203,4 +200,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
return super.getChangeAddress();
}
+
+ @override
+ String get addressForExchange {
+ // don't use mweb addresses for exchange refund address:
+ final addresses = receiveAddresses
+ .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed);
+ return addresses.first.address;
+ }
}
diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart
index 7cc266f5b..d519f4d0a 100644
--- a/cw_bitcoin/lib/litecoin_wallet_service.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_service.dart
@@ -112,6 +112,7 @@ class LitecoinWalletService extends WalletService<
File neturinoDb = File('$appDirPath/neutrino.db');
File blockHeaders = File('$appDirPath/block_headers.bin');
File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin');
+ File mwebdLogs = File('$appDirPath/logs/debug.log');
if (neturinoDb.existsSync()) {
neturinoDb.deleteSync();
}
@@ -121,6 +122,9 @@ class LitecoinWalletService extends WalletService<
if (regFilterHeaders.existsSync()) {
regFilterHeaders.deleteSync();
}
+ if (mwebdLogs.existsSync()) {
+ mwebdLogs.deleteSync();
+ }
}
}
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index 5ed84dbf4..140965655 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -118,8 +118,7 @@ class PendingBitcoinTransaction with PendingTransaction {
Future _ltcCommit() async {
try {
- final stub = await CwMweb.stub();
- final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
+ final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
idOverride = resp.txid;
} on GrpcError catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 9f1cee67d..bff6104ac 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -64,7 +64,7 @@ dependency_overrides:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base
- ref: cake-update-v8
+ ref: cake-update-v9
pointycastle: 3.7.4
ffi: 2.1.0
@@ -73,6 +73,7 @@ dependency_overrides:
# The following section is specific to Flutter.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml
index cd1e52f51..9a5c4f14f 100644
--- a/cw_bitcoin_cash/pubspec.yaml
+++ b/cw_bitcoin_cash/pubspec.yaml
@@ -42,13 +42,14 @@ dependency_overrides:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base
- ref: cake-update-v8
+ ref: cake-update-v9
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_core/lib/mweb_utxo.dart b/cw_core/lib/mweb_utxo.dart
index f8dfab395..5eab11734 100644
--- a/cw_core/lib/mweb_utxo.dart
+++ b/cw_core/lib/mweb_utxo.dart
@@ -11,6 +11,7 @@ class MwebUtxo extends HiveObject {
required this.address,
required this.outputId,
required this.blockTime,
+ this.spent = false,
});
static const typeId = MWEB_UTXO_TYPE_ID;
@@ -30,4 +31,7 @@ class MwebUtxo extends HiveObject {
@HiveField(4)
int blockTime;
+
+ @HiveField(5, defaultValue: false)
+ bool spent;
}
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index e19d2a54b..1094b6402 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -79,6 +79,9 @@ class Node extends HiveObject with Keyable {
@HiveField(9)
bool? supportsSilentPayments;
+ @HiveField(10)
+ bool? supportsMweb;
+
bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart
index 9d0c968d8..3e34af75f 100644
--- a/cw_core/lib/transaction_info.dart
+++ b/cw_core/lib/transaction_info.dart
@@ -25,6 +25,5 @@ abstract class TransactionInfo extends Object with Keyable {
@override
dynamic get keyIndex => id;
- late Map additionalInfo;
+ Map additionalInfo = {};
}
-
diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml
index 070779caa..6e32c2ba1 100644
--- a/cw_core/pubspec.yaml
+++ b/cw_core/pubspec.yaml
@@ -47,6 +47,7 @@ dependency_overrides:
# The following section is specific to Flutter.
flutter:
+ uses-material-design: true
# To add assets to your package, add an assets section, like this:
# assets:
diff --git a/cw_haven/lib/haven_wallet_addresses.dart b/cw_haven/lib/haven_wallet_addresses.dart
index 06de44dff..c3d1ef46c 100644
--- a/cw_haven/lib/haven_wallet_addresses.dart
+++ b/cw_haven/lib/haven_wallet_addresses.dart
@@ -23,6 +23,8 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount address;
+
// @override
@observable
Account? account;
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
index a1a592fb8..75cc0bcca 100644
--- a/cw_mweb/lib/cw_mweb.dart
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -13,8 +13,18 @@ class CwMweb {
static RpcClient? _rpcClient;
static ClientChannel? _clientChannel;
static int? _port;
- static const TIMEOUT_DURATION = Duration(seconds: 5);
+ static const TIMEOUT_DURATION = Duration(seconds: 15);
static Timer? logTimer;
+ static String? nodeUriOverride;
+
+
+ static Future setNodeUriOverride(String uri) async {
+ nodeUriOverride = uri;
+ if (_rpcClient != null) {
+ await stop();
+ // will be re-started automatically when the next rpc call is made
+ }
+ }
static void readFileWithTimer(String filePath) {
final file = File(filePath);
@@ -47,7 +57,7 @@ class CwMweb {
String debugLogPath = "${appDir.path}/logs/debug.log";
readFileWithTimer(debugLogPath);
- _port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri);
+ _port = await CwMwebPlatform.instance.start(appDir.path, nodeUriOverride ?? ltcNodeUri);
if (_port == null || _port == 0) {
throw Exception("Failed to start server");
}
@@ -197,4 +207,18 @@ class CwMweb {
}
return null;
}
+
+ static Future broadcast(BroadcastRequest request) async {
+ log("mweb.broadcast() called");
+ try {
+ _rpcClient = await stub();
+ return await _rpcClient!.broadcast(request, options: CallOptions(timeout: TIMEOUT_DURATION));
+ } on GrpcError catch (e) {
+ log('Caught grpc error: ${e.message}');
+ throw "error from broadcast mweb: $e";
+ } catch (e) {
+ log("Error getting create: $e");
+ rethrow;
+ }
+ }
}
diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart
index 2e2991804..83bbb0449 100644
--- a/integration_test/components/common_test_cases.dart
+++ b/integration_test/components/common_test_cases.dart
@@ -10,10 +10,16 @@ class CommonTestCases {
hasType();
}
- Future tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async {
+ Future tapItemByKey(
+ String key, {
+ bool shouldPumpAndSettle = true,
+ int pumpDuration = 100,
+ }) async {
final widget = find.byKey(ValueKey(key));
await tester.tap(widget);
- shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
+ shouldPumpAndSettle
+ ? await tester.pumpAndSettle(Duration(milliseconds: pumpDuration))
+ : await tester.pump();
}
Future tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async {
@@ -31,6 +37,11 @@ class CommonTestCases {
expect(typeWidget, findsOneWidget);
}
+ bool isKeyPresent(String key) {
+ final typeWidget = find.byKey(ValueKey(key));
+ return typeWidget.tryEvaluate();
+ }
+
void hasValueKey(String key) {
final typeWidget = find.byKey(ValueKey(key));
expect(typeWidget, findsOneWidget);
@@ -53,33 +64,86 @@ class CommonTestCases {
await tester.pumpAndSettle();
}
- Future scrollUntilVisible(String childKey, String parentScrollableKey,
- {double delta = 300}) async {
- final scrollableWidget = find.descendant(
- of: find.byKey(Key(parentScrollableKey)),
+ Future dragUntilVisible(String childKey, String parentKey) async {
+ await tester.pumpAndSettle();
+
+ final itemFinder = find.byKey(ValueKey(childKey));
+ final listFinder = find.byKey(ValueKey(parentKey));
+
+ // Check if the widget is already in the widget tree
+ if (tester.any(itemFinder)) {
+ // Widget is already built and in the tree
+ tester.printToConsole('Child is already present');
+ return;
+ }
+
+ // We can adjust this as needed
+ final maxScrolls = 200;
+
+ int scrolls = 0;
+ bool found = false;
+
+ // We start by scrolling down
+ bool scrollDown = true;
+
+ // Flag to check if we've already reversed direction
+ bool reversedDirection = false;
+
+ // Find the Scrollable associated with the Parent Ad
+ final scrollableFinder = find.descendant(
+ of: listFinder,
matching: find.byType(Scrollable),
);
- final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey)));
-
- if (isAlreadyVisibile) return;
-
- await tester.scrollUntilVisible(
- find.byKey(ValueKey(childKey)),
- delta,
- scrollable: scrollableWidget,
+ // Ensure that the Scrollable is found
+ expect(
+ scrollableFinder,
+ findsOneWidget,
+ reason: 'Scrollable descendant of the Parent Widget not found.',
);
- }
- bool isWidgetVisible(Finder finder) {
- try {
- final Element element = finder.evaluate().single;
- final RenderBox renderBox = element.renderObject as RenderBox;
- return renderBox.paintBounds
- .shift(renderBox.localToGlobal(Offset.zero))
- .overlaps(tester.binding.renderViews.first.paintBounds);
- } catch (e) {
- return false;
+ // Get the initial scroll position
+ final scrollableState = tester.state(scrollableFinder);
+ double previousScrollPosition = scrollableState.position.pixels;
+
+ while (!found && scrolls < maxScrolls) {
+ tester.printToConsole('Scrolling ${scrollDown ? 'down' : 'up'}, attempt $scrolls');
+
+ // Perform the drag in the current direction
+ await tester.drag(
+ scrollableFinder,
+ scrollDown ? const Offset(0, -100) : const Offset(0, 100),
+ );
+ await tester.pumpAndSettle();
+ scrolls++;
+
+ // Update the scroll position after the drag
+ final currentScrollPosition = scrollableState.position.pixels;
+
+ if (currentScrollPosition == previousScrollPosition) {
+ // Cannot scroll further in this direction
+ if (reversedDirection) {
+ // We've already tried both directions
+ tester.printToConsole('Cannot scroll further in both directions. Widget not found.');
+ break;
+ } else {
+ // Reverse the scroll direction
+ scrollDown = !scrollDown;
+ reversedDirection = true;
+ tester.printToConsole('Reached the end, reversing direction');
+ }
+ } else {
+ // Continue scrolling in the current direction
+ previousScrollPosition = currentScrollPosition;
+ }
+
+ // Check if the widget is now in the widget tree
+ found = tester.any(itemFinder);
+ }
+
+ if (!found) {
+ tester.printToConsole('Widget not found after scrolling in both directions.');
+ return;
}
}
@@ -91,6 +155,15 @@ class CommonTestCases {
await tester.pumpAndSettle();
}
+ void findWidgetViaDescendant({
+ required FinderBase of,
+ required FinderBase matching,
+ }) {
+ final textWidget = find.descendant(of: of, matching: matching);
+
+ expect(textWidget, findsOneWidget);
+ }
+
Future defaultSleepTime({int seconds = 2}) async =>
await Future.delayed(Duration(seconds: seconds));
}
diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart
index d8381973e..302d52189 100644
--- a/integration_test/components/common_test_constants.dart
+++ b/integration_test/components/common_test_constants.dart
@@ -9,5 +9,5 @@ class CommonTestConstants {
static final String testWalletName = 'Integrated Testing Wallet';
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
- static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L';
+ static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm';
}
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index 807509de9..82f714da0 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -1,41 +1,65 @@
-import 'package:flutter/foundation.dart';
+import 'package:cake_wallet/entities/seed_type.dart';
+import 'package:cake_wallet/reactions/bip39_wallet_utils.dart';
+import 'package:cake_wallet/wallet_types.g.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/main.dart' as app;
+import '../robots/dashboard_page_robot.dart';
import '../robots/disclaimer_page_robot.dart';
+import '../robots/new_wallet_page_robot.dart';
import '../robots/new_wallet_type_page_robot.dart';
+import '../robots/pre_seed_page_robot.dart';
import '../robots/restore_from_seed_or_key_robot.dart';
import '../robots/restore_options_page_robot.dart';
import '../robots/setup_pin_code_robot.dart';
+import '../robots/wallet_group_description_page_robot.dart';
+import '../robots/wallet_list_page_robot.dart';
+import '../robots/wallet_seed_page_robot.dart';
import '../robots/welcome_page_robot.dart';
import 'common_test_cases.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+
import 'common_test_constants.dart';
class CommonTestFlows {
CommonTestFlows(this._tester)
: _commonTestCases = CommonTestCases(_tester),
_welcomePageRobot = WelcomePageRobot(_tester),
+ _preSeedPageRobot = PreSeedPageRobot(_tester),
_setupPinCodeRobot = SetupPinCodeRobot(_tester),
+ _dashboardPageRobot = DashboardPageRobot(_tester),
+ _newWalletPageRobot = NewWalletPageRobot(_tester),
_disclaimerPageRobot = DisclaimerPageRobot(_tester),
+ _walletSeedPageRobot = WalletSeedPageRobot(_tester),
+ _walletListPageRobot = WalletListPageRobot(_tester),
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
- _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester);
+ _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
+ _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
final WidgetTester _tester;
final CommonTestCases _commonTestCases;
final WelcomePageRobot _welcomePageRobot;
+ final PreSeedPageRobot _preSeedPageRobot;
final SetupPinCodeRobot _setupPinCodeRobot;
+ final NewWalletPageRobot _newWalletPageRobot;
+ final DashboardPageRobot _dashboardPageRobot;
final DisclaimerPageRobot _disclaimerPageRobot;
+ final WalletSeedPageRobot _walletSeedPageRobot;
+ final WalletListPageRobot _walletListPageRobot;
final NewWalletTypePageRobot _newWalletTypePageRobot;
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
+ final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
+ //* ========== Handles flow to start the app afresh and accept disclaimer =============
Future startAppFlow(Key key) async {
await app.main(topLevelKey: ValueKey('send_flow_test_app_key'));
-
+
await _tester.pumpAndSettle();
// --------- Disclaimer Page ------------
@@ -46,56 +70,275 @@ class CommonTestFlows {
await _disclaimerPageRobot.tapAcceptButton();
}
- Future restoreWalletThroughSeedsFlow() async {
- await _welcomeToRestoreFromSeedsPath();
- await _restoreFromSeeds();
+ //* ========== Handles flow from welcome to creating a new wallet ===============
+ Future welcomePageToCreateNewWalletFlow(
+ WalletType walletTypeToCreate,
+ List walletPin,
+ ) async {
+ await _welcomeToCreateWalletPath(walletTypeToCreate, walletPin);
+
+ await _generateNewWalletDetails();
+
+ await _confirmPreSeedInfo();
+
+ await _confirmWalletDetails();
}
- Future restoreWalletThroughKeysFlow() async {
- await _welcomeToRestoreFromSeedsPath();
+ //* ========== Handles flow from welcome to restoring wallet from seeds ===============
+ Future welcomePageToRestoreWalletThroughSeedsFlow(
+ WalletType walletTypeToRestore,
+ String walletSeed,
+ List walletPin,
+ ) async {
+ await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
+ await _restoreFromSeeds(walletTypeToRestore, walletSeed);
+ }
+
+ //* ========== Handles flow from welcome to restoring wallet from keys ===============
+ Future welcomePageToRestoreWalletThroughKeysFlow(
+ WalletType walletTypeToRestore,
+ List walletPin,
+ ) async {
+ await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
await _restoreFromKeys();
}
- Future _welcomeToRestoreFromSeedsPath() async {
- // --------- Welcome Page ---------------
- await _welcomePageRobot.navigateToRestoreWalletPage();
+ //* ========== Handles switching to wallet list or menu from dashboard ===============
+ Future switchToWalletMenuFromDashboardPage() async {
+ _tester.printToConsole('Switching to Wallet Menu');
+ await _dashboardPageRobot.openDrawerMenu();
- // ----------- Restore Options Page -----------
- // Route to restore from seeds page to continue flow
- await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage();
+ await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu();
+ }
+ void confirmAllAvailableWalletTypeIconsDisplayCorrectly() {
+ for (var walletType in availableWalletTypes) {
+ final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath;
+
+ final walletIconFinder = find.image(
+ Image.asset(
+ imageUrl!,
+ width: 32,
+ height: 32,
+ ).image,
+ );
+
+ expect(walletIconFinder, findsAny);
+ }
+ }
+
+ //* ========== Handles creating new wallet flow from wallet list/menu ===============
+ Future createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async {
+ _tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet');
+ await _walletListPageRobot.navigateToCreateNewWalletPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _selectWalletTypeForWallet(walletTypeToCreate);
+ await _commonTestCases.defaultSleepTime();
+
+ // ---- Wallet Group/New Seed Implementation Comes here
+ await _walletGroupDescriptionPageFlow(true, walletTypeToCreate);
+
+ await _generateNewWalletDetails();
+
+ await _confirmPreSeedInfo();
+
+ await _confirmWalletDetails();
+ await _commonTestCases.defaultSleepTime();
+ }
+
+ Future _walletGroupDescriptionPageFlow(bool isNewSeed, WalletType walletType) async {
+ if (!isBIP39Wallet(walletType)) return;
+
+ await _walletGroupDescriptionPageRobot.isWalletGroupDescriptionPage();
+
+ if (isNewSeed) {
+ await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage();
+ } else {
+ await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup();
+ }
+ }
+
+ //* ========== Handles restore wallet flow from wallet list/menu ===============
+ Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async {
+ _tester.printToConsole('Restoring ${walletType.name} Wallet');
+ await _walletListPageRobot.navigateToRestoreWalletOptionsPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
+ await _commonTestCases.defaultSleepTime();
+
+ await _selectWalletTypeForWallet(walletType);
+ await _commonTestCases.defaultSleepTime();
+
+ await _restoreFromSeeds(walletType, walletSeed);
+ await _commonTestCases.defaultSleepTime();
+ }
+
+ //* ========== Handles setting up pin code for wallet on first install ===============
+ Future setupPinCodeForWallet(List pin) async {
// ----------- SetupPinCode Page -------------
// Confirm initial defaults - Widgets to be displayed etc
await _setupPinCodeRobot.isSetupPinCodePage();
- await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true);
- await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false);
+ await _setupPinCodeRobot.enterPinCode(pin);
+ await _setupPinCodeRobot.enterPinCode(pin);
await _setupPinCodeRobot.tapSuccessButton();
+ }
+ Future _welcomeToCreateWalletPath(
+ WalletType walletTypeToCreate,
+ List pin,
+ ) async {
+ await _welcomePageRobot.navigateToCreateNewWalletPage();
+
+ await setupPinCodeForWallet(pin);
+
+ await _selectWalletTypeForWallet(walletTypeToCreate);
+ }
+
+ Future _welcomeToRestoreFromSeedsOrKeysPath(
+ WalletType walletTypeToRestore,
+ List pin,
+ ) async {
+ await _welcomePageRobot.navigateToRestoreWalletPage();
+
+ await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
+
+ await setupPinCodeForWallet(pin);
+
+ await _selectWalletTypeForWallet(walletTypeToRestore);
+ }
+
+ //* ============ Handles New Wallet Type Page ==================
+ Future _selectWalletTypeForWallet(WalletType type) async {
// ----------- NewWalletType Page -------------
// Confirm scroll behaviour works properly
- await _newWalletTypePageRobot
- .findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType);
+ await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
// Select a wallet and route to next page
- await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType);
+ await _newWalletTypePageRobot.selectWalletType(type);
await _newWalletTypePageRobot.onNextButtonPressed();
}
- Future _restoreFromSeeds() async {
+ //* ============ Handles New Wallet Page ==================
+ Future _generateNewWalletDetails() async {
+ await _newWalletPageRobot.isNewWalletPage();
+
+ await _newWalletPageRobot.generateWalletName();
+
+ await _newWalletPageRobot.onNextButtonPressed();
+ }
+
+ //* ============ Handles Pre Seed Page =====================
+ Future _confirmPreSeedInfo() async {
+ await _preSeedPageRobot.isPreSeedPage();
+
+ await _preSeedPageRobot.onConfirmButtonPressed();
+ }
+
+ //* ============ Handles Wallet Seed Page ==================
+ Future _confirmWalletDetails() async {
+ await _walletSeedPageRobot.isWalletSeedPage();
+
+ _walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly();
+
+ _walletSeedPageRobot.confirmWalletSeedReminderDisplays();
+
+ await _walletSeedPageRobot.onCopySeedsButtonPressed();
+
+ await _walletSeedPageRobot.onNextButtonPressed();
+
+ await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed();
+ }
+
+ //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action
+ Future _restoreFromSeeds(WalletType type, String walletSeed) async {
// ----------- RestoreFromSeedOrKeys Page -------------
- await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
- await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds);
+
+ await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions();
+ await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed);
+
+ final numberOfWords = walletSeed.split(' ').length;
+
+ if (numberOfWords == 25 && (type == WalletType.monero)) {
+ await _restoreFromSeedOrKeysPageRobot
+ .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy);
+
+ // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet
+ await _restoreFromSeedOrKeysPageRobot
+ .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight);
+ }
+
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}
+ //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Keys Action
Future _restoreFromKeys() async {
await _commonTestCases.swipePage();
await _commonTestCases.defaultSleepTime();
- await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
+ await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(
+ isSeedFormEntry: false,
+ );
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}
+
+ //* ====== Utility Function to get test wallet seeds for each wallet type ========
+ String getWalletSeedsByWalletType(WalletType walletType) {
+ switch (walletType) {
+ case WalletType.monero:
+ return secrets.moneroTestWalletSeeds;
+ case WalletType.bitcoin:
+ return secrets.bitcoinTestWalletSeeds;
+ case WalletType.ethereum:
+ return secrets.ethereumTestWalletSeeds;
+ case WalletType.litecoin:
+ return secrets.litecoinTestWalletSeeds;
+ case WalletType.bitcoinCash:
+ return secrets.bitcoinCashTestWalletSeeds;
+ case WalletType.polygon:
+ return secrets.polygonTestWalletSeeds;
+ case WalletType.solana:
+ return secrets.solanaTestWalletSeeds;
+ case WalletType.tron:
+ return secrets.tronTestWalletSeeds;
+ case WalletType.nano:
+ return secrets.nanoTestWalletSeeds;
+ case WalletType.wownero:
+ return secrets.wowneroTestWalletSeeds;
+ default:
+ return '';
+ }
+ }
+
+ //* ====== Utility Function to get test receive address for each wallet type ========
+ String getReceiveAddressByWalletType(WalletType walletType) {
+ switch (walletType) {
+ case WalletType.monero:
+ return secrets.moneroTestWalletReceiveAddress;
+ case WalletType.bitcoin:
+ return secrets.bitcoinTestWalletReceiveAddress;
+ case WalletType.ethereum:
+ return secrets.ethereumTestWalletReceiveAddress;
+ case WalletType.litecoin:
+ return secrets.litecoinTestWalletReceiveAddress;
+ case WalletType.bitcoinCash:
+ return secrets.bitcoinCashTestWalletReceiveAddress;
+ case WalletType.polygon:
+ return secrets.polygonTestWalletReceiveAddress;
+ case WalletType.solana:
+ return secrets.solanaTestWalletReceiveAddress;
+ case WalletType.tron:
+ return secrets.tronTestWalletReceiveAddress;
+ case WalletType.nano:
+ return secrets.nanoTestWalletReceiveAddress;
+ case WalletType.wownero:
+ return secrets.wowneroTestWalletReceiveAddress;
+ default:
+ return '';
+ }
+ }
}
diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart
index 9d97d47f8..db24fbc0b 100644
--- a/integration_test/funds_related_tests.dart
+++ b/integration_test/funds_related_tests.dart
@@ -9,6 +9,7 @@ import 'robots/dashboard_page_robot.dart';
import 'robots/exchange_confirm_page_robot.dart';
import 'robots/exchange_page_robot.dart';
import 'robots/exchange_trade_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -32,7 +33,11 @@ void main() {
await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key'));
- await commonTestFlows.restoreWalletThroughSeedsFlow();
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ CommonTestConstants.testWalletType,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
// ----------- RestoreFromSeedOrKeys Page -------------
await dashboardPageRobot.navigateToExchangePage();
@@ -59,7 +64,7 @@ void main() {
final onAuthPage = authPageRobot.onAuthPage();
if (onAuthPage) {
- await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
+ await authPageRobot.enterPinCode(CommonTestConstants.pin);
}
// ----------- Exchange Confirm Page -------------
diff --git a/integration_test/robots/dashboard_menu_widget_robot.dart b/integration_test/robots/dashboard_menu_widget_robot.dart
new file mode 100644
index 000000000..f48033dda
--- /dev/null
+++ b/integration_test/robots/dashboard_menu_widget_robot.dart
@@ -0,0 +1,39 @@
+import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class DashboardMenuWidgetRobot {
+ DashboardMenuWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future hasMenuWidget() async {
+ commonTestCases.hasType();
+ }
+
+ void displaysTheCorrectWalletNameAndSubName() {
+ final menuWidgetState = tester.state(find.byType(MenuWidget));
+
+ final walletName = menuWidgetState.widget.dashboardViewModel.name;
+ commonTestCases.hasText(walletName);
+
+ final walletSubName = menuWidgetState.widget.dashboardViewModel.subname;
+ if (walletSubName.isNotEmpty) {
+ commonTestCases.hasText(walletSubName);
+ }
+ }
+
+ Future navigateToWalletMenu() async {
+ await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future navigateToSecurityAndBackupPage() async {
+ await commonTestCases.tapItemByKey(
+ 'dashboard_page_menu_widget_security_and_backup_button_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart
index fc917c3b2..bc5f411ad 100644
--- a/integration_test/robots/dashboard_page_robot.dart
+++ b/integration_test/robots/dashboard_page_robot.dart
@@ -1,20 +1,44 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
+import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_test/flutter_test.dart';
import '../components/common_test_cases.dart';
+import 'dashboard_menu_widget_robot.dart';
class DashboardPageRobot {
- DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+ DashboardPageRobot(this.tester)
+ : commonTestCases = CommonTestCases(tester),
+ dashboardMenuWidgetRobot = DashboardMenuWidgetRobot(tester);
final WidgetTester tester;
+ final DashboardMenuWidgetRobot dashboardMenuWidgetRobot;
late CommonTestCases commonTestCases;
Future isDashboardPage() async {
await commonTestCases.isSpecificPage();
}
+ Future confirmWalletTypeIsDisplayedCorrectly(
+ WalletType type, {
+ bool isHaven = false,
+ }) async {
+ final cryptoBalanceWidget =
+ tester.widget(find.byType(CryptoBalanceWidget));
+ final hasAccounts = cryptoBalanceWidget.dashboardViewModel.balanceViewModel.hasAccounts;
+
+ if (hasAccounts) {
+ final walletName = cryptoBalanceWidget.dashboardViewModel.name;
+ commonTestCases.hasText(walletName);
+ } else {
+ final walletName = walletTypeToString(type);
+ final assetName = isHaven ? '$walletName Assets' : walletName;
+ commonTestCases.hasText(assetName);
+ }
+ await commonTestCases.defaultSleepTime(seconds: 5);
+ }
+
void confirmServiceUpdateButtonDisplays() {
commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
}
@@ -27,30 +51,40 @@ class DashboardPageRobot {
commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
}
- Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type,
- {bool isHaven = false}) async {
+ Future confirmRightCryptoAssetTitleDisplaysPerPageView(
+ WalletType type, {
+ bool isHaven = false,
+ }) async {
//Balance Page
- final walletName = walletTypeToString(type);
- final assetName = isHaven ? '$walletName Assets' : walletName;
- commonTestCases.hasText(assetName);
+ await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven);
// Swipe to Cake features Page
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(false);
commonTestCases.hasText('Cake ${S.current.features}');
// Swipe back to balance
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(true);
// Swipe to Transactions Page
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key');
- await commonTestCases.defaultSleepTime();
+ await swipeDashboardTab(true);
commonTestCases.hasText(S.current.transactions);
// Swipe back to balance
- await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false);
- await commonTestCases.defaultSleepTime(seconds: 5);
+ await swipeDashboardTab(false);
+ await commonTestCases.defaultSleepTime(seconds: 3);
+ }
+
+ Future swipeDashboardTab(bool swipeRight) async {
+ await commonTestCases.swipeByPageKey(
+ key: 'dashboard_page_view_key',
+ swipeRight: swipeRight,
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future openDrawerMenu() async {
+ await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key');
+ await commonTestCases.defaultSleepTime();
}
Future navigateToBuyPage() async {
diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart
index b439e4791..e01b2df9c 100644
--- a/integration_test/robots/exchange_page_robot.dart
+++ b/integration_test/robots/exchange_page_robot.dart
@@ -123,7 +123,7 @@ class ExchangePageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${depositCurrency.name}_button_key',
'picker_scrollbar_key',
);
@@ -149,7 +149,7 @@ class ExchangePageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
diff --git a/integration_test/robots/new_wallet_page_robot.dart b/integration_test/robots/new_wallet_page_robot.dart
new file mode 100644
index 000000000..f8deb00ae
--- /dev/null
+++ b/integration_test/robots/new_wallet_page_robot.dart
@@ -0,0 +1,35 @@
+import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class NewWalletPageRobot {
+ NewWalletPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isNewWalletPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future enterWalletName(String walletName) async {
+ await commonTestCases.enterText(
+ walletName,
+ 'new_wallet_page_wallet_name_textformfield_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future generateWalletName() async {
+ await commonTestCases.tapItemByKey(
+ 'new_wallet_page_wallet_name_textformfield_generate_name_button_key',
+ );
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future onNextButtonPressed() async {
+ await commonTestCases.tapItemByKey('new_wallet_page_confirm_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart
index b6805e9e0..18dc5fba4 100644
--- a/integration_test/robots/pin_code_widget_robot.dart
+++ b/integration_test/robots/pin_code_widget_robot.dart
@@ -24,13 +24,12 @@ class PinCodeWidgetRobot {
commonTestCases.hasValueKey('pin_code_button_0_key');
}
- Future pushPinButton(int index) async {
- await commonTestCases.tapItemByKey('pin_code_button_${index}_key');
- }
-
- Future enterPinCode(List pinCode, bool isFirstEntry) async {
+ Future enterPinCode(List pinCode, {int pumpDuration = 100}) async {
for (int pin in pinCode) {
- await pushPinButton(pin);
+ await commonTestCases.tapItemByKey(
+ 'pin_code_button_${pin}_key',
+ pumpDuration: pumpDuration,
+ );
}
await commonTestCases.defaultSleepTime();
diff --git a/integration_test/robots/pre_seed_page_robot.dart b/integration_test/robots/pre_seed_page_robot.dart
new file mode 100644
index 000000000..01be1249c
--- /dev/null
+++ b/integration_test/robots/pre_seed_page_robot.dart
@@ -0,0 +1,20 @@
+import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class PreSeedPageRobot {
+ PreSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isPreSeedPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future onConfirmButtonPressed() async {
+ await commonTestCases.tapItemByKey('pre_seed_page_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart
index 43a65095d..9d973061b 100644
--- a/integration_test/robots/restore_from_seed_or_key_robot.dart
+++ b/integration_test/robots/restore_from_seed_or_key_robot.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
@@ -70,6 +71,22 @@ class RestoreFromSeedOrKeysPageRobot {
await tester.pumpAndSettle();
}
+ Future enterBlockHeightForWalletRestore(String blockHeight) async {
+ await commonTestCases.enterText(
+ blockHeight,
+ 'wallet_restore_from_seed_blockheight_textfield_key',
+ );
+ await tester.pumpAndSettle();
+ }
+
+ Future chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType selectedType) async {
+ await commonTestCases.tapItemByKey('wallet_restore_from_seed_seedtype_picker_button_key');
+
+ await commonTestCases.defaultSleepTime();
+
+ await commonTestCases.tapItemByKey('picker_items_index_${selectedType.title}_button_key');
+ }
+
Future onPasteSeedPhraseButtonPressed() async {
await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
}
diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart
index b3cefc90c..cd1919609 100644
--- a/integration_test/robots/restore_options_page_robot.dart
+++ b/integration_test/robots/restore_options_page_robot.dart
@@ -14,14 +14,14 @@ class RestoreOptionsPageRobot {
}
void hasRestoreOptionsButton() {
- commonTestCases.hasValueKey('restore_options_from_seeds_button_key');
+ commonTestCases.hasValueKey('restore_options_from_seeds_or_keys_button_key');
commonTestCases.hasValueKey('restore_options_from_backup_button_key');
commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key');
commonTestCases.hasValueKey('restore_options_from_qr_button_key');
}
- Future navigateToRestoreFromSeedsPage() async {
- await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key');
+ Future navigateToRestoreFromSeedsOrKeysPage() async {
+ await commonTestCases.tapItemByKey('restore_options_from_seeds_or_keys_button_key');
await commonTestCases.defaultSleepTime();
}
diff --git a/integration_test/robots/security_and_backup_page_robot.dart b/integration_test/robots/security_and_backup_page_robot.dart
new file mode 100644
index 000000000..eb7c1bc87
--- /dev/null
+++ b/integration_test/robots/security_and_backup_page_robot.dart
@@ -0,0 +1,24 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class SecurityAndBackupPageRobot {
+ SecurityAndBackupPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ final CommonTestCases commonTestCases;
+
+ Future isSecurityAndBackupPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ void hasTitle() {
+ commonTestCases.hasText(S.current.security_and_backup);
+ }
+
+ Future navigateToShowKeysPage() async {
+ await commonTestCases.tapItemByKey('security_backup_page_show_keys_button_key');
+ }
+}
diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart
index 971556620..20cef948d 100644
--- a/integration_test/robots/send_page_robot.dart
+++ b/integration_test/robots/send_page_robot.dart
@@ -84,7 +84,7 @@ class SendPageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
@@ -117,7 +117,7 @@ class SendPageRobot {
return;
}
- await commonTestCases.scrollUntilVisible(
+ await commonTestCases.dragUntilVisible(
'picker_items_index_${priority.title}_button_key',
'picker_scrollbar_key',
);
@@ -198,7 +198,7 @@ class SendPageRobot {
tester.printToConsole('Starting inner _handleAuth loop checks');
try {
- await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
+ await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500);
tester.printToConsole('Auth done');
await tester.pump();
@@ -213,6 +213,7 @@ class SendPageRobot {
}
Future handleSendResult() async {
+ await tester.pump();
tester.printToConsole('Inside handle function');
bool hasError = false;
@@ -287,6 +288,8 @@ class SendPageRobot {
// Loop to wait for the operation to commit transaction
await _waitForCommitTransactionCompletion();
+ await tester.pump();
+
await commonTestCases.defaultSleepTime(seconds: 4);
} else {
await commonTestCases.defaultSleepTime();
diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart
new file mode 100644
index 000000000..40a49928f
--- /dev/null
+++ b/integration_test/robots/transactions_page_robot.dart
@@ -0,0 +1,286 @@
+import 'dart:async';
+
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
+import 'package:cake_wallet/utils/date_formatter.dart';
+import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
+import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
+import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/sync_status.dart';
+import 'package:cw_core/transaction_direction.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:intl/intl.dart';
+
+import '../components/common_test_cases.dart';
+
+class TransactionsPageRobot {
+ TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isTransactionsPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future confirmTransactionsPageConstantsDisplayProperly() async {
+ await commonTestCases.defaultSleepTime();
+
+ final transactionsPage = tester.widget(find.byType(TransactionsPage));
+ final dashboardViewModel = transactionsPage.dashboardViewModel;
+ if (dashboardViewModel.status is SyncingSyncStatus) {
+ commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key');
+ commonTestCases.hasText(S.current.syncing_wallet_alert_title);
+ commonTestCases.hasText(S.current.syncing_wallet_alert_content);
+ }
+
+ commonTestCases.hasValueKey('transactions_page_header_row_key');
+ commonTestCases.hasText(S.current.transactions);
+ commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key');
+ }
+
+ Future confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async {
+ // Retrieve the TransactionsPage widget and its DashboardViewModel
+ final transactionsPage = tester.widget(find.byType(TransactionsPage));
+ final dashboardViewModel = transactionsPage.dashboardViewModel;
+
+ // Define a timeout to prevent infinite loops
+ // Putting at one hour for cases like monero that takes time to sync
+ final timeout = Duration(hours: 1);
+ final pollingInterval = Duration(seconds: 2);
+ final endTime = DateTime.now().add(timeout);
+
+ while (DateTime.now().isBefore(endTime)) {
+ final isSynced = dashboardViewModel.status is SyncedSyncStatus;
+ final itemsLoaded = dashboardViewModel.items.isNotEmpty;
+
+ // Perform item checks if items are loaded
+ if (itemsLoaded) {
+ await _performItemChecks(dashboardViewModel);
+ } else {
+ // Verify placeholder when items are not loaded
+ _verifyPlaceholder();
+ }
+
+ // Determine if we should exit the loop
+ if (_shouldExitLoop(hasTxHistoryWhileSyncing, isSynced, itemsLoaded)) {
+ break;
+ }
+
+ // Pump the UI and wait for the next polling interval
+ await tester.pump(pollingInterval);
+ }
+
+ // After the loop, verify that both status is synced and items are loaded
+ if (!_isFinalStateValid(dashboardViewModel)) {
+ throw TimeoutException('Dashboard did not sync and load items within the allotted time.');
+ }
+ }
+
+ bool _shouldExitLoop(bool hasTxHistoryWhileSyncing, bool isSynced, bool itemsLoaded) {
+ if (hasTxHistoryWhileSyncing) {
+ // When hasTxHistoryWhileSyncing is true, exit when status is synced
+ return isSynced;
+ } else {
+ // When hasTxHistoryWhileSyncing is false, exit when status is synced and items are loaded
+ return isSynced && itemsLoaded;
+ }
+ }
+
+ void _verifyPlaceholder() {
+ commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key');
+ commonTestCases.hasText(S.current.placeholder_transactions);
+ }
+
+ bool _isFinalStateValid(DashboardViewModel dashboardViewModel) {
+ final isSynced = dashboardViewModel.status is SyncedSyncStatus;
+ final itemsLoaded = dashboardViewModel.items.isNotEmpty;
+ return isSynced && itemsLoaded;
+ }
+
+ Future _performItemChecks(DashboardViewModel dashboardViewModel) async {
+ List items = dashboardViewModel.items;
+ for (var item in items) {
+ final keyId = (item.key as ValueKey).value;
+ tester.printToConsole('\n');
+ tester.printToConsole(keyId);
+
+ await commonTestCases.dragUntilVisible(keyId, 'transactions_page_list_view_builder_key');
+ await tester.pump();
+
+ final isWidgetVisible = tester.any(find.byKey(ValueKey(keyId)));
+ if (!isWidgetVisible) {
+ tester.printToConsole('Moving to next visible item on list');
+ continue;
+ }
+ ;
+ await tester.pump();
+
+ if (item is DateSectionItem) {
+ await _verifyDateSectionItem(item);
+ } else if (item is TransactionListItem) {
+ tester.printToConsole(item.formattedTitle);
+ tester.printToConsole(item.formattedFiatAmount);
+ tester.printToConsole('\n');
+ await _verifyTransactionListItemDisplay(item, dashboardViewModel);
+ } else if (item is AnonpayTransactionListItem) {
+ await _verifyAnonpayTransactionListItemDisplay(item);
+ } else if (item is TradeListItem) {
+ await _verifyTradeListItemDisplay(item);
+ } else if (item is OrderListItem) {
+ await _verifyOrderListItemDisplay(item);
+ }
+ }
+ }
+
+ Future _verifyDateSectionItem(DateSectionItem item) async {
+ final title = DateFormatter.convertDateTimeToReadableString(item.date);
+ tester.printToConsole(title);
+ await tester.pump();
+
+ commonTestCases.findWidgetViaDescendant(
+ of: find.byKey(item.key),
+ matching: find.text(title),
+ );
+ }
+
+ Future _verifyTransactionListItemDisplay(
+ TransactionListItem item,
+ DashboardViewModel dashboardViewModel,
+ ) async {
+ final keyId =
+ '${dashboardViewModel.type.name}_transaction_history_item_${item.transaction.id}_key';
+
+ if (item.hasTokens && item.assetOfTransaction == null) return;
+
+ //* ==============Confirm it has the right key for this item ========
+ commonTestCases.hasValueKey(keyId);
+
+ //* ======Confirm it displays the properly formatted amount==========
+ commonTestCases.findWidgetViaDescendant(
+ of: find.byKey(ValueKey(keyId)),
+ matching: find.text(item.formattedCryptoAmount),
+ );
+
+ //* ======Confirm it displays the properly formatted title===========
+ final transactionType = dashboardViewModel.getTransactionType(item.transaction);
+
+ final title = item.formattedTitle + item.formattedStatus + transactionType;
+
+ commonTestCases.findWidgetViaDescendant(
+ of: find.byKey(ValueKey(keyId)),
+ matching: find.text(title),
+ );
+
+ //* ======Confirm it displays the properly formatted date============
+ final formattedDate = DateFormat('HH:mm').format(item.transaction.date);
+ commonTestCases.findWidgetViaDescendant(
+ of: find.byKey(ValueKey(keyId)),
+ matching: find.text(formattedDate),
+ );
+
+ //* ======Confirm it displays the properly formatted fiat amount=====
+ final formattedFiatAmount =
+ dashboardViewModel.balanceViewModel.isFiatDisabled ? '' : item.formattedFiatAmount;
+ if (formattedFiatAmount.isNotEmpty) {
+ commonTestCases.findWidgetViaDescendant(
+ of: find.byKey(ValueKey(keyId)),
+ matching: find.text(formattedFiatAmount),
+ );
+ }
+
+ //* ======Confirm it displays the right image based on the transaction direction=====
+ final imageToUse = item.transaction.direction == TransactionDirection.incoming
+ ? 'assets/images/down_arrow.png'
+ : 'assets/images/up_arrow.png';
+
+ find.widgetWithImage(Container, AssetImage(imageToUse));
+ }
+
+ Future _verifyAnonpayTransactionListItemDisplay(AnonpayTransactionListItem item) async {
+ final keyId = 'anonpay_invoice_transaction_list_item_${item.transaction.invoiceId}_key';
+
+ //* ==============Confirm it has the right key for this item ========
+ commonTestCases.hasValueKey(keyId);
+
+ //* ==============Confirm it displays the correct provider =========================
+ commonTestCases.hasText(item.transaction.provider);
+
+ //* ===========Confirm it displays the properly formatted amount with currency ========
+ final currency = item.transaction.fiatAmount != null
+ ? item.transaction.fiatEquiv ?? ''
+ : CryptoCurrency.fromFullName(item.transaction.coinTo).name.toUpperCase();
+
+ final amount =
+ item.transaction.fiatAmount?.toString() ?? (item.transaction.amountTo?.toString() ?? '');
+
+ final amountCurrencyText = amount + ' ' + currency;
+
+ commonTestCases.hasText(amountCurrencyText);
+
+ //* ======Confirm it displays the properly formatted date=================
+ final formattedDate = DateFormat('HH:mm').format(item.transaction.createdAt);
+ commonTestCases.hasText(formattedDate);
+
+ //* ===============Confirm it displays the right image====================
+ find.widgetWithImage(ClipRRect, AssetImage('assets/images/trocador.png'));
+ }
+
+ Future _verifyTradeListItemDisplay(TradeListItem item) async {
+ final keyId = 'trade_list_item_${item.trade.id}_key';
+
+ //* ==============Confirm it has the right key for this item ========
+ commonTestCases.hasValueKey(keyId);
+
+ //* ==============Confirm it displays the correct provider =========================
+ final conversionFlow = '${item.trade.from.toString()} → ${item.trade.to.toString()}';
+
+ commonTestCases.hasText(conversionFlow);
+
+ //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
+
+ final amountCryptoText = item.tradeFormattedAmount + ' ' + item.trade.from.toString();
+
+ commonTestCases.hasText(amountCryptoText);
+
+ //* ======Confirm it displays the properly formatted date=================
+ final createdAtFormattedDate =
+ item.trade.createdAt != null ? DateFormat('HH:mm').format(item.trade.createdAt!) : null;
+
+ if (createdAtFormattedDate != null) {
+ commonTestCases.hasText(createdAtFormattedDate);
+ }
+
+ //* ===============Confirm it displays the right image====================
+ commonTestCases.hasValueKey(item.trade.provider.image);
+ }
+
+ Future _verifyOrderListItemDisplay(OrderListItem item) async {
+ final keyId = 'order_list_item_${item.order.id}_key';
+
+ //* ==============Confirm it has the right key for this item ========
+ commonTestCases.hasValueKey(keyId);
+
+ //* ==============Confirm it displays the correct provider =========================
+ final orderFlow = '${item.order.from!} → ${item.order.to}';
+
+ commonTestCases.hasText(orderFlow);
+
+ //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
+
+ final amountCryptoText = item.orderFormattedAmount + ' ' + item.order.to!;
+
+ commonTestCases.hasText(amountCryptoText);
+
+ //* ======Confirm it displays the properly formatted date=================
+ final createdAtFormattedDate = DateFormat('HH:mm').format(item.order.createdAt);
+
+ commonTestCases.hasText(createdAtFormattedDate);
+ }
+}
diff --git a/integration_test/robots/wallet_group_description_page_robot.dart b/integration_test/robots/wallet_group_description_page_robot.dart
new file mode 100644
index 000000000..57500dc3c
--- /dev/null
+++ b/integration_test/robots/wallet_group_description_page_robot.dart
@@ -0,0 +1,32 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/new_wallet/wallet_group_description_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class WalletGroupDescriptionPageRobot {
+ WalletGroupDescriptionPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ final CommonTestCases commonTestCases;
+
+ Future isWalletGroupDescriptionPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ void hasTitle() {
+ commonTestCases.hasText(S.current.wallet_group);
+ }
+
+ Future navigateToCreateNewSeedPage() async {
+ await commonTestCases.tapItemByKey(
+ 'wallet_group_description_page_create_new_seed_button_key',
+ );
+ }
+
+ Future navigateToChooseWalletGroup() async {
+ await commonTestCases.tapItemByKey(
+ 'wallet_group_description_page_choose_wallet_group_button_key',
+ );
+ }
+}
diff --git a/integration_test/robots/wallet_keys_robot.dart b/integration_test/robots/wallet_keys_robot.dart
new file mode 100644
index 000000000..f6aeb3a66
--- /dev/null
+++ b/integration_test/robots/wallet_keys_robot.dart
@@ -0,0 +1,162 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/reactions/wallet_connect.dart';
+import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
+import 'package:cake_wallet/store/app_store.dart';
+import 'package:cw_core/monero_wallet_keys.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_monero/monero_wallet.dart';
+import 'package:cw_wownero/wownero_wallet.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:polyseed/polyseed.dart';
+
+import '../components/common_test_cases.dart';
+
+class WalletKeysAndSeedPageRobot {
+ WalletKeysAndSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ final CommonTestCases commonTestCases;
+
+ Future isWalletKeysAndSeedPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ void hasTitle() {
+ final walletKeysPage = tester.widget(find.byType(WalletKeysPage));
+ final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
+ commonTestCases.hasText(walletKeysViewModel.title);
+ }
+
+ void hasShareWarning() {
+ commonTestCases.hasText(S.current.do_not_share_warning_text.toUpperCase());
+ }
+
+ Future confirmWalletCredentials(WalletType walletType) async {
+ final walletKeysPage = tester.widget(find.byType(WalletKeysPage));
+ final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
+
+ final appStore = walletKeysViewModel.appStore;
+ final walletName = walletType.name;
+ bool hasSeed = appStore.wallet!.seed != null;
+ bool hasHexSeed = appStore.wallet!.hexSeed != null;
+ bool hasPrivateKey = appStore.wallet!.privateKey != null;
+
+ if (walletType == WalletType.monero) {
+ final moneroWallet = appStore.wallet as MoneroWallet;
+ final lang = PolyseedLang.getByPhrase(moneroWallet.seed);
+ final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish);
+
+ _confirmMoneroWalletCredentials(
+ appStore,
+ walletName,
+ moneroWallet.seed,
+ legacySeed,
+ );
+ }
+
+ if (walletType == WalletType.wownero) {
+ final wowneroWallet = appStore.wallet as WowneroWallet;
+ final lang = PolyseedLang.getByPhrase(wowneroWallet.seed);
+ final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish);
+
+ _confirmMoneroWalletCredentials(
+ appStore,
+ walletName,
+ wowneroWallet.seed,
+ legacySeed,
+ );
+ }
+
+ if (walletType == WalletType.bitcoin ||
+ walletType == WalletType.litecoin ||
+ walletType == WalletType.bitcoinCash) {
+ commonTestCases.hasText(appStore.wallet!.seed!);
+ tester.printToConsole('$walletName wallet has seeds properly displayed');
+ }
+
+ if (isEVMCompatibleChain(walletType) ||
+ walletType == WalletType.solana ||
+ walletType == WalletType.tron) {
+ if (hasSeed) {
+ commonTestCases.hasText(appStore.wallet!.seed!);
+ tester.printToConsole('$walletName wallet has seeds properly displayed');
+ }
+ if (hasPrivateKey) {
+ commonTestCases.hasText(appStore.wallet!.privateKey!);
+ tester.printToConsole('$walletName wallet has private key properly displayed');
+ }
+ }
+
+ if (walletType == WalletType.nano || walletType == WalletType.banano) {
+ if (hasSeed) {
+ commonTestCases.hasText(appStore.wallet!.seed!);
+ tester.printToConsole('$walletName wallet has seeds properly displayed');
+ }
+ if (hasHexSeed) {
+ commonTestCases.hasText(appStore.wallet!.hexSeed!);
+ tester.printToConsole('$walletName wallet has hexSeed properly displayed');
+ }
+ if (hasPrivateKey) {
+ commonTestCases.hasText(appStore.wallet!.privateKey!);
+ tester.printToConsole('$walletName wallet has private key properly displayed');
+ }
+ }
+
+ await commonTestCases.defaultSleepTime(seconds: 5);
+ }
+
+ void _confirmMoneroWalletCredentials(
+ AppStore appStore,
+ String walletName,
+ String seed,
+ String legacySeed,
+ ) {
+ final keys = appStore.wallet!.keys as MoneroWalletKeys;
+
+ final hasPublicSpendKey = commonTestCases.isKeyPresent(
+ '${walletName}_wallet_public_spend_key_item_key',
+ );
+ final hasPrivateSpendKey = commonTestCases.isKeyPresent(
+ '${walletName}_wallet_private_spend_key_item_key',
+ );
+ final hasPublicViewKey = commonTestCases.isKeyPresent(
+ '${walletName}_wallet_public_view_key_item_key',
+ );
+ final hasPrivateViewKey = commonTestCases.isKeyPresent(
+ '${walletName}_wallet_private_view_key_item_key',
+ );
+ final hasSeeds = seed.isNotEmpty;
+ final hasSeedLegacy = Polyseed.isValidSeed(seed);
+
+ if (hasPublicSpendKey) {
+ commonTestCases.hasText(keys.publicSpendKey);
+ tester.printToConsole('$walletName wallet has public spend key properly displayed');
+ }
+ if (hasPrivateSpendKey) {
+ commonTestCases.hasText(keys.privateSpendKey);
+ tester.printToConsole('$walletName wallet has private spend key properly displayed');
+ }
+ if (hasPublicViewKey) {
+ commonTestCases.hasText(keys.publicViewKey);
+ tester.printToConsole('$walletName wallet has public view key properly displayed');
+ }
+ if (hasPrivateViewKey) {
+ commonTestCases.hasText(keys.privateViewKey);
+ tester.printToConsole('$walletName wallet has private view key properly displayed');
+ }
+ if (hasSeeds) {
+ commonTestCases.hasText(seed);
+ tester.printToConsole('$walletName wallet has seeds properly displayed');
+ }
+ if (hasSeedLegacy) {
+ commonTestCases.hasText(legacySeed);
+ tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
+ }
+ }
+
+ Future backToDashboard() async {
+ tester.printToConsole('Going back to dashboard from credentials page');
+ await commonTestCases.goBack();
+ await commonTestCases.goBack();
+ }
+}
diff --git a/integration_test/robots/wallet_list_page_robot.dart b/integration_test/robots/wallet_list_page_robot.dart
new file mode 100644
index 000000000..b46d4ca95
--- /dev/null
+++ b/integration_test/robots/wallet_list_page_robot.dart
@@ -0,0 +1,27 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class WalletListPageRobot {
+ WalletListPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isWalletListPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ void displaysCorrectTitle() {
+ commonTestCases.hasText(S.current.wallets);
+ }
+
+ Future navigateToCreateNewWalletPage() async {
+ commonTestCases.tapItemByKey('wallet_list_page_create_new_wallet_button_key');
+ }
+
+ Future navigateToRestoreWalletOptionsPage() async {
+ commonTestCases.tapItemByKey('wallet_list_page_restore_wallet_button_key');
+ }
+}
diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart
new file mode 100644
index 000000000..d52f3b1ec
--- /dev/null
+++ b/integration_test/robots/wallet_seed_page_robot.dart
@@ -0,0 +1,57 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../components/common_test_cases.dart';
+
+class WalletSeedPageRobot {
+ WalletSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
+
+ final WidgetTester tester;
+ late CommonTestCases commonTestCases;
+
+ Future isWalletSeedPage() async {
+ await commonTestCases.isSpecificPage();
+ }
+
+ Future onNextButtonPressed() async {
+ await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future onConfirmButtonOnSeedAlertDialogPressed() async {
+ await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_confirm_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future onBackButtonOnSeedAlertDialogPressed() async {
+ await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_back_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ void confirmWalletDetailsDisplayCorrectly() {
+ final walletSeedPage = tester.widget(find.byType(WalletSeedPage));
+
+ final walletSeedViewModel = walletSeedPage.walletSeedViewModel;
+
+ final walletName = walletSeedViewModel.name;
+ final walletSeeds = walletSeedViewModel.seed;
+
+ commonTestCases.hasText(walletName);
+ commonTestCases.hasText(walletSeeds);
+ }
+
+ void confirmWalletSeedReminderDisplays() {
+ commonTestCases.hasText(S.current.seed_reminder);
+ }
+
+ Future onSaveSeedsButtonPressed() async {
+ await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+
+ Future onCopySeedsButtonPressed() async {
+ await commonTestCases.tapItemByKey('wallet_seed_page_copy_seeds_button_key');
+ await commonTestCases.defaultSleepTime();
+ }
+}
diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart
new file mode 100644
index 000000000..bf6fd5a5f
--- /dev/null
+++ b/integration_test/test_suites/confirm_seeds_flow_test.dart
@@ -0,0 +1,107 @@
+import 'package:cake_wallet/wallet_types.g.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../components/common_test_constants.dart';
+import '../components/common_test_flows.dart';
+import '../robots/auth_page_robot.dart';
+import '../robots/dashboard_page_robot.dart';
+import '../robots/security_and_backup_page_robot.dart';
+import '../robots/wallet_keys_robot.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ AuthPageRobot authPageRobot;
+ CommonTestFlows commonTestFlows;
+ DashboardPageRobot dashboardPageRobot;
+ WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot;
+ SecurityAndBackupPageRobot securityAndBackupPageRobot;
+
+ testWidgets(
+ 'Confirm if the seeds display properly',
+ (tester) async {
+ authPageRobot = AuthPageRobot(tester);
+ commonTestFlows = CommonTestFlows(tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
+ walletKeysAndSeedPageRobot = WalletKeysAndSeedPageRobot(tester);
+ securityAndBackupPageRobot = SecurityAndBackupPageRobot(tester);
+
+ // Start the app
+ await commonTestFlows.startAppFlow(
+ ValueKey('confirm_creds_display_correctly_flow_app_key'),
+ );
+
+ await commonTestFlows.welcomePageToCreateNewWalletFlow(
+ WalletType.solana,
+ CommonTestConstants.pin,
+ );
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
+
+ await _confirmSeedsFlowForWalletType(
+ WalletType.solana,
+ authPageRobot,
+ dashboardPageRobot,
+ securityAndBackupPageRobot,
+ walletKeysAndSeedPageRobot,
+ tester,
+ );
+
+ // Do the same for other available wallet types
+ for (var walletType in availableWalletTypes) {
+ if (walletType == WalletType.solana) {
+ continue;
+ }
+
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ await commonTestFlows.createNewWalletFromWalletMenu(walletType);
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
+
+ await _confirmSeedsFlowForWalletType(
+ walletType,
+ authPageRobot,
+ dashboardPageRobot,
+ securityAndBackupPageRobot,
+ walletKeysAndSeedPageRobot,
+ tester,
+ );
+ }
+
+ await Future.delayed(Duration(seconds: 15));
+ },
+ );
+}
+
+Future _confirmSeedsFlowForWalletType(
+ WalletType walletType,
+ AuthPageRobot authPageRobot,
+ DashboardPageRobot dashboardPageRobot,
+ SecurityAndBackupPageRobot securityAndBackupPageRobot,
+ WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot,
+ WidgetTester tester,
+) async {
+ await dashboardPageRobot.openDrawerMenu();
+ await dashboardPageRobot.dashboardMenuWidgetRobot.navigateToSecurityAndBackupPage();
+
+ await securityAndBackupPageRobot.navigateToShowKeysPage();
+
+ final onAuthPage = authPageRobot.onAuthPage();
+ if (onAuthPage) {
+ await authPageRobot.enterPinCode(CommonTestConstants.pin);
+ }
+
+ await tester.pumpAndSettle();
+
+ await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage();
+ walletKeysAndSeedPageRobot.hasTitle();
+ walletKeysAndSeedPageRobot.hasShareWarning();
+
+ walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
+
+ await walletKeysAndSeedPageRobot.backToDashboard();
+}
diff --git a/integration_test/test_suites/create_wallet_flow_test.dart b/integration_test/test_suites/create_wallet_flow_test.dart
new file mode 100644
index 000000000..2a50dbbe8
--- /dev/null
+++ b/integration_test/test_suites/create_wallet_flow_test.dart
@@ -0,0 +1,57 @@
+import 'package:cake_wallet/wallet_types.g.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../components/common_test_constants.dart';
+import '../components/common_test_flows.dart';
+import '../robots/dashboard_page_robot.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ CommonTestFlows commonTestFlows;
+ DashboardPageRobot dashboardPageRobot;
+
+ testWidgets(
+ 'Create Wallet Flow',
+ (tester) async {
+ commonTestFlows = CommonTestFlows(tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
+
+ // Start the app
+ await commonTestFlows.startAppFlow(
+ ValueKey('create_wallets_through_seeds_test_app_key'),
+ );
+
+ await commonTestFlows.welcomePageToCreateNewWalletFlow(
+ WalletType.solana,
+ CommonTestConstants.pin,
+ );
+
+ // Confirm it actually restores a solana wallet
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
+
+ // Do the same for other available wallet types
+ for (var walletType in availableWalletTypes) {
+ if (walletType == WalletType.solana) {
+ continue;
+ }
+
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ await commonTestFlows.createNewWalletFromWalletMenu(walletType);
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
+ }
+
+ // Goes to the wallet menu and provides a confirmation that all the wallets were correctly restored
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
+
+ await Future.delayed(Duration(seconds: 5));
+ },
+ );
+}
diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart
index 6c993634c..c36ef9396 100644
--- a/integration_test/test_suites/exchange_flow_test.dart
+++ b/integration_test/test_suites/exchange_flow_test.dart
@@ -9,6 +9,7 @@ import '../robots/dashboard_page_robot.dart';
import '../robots/exchange_confirm_page_robot.dart';
import '../robots/exchange_page_robot.dart';
import '../robots/exchange_trade_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -20,40 +21,42 @@ void main() {
ExchangeTradePageRobot exchangeTradePageRobot;
ExchangeConfirmPageRobot exchangeConfirmPageRobot;
- group('Exchange Flow Tests', () {
- testWidgets('Exchange flow', (tester) async {
- authPageRobot = AuthPageRobot(tester);
- commonTestFlows = CommonTestFlows(tester);
- exchangePageRobot = ExchangePageRobot(tester);
- dashboardPageRobot = DashboardPageRobot(tester);
- exchangeTradePageRobot = ExchangeTradePageRobot(tester);
- exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
+ testWidgets('Exchange flow', (tester) async {
+ authPageRobot = AuthPageRobot(tester);
+ commonTestFlows = CommonTestFlows(tester);
+ exchangePageRobot = ExchangePageRobot(tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
+ exchangeTradePageRobot = ExchangeTradePageRobot(tester);
+ exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
- await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
- await commonTestFlows.restoreWalletThroughSeedsFlow();
- await dashboardPageRobot.navigateToExchangePage();
+ await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ CommonTestConstants.testWalletType,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
+ await dashboardPageRobot.navigateToExchangePage();
- // ----------- Exchange Page -------------
- await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
- await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
+ // ----------- Exchange Page -------------
+ await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
+ await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
- await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
- await exchangePageRobot.enterDepositRefundAddress(
- depositAddress: CommonTestConstants.testWalletAddress,
- );
- await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
-
- await exchangePageRobot.onExchangeButtonPressed();
+ await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
+ await exchangePageRobot.enterDepositRefundAddress(
+ depositAddress: CommonTestConstants.testWalletAddress,
+ );
+ await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
- await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
+ await exchangePageRobot.onExchangeButtonPressed();
- final onAuthPage = authPageRobot.onAuthPage();
- if (onAuthPage) {
- await authPageRobot.enterPinCode(CommonTestConstants.pin, false);
- }
+ await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
- await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
- await exchangeTradePageRobot.onGotItButtonPressed();
- });
+ final onAuthPage = authPageRobot.onAuthPage();
+ if (onAuthPage) {
+ await authPageRobot.enterPinCode(CommonTestConstants.pin);
+ }
+
+ await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
+ await exchangeTradePageRobot.onGotItButtonPressed();
});
}
diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart
new file mode 100644
index 000000000..a810aa722
--- /dev/null
+++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart
@@ -0,0 +1,63 @@
+import 'package:cake_wallet/wallet_types.g.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../components/common_test_constants.dart';
+import '../components/common_test_flows.dart';
+import '../robots/dashboard_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ CommonTestFlows commonTestFlows;
+ DashboardPageRobot dashboardPageRobot;
+
+ testWidgets(
+ 'Restoring Wallets Through Seeds',
+ (tester) async {
+ commonTestFlows = CommonTestFlows(tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
+
+ // Start the app
+ await commonTestFlows.startAppFlow(
+ ValueKey('restore_wallets_through_seeds_test_app_key'),
+ );
+
+ // Restore the first wallet type: Solana
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ WalletType.solana,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
+
+ // Confirm it actually restores a solana wallet
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
+
+ // Do the same for other available wallet types
+ for (var walletType in availableWalletTypes) {
+ if (walletType == WalletType.solana) {
+ continue;
+ }
+
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ await commonTestFlows.restoreWalletFromWalletMenu(
+ walletType,
+ commonTestFlows.getWalletSeedsByWalletType(walletType),
+ );
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
+ }
+
+ // Goes to the wallet menu and provides a visual confirmation that all the wallets were correctly restored
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
+
+ await Future.delayed(Duration(seconds: 5));
+ },
+ );
+}
diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart
index 38ac1574f..7a46435b8 100644
--- a/integration_test/test_suites/send_flow_test.dart
+++ b/integration_test/test_suites/send_flow_test.dart
@@ -6,6 +6,7 @@ import '../components/common_test_constants.dart';
import '../components/common_test_flows.dart';
import '../robots/dashboard_page_robot.dart';
import '../robots/send_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -14,28 +15,30 @@ void main() {
CommonTestFlows commonTestFlows;
DashboardPageRobot dashboardPageRobot;
- group('Send Flow Tests', () {
- testWidgets('Send flow', (tester) async {
- commonTestFlows = CommonTestFlows(tester);
- sendPageRobot = SendPageRobot(tester: tester);
- dashboardPageRobot = DashboardPageRobot(tester);
+ testWidgets('Send flow', (tester) async {
+ commonTestFlows = CommonTestFlows(tester);
+ sendPageRobot = SendPageRobot(tester: tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
- await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
- await commonTestFlows.restoreWalletThroughSeedsFlow();
- await dashboardPageRobot.navigateToSendPage();
+ await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ CommonTestConstants.testWalletType,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
+ await dashboardPageRobot.navigateToSendPage();
- await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
- await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
- await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount);
- await sendPageRobot.selectTransactionPriority();
+ await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
+ await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);
+ await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount);
+ await sendPageRobot.selectTransactionPriority();
- await sendPageRobot.onSendButtonPressed();
+ await sendPageRobot.onSendButtonPressed();
- await sendPageRobot.handleSendResult();
+ await sendPageRobot.handleSendResult();
- await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed();
+ await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed();
- await sendPageRobot.onSentDialogPopUp();
- });
+ await sendPageRobot.onSentDialogPopUp();
});
}
diff --git a/integration_test/test_suites/transaction_history_flow_test.dart b/integration_test/test_suites/transaction_history_flow_test.dart
new file mode 100644
index 000000000..8af6d39fd
--- /dev/null
+++ b/integration_test/test_suites/transaction_history_flow_test.dart
@@ -0,0 +1,70 @@
+import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../components/common_test_constants.dart';
+import '../components/common_test_flows.dart';
+import '../robots/dashboard_page_robot.dart';
+import '../robots/transactions_page_robot.dart';
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ CommonTestFlows commonTestFlows;
+ DashboardPageRobot dashboardPageRobot;
+ TransactionsPageRobot transactionsPageRobot;
+
+ /// Two Test Scenarios
+ /// - Fully Synchronizes and display the transaction history either immediately or few seconds after fully synchronizing
+ /// - Displays the transaction history progressively as synchronizing happens
+ testWidgets('Transaction history flow', (tester) async {
+ commonTestFlows = CommonTestFlows(tester);
+ dashboardPageRobot = DashboardPageRobot(tester);
+ transactionsPageRobot = TransactionsPageRobot(tester);
+
+ await commonTestFlows.startAppFlow(
+ ValueKey('confirm_creds_display_correctly_flow_app_key'),
+ );
+
+ /// Test Scenario 1 - Displays transaction history list after fully synchronizing.
+ ///
+ /// For Solana/Tron WalletTypes.
+ await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
+ WalletType.solana,
+ secrets.solanaTestWalletSeeds,
+ CommonTestConstants.pin,
+ );
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
+
+ await dashboardPageRobot.swipeDashboardTab(true);
+
+ await transactionsPageRobot.isTransactionsPage();
+
+ await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
+
+ await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(false);
+
+ /// Test Scenario 2 - Displays transaction history list while synchronizing.
+ ///
+ /// For bitcoin/Monero/Wownero WalletTypes.
+ await commonTestFlows.switchToWalletMenuFromDashboardPage();
+
+ await commonTestFlows.restoreWalletFromWalletMenu(
+ WalletType.bitcoin,
+ secrets.bitcoinTestWalletSeeds,
+ );
+
+ await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.bitcoin);
+
+ await dashboardPageRobot.swipeDashboardTab(true);
+
+ await transactionsPageRobot.isTransactionsPage();
+
+ await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
+
+ await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(true);
+ });
+}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 322ef6f86..470bdf4e7 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -109,15 +109,15 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.1.1):
- Flutter
- - Protobuf (3.27.2)
+ - Protobuf (3.28.2)
- ReachabilitySwift (5.2.3)
- reactive_ble_mobile (0.0.1):
- Flutter
- Protobuf (~> 3.5)
- SwiftProtobuf (~> 1.0)
- - SDWebImage (5.19.4):
- - SDWebImage/Core (= 5.19.4)
- - SDWebImage/Core (5.19.4)
+ - SDWebImage (5.19.7):
+ - SDWebImage/Core (= 5.19.7)
+ - SDWebImage/Core (5.19.7)
- sensitive_clipboard (0.0.1):
- Flutter
- share_plus (0.0.1):
@@ -271,10 +271,10 @@ SPEC CHECKSUMS:
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
- Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2
+ Protobuf: 28c89b24435762f60244e691544ed80f50d82701
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
- SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
+ SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
diff --git a/lib/cake_pay/cake_pay_api.dart b/lib/cake_pay/cake_pay_api.dart
index f403ebc63..1f91b338d 100644
--- a/lib/cake_pay/cake_pay_api.dart
+++ b/lib/cake_pay/cake_pay_api.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:cake_wallet/cake_pay/cake_pay_order.dart';
import 'package:cake_wallet/cake_pay/cake_pay_user_credentials.dart';
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
+import 'package:cake_wallet/entities/country.dart';
import 'package:http/http.dart' as http;
class CakePayApi {
@@ -171,7 +172,7 @@ class CakePayApi {
}
/// Get Countries
- Future> getCountries(
+ Future> getCountries(
{required String CSRFToken, required String authorization}) async {
final uri = Uri.https(baseCakePayUri, countriesPath);
@@ -188,8 +189,11 @@ class CakePayApi {
}
final bodyJson = json.decode(response.body) as List;
-
- return bodyJson.map((country) => country['name'] as String).toList();
+ return bodyJson
+ .map((country) => country['name'] as String)
+ .map((name) => Country.fromCakePayName(name))
+ .whereType()
+ .toList();
}
/// Get Vendors
diff --git a/lib/cake_pay/cake_pay_service.dart b/lib/cake_pay/cake_pay_service.dart
index be8e1d189..39f2ca77a 100644
--- a/lib/cake_pay/cake_pay_service.dart
+++ b/lib/cake_pay/cake_pay_service.dart
@@ -3,6 +3,7 @@ import 'package:cake_wallet/cake_pay/cake_pay_api.dart';
import 'package:cake_wallet/cake_pay/cake_pay_order.dart';
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
import 'package:cake_wallet/core/secure_storage.dart';
+import 'package:cake_wallet/entities/country.dart';
class CakePayService {
CakePayService(this.secureStorage, this.cakePayApi);
@@ -23,7 +24,7 @@ class CakePayService {
final CakePayApi cakePayApi;
/// Get Available Countries
- Future> getCountries() async =>
+ Future> getCountries() async =>
await cakePayApi.getCountries(CSRFToken: CSRFToken, authorization: authorization);
/// Get Vendors
diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart
index 21726fab8..9ca8a41ad 100644
--- a/lib/core/address_validator.dart
+++ b/lib/core/address_validator.dart
@@ -106,8 +106,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.wow:
pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.bch:
- pattern =
- '(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}';
+ pattern = '^(bitcoincash:)?(q|p)[0-9a-zA-Z]{41,42}';
case CryptoCurrency.bnb:
pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.hbar:
diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart
index adb516817..ba4785643 100644
--- a/lib/core/wallet_connect/web3wallet_service.dart
+++ b/lib/core/wallet_connect/web3wallet_service.dart
@@ -267,6 +267,8 @@ abstract class Web3WalletServiceBase with Store {
final keyForWallet = getKeyForStoringTopicsForWallet();
+ if (keyForWallet.isEmpty) return;
+
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
final filteredPairings =
@@ -360,6 +362,10 @@ abstract class Web3WalletServiceBase with Store {
String getKeyForStoringTopicsForWallet() {
List chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
+ if (chainKeys.isEmpty) {
+ return '';
+ }
+
final keyForPairingTopic =
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
@@ -386,6 +392,8 @@ abstract class Web3WalletServiceBase with Store {
// Get key specific to the current wallet
final key = getKeyForStoringTopicsForWallet();
+ if (key.isEmpty) return;
+
// Get all pairing topics attached to this key
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
diff --git a/lib/di.dart b/lib/di.dart
index f50dc5875..cf182e1ea 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -35,6 +35,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
+import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
+import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
@@ -945,6 +947,10 @@ Future setup({
getIt.registerFactory(() => MwebSettingsPage(getIt.get()));
+ getIt.registerFactory(() => MwebLogsPage(getIt.get()));
+
+ getIt.registerFactory(() => MwebNodePage(getIt.get()));
+
getIt.registerFactory(() => OtherSettingsPage(getIt.get()));
getIt.registerFactory(() => NanoChangeRepPage(
@@ -1265,7 +1271,8 @@ Future setup({
() => CakePayService(getIt.get(), getIt.get()));
getIt.registerFactory(
- () => CakePayCardsListViewModel(cakePayService: getIt.get()));
+ () => CakePayCardsListViewModel(cakePayService: getIt.get(),
+ settingsStore: getIt.get()));
getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get()));
diff --git a/lib/entities/contact.dart b/lib/entities/contact.dart
index cd4fa55a2..901993f43 100644
--- a/lib/entities/contact.dart
+++ b/lib/entities/contact.dart
@@ -7,7 +7,8 @@ part 'contact.g.dart';
@HiveType(typeId: Contact.typeId)
class Contact extends HiveObject with Keyable {
- Contact({required this.name, required this.address, CryptoCurrency? type}) {
+ Contact({required this.name, required this.address, CryptoCurrency? type, DateTime? lastChange})
+ : lastChange = lastChange ?? DateTime.now() {
if (type != null) {
raw = type.raw;
}
@@ -25,6 +26,9 @@ class Contact extends HiveObject with Keyable {
@HiveField(2, defaultValue: 0)
late int raw;
+ @HiveField(3)
+ DateTime lastChange;
+
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
@override
@@ -36,6 +40,5 @@ class Contact extends HiveObject with Keyable {
@override
int get hashCode => key.hashCode;
- void updateCryptoCurrency({required CryptoCurrency currency}) =>
- raw = currency.raw;
+ void updateCryptoCurrency({required CryptoCurrency currency}) => raw = currency.raw;
}
diff --git a/lib/entities/contact_record.dart b/lib/entities/contact_record.dart
index 50a432727..4b0e120ba 100644
--- a/lib/entities/contact_record.dart
+++ b/lib/entities/contact_record.dart
@@ -1,22 +1,21 @@
+import 'package:cake_wallet/entities/contact.dart';
+import 'package:cake_wallet/entities/contact_base.dart';
+import 'package:cake_wallet/entities/record.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
-import 'package:cake_wallet/entities/contact.dart';
-import 'package:cw_core/crypto_currency.dart';
-import 'package:cake_wallet/entities/record.dart';
-import 'package:cake_wallet/entities/contact_base.dart';
part 'contact_record.g.dart';
class ContactRecord = ContactRecordBase with _$ContactRecord;
-abstract class ContactRecordBase extends Record
- with Store
- implements ContactBase {
+abstract class ContactRecordBase extends Record with Store implements ContactBase {
ContactRecordBase(Box source, Contact original)
: name = original.name,
address = original.address,
type = original.type,
- super(source, original);
+ lastChange = original.lastChange,
+ super(source, original);
@override
@observable
@@ -30,14 +29,14 @@ abstract class ContactRecordBase extends Record
@observable
CryptoCurrency type;
+ DateTime? lastChange;
+
@override
void toBind(Contact original) {
reaction((_) => name, (String name) => original.name = name);
reaction((_) => address, (String address) => original.address = address);
- reaction(
- (_) => type,
- (CryptoCurrency currency) =>
- original.updateCryptoCurrency(currency: currency));
+ reaction((_) => type,
+ (CryptoCurrency currency) => original.updateCryptoCurrency(currency: currency));
}
@override
diff --git a/lib/entities/country.dart b/lib/entities/country.dart
new file mode 100644
index 000000000..63eee9a18
--- /dev/null
+++ b/lib/entities/country.dart
@@ -0,0 +1,386 @@
+import 'package:cw_core/enumerable_item.dart';
+import 'package:collection/collection.dart';
+
+class Country extends EnumerableItem with Serializable {
+ const Country({required String code, required this.fullName, required this.countryCode})
+ : super(title: fullName, raw: code);
+
+ final String fullName;
+ final String countryCode;
+
+ static List get all => _all.values.toList();
+
+ static const afghanistan = Country(code: 'afg', countryCode: 'AF', fullName: "Afghanistan");
+ static const andorra = Country(code: 'and', countryCode: 'AD', fullName: "Andorra");
+ static const angola = Country(code: 'ago', countryCode: 'AO', fullName: "Angola");
+ static const anguilla = Country(code: 'aia', countryCode: 'AI', fullName: "Anguilla");
+ static const antigua_and_barbuda =
+ Country(code: 'atg', countryCode: 'AG', fullName: "Antigua and Barbuda");
+ static const are = Country(code: 'are', countryCode: 'AE', fullName: "United Arab Emirates");
+ static const arg = Country(code: 'arg', countryCode: 'AR', fullName: "Argentina");
+ static const arm = Country(code: 'arm', countryCode: 'AM', fullName: "Armenia");
+ static const aruba = Country(code: 'abw', countryCode: 'AW', fullName: "Aruba");
+ static const aus = Country(code: 'aus', countryCode: 'AU', fullName: "Australia");
+ static const aut = Country(code: 'aut', countryCode: 'AT', fullName: "Austria");
+ static const aze = Country(code: 'aze', countryCode: 'AZ', fullName: "Azerbaijan");
+ static const belize = Country(code: 'blz', countryCode: 'BZ', fullName: "Belize");
+ static const bfa = Country(code: 'bfa', countryCode: 'BF', fullName: "Burkina Faso");
+ static const bel = Country(code: 'bel', countryCode: 'BE', fullName: "Belgium");
+ static const bgd = Country(code: 'bgd', countryCode: 'BD', fullName: "Bangladesh");
+ static const bhr = Country(code: 'bhr', countryCode: 'BH', fullName: "Bahrain");
+ static const bhs = Country(code: 'bhs', countryCode: 'BS', fullName: "Bahamas");
+ static const bhutan = Country(code: 'btn', countryCode: 'BT', fullName: "Bhutan");
+ static const bol = Country(code: 'bol', countryCode: 'BO', fullName: "Bolivia");
+ static const bra = Country(code: 'bra', countryCode: 'BR', fullName: "Brazil");
+ static const brn = Country(code: 'brn', countryCode: 'BN', fullName: "Brunei");
+ static const bwa = Country(code: 'bwa', countryCode: 'BW', fullName: "Botswana");
+ static const cad = Country(code: 'cad', countryCode: 'CA', fullName: "Canada");
+ static const che = Country(code: 'che', countryCode: 'CH', fullName: "Switzerland");
+ static const chl = Country(code: 'chl', countryCode: 'CL', fullName: "Chile");
+ static const chn = Country(code: 'chn', countryCode: 'CN', fullName: "China");
+ static const col = Country(code: 'col', countryCode: 'CO', fullName: "Colombia");
+ static const cri = Country(code: 'cri', countryCode: 'CR', fullName: "Costa Rica");
+ static const cyp = Country(code: 'cyp', countryCode: 'CY', fullName: "Cyprus");
+ static const czk = Country(code: 'czk', countryCode: 'CZ', fullName: "Czech Republic");
+ static const deu = Country(code: 'deu', countryCode: 'DE', fullName: "Germany");
+ static const dji = Country(code: 'dji', countryCode: 'DJ', fullName: "Djibouti");
+ static const dnk = Country(code: 'dnk', countryCode: 'DK', fullName: "Denmark");
+ static const dza = Country(code: 'dza', countryCode: 'DZ', fullName: "Algeria");
+ static const ecu = Country(code: 'ecu', countryCode: 'EC', fullName: "Ecuador");
+ static const egy = Country(code: 'egy', countryCode: 'EG', fullName: "Egypt");
+ static const esp = Country(code: 'esp', countryCode: 'ES', fullName: "Spain");
+ static const est = Country(code: 'est', countryCode: 'EE', fullName: "Estonia");
+ static const eur = Country(code: 'eur', countryCode: 'EU', fullName: "European Union");
+ static const fin = Country(code: 'fin', countryCode: 'FI', fullName: "Finland");
+ static const fji = Country(code: 'fji', countryCode: 'FJ', fullName: "Fiji");
+ static const flk = Country(code: 'flk', countryCode: 'FK', fullName: "Falkland Islands");
+ static const fra = Country(code: 'fra', countryCode: 'FR', fullName: "France");
+ static const fsm = Country(code: 'fsm', countryCode: 'FM', fullName: "Micronesia");
+ static const gab = Country(code: 'gab', countryCode: 'GA', fullName: "Gabon");
+ static const gbr = Country(code: 'gbr', countryCode: 'GB', fullName: "United Kingdom");
+ static const geo = Country(code: 'geo', countryCode: 'GE', fullName: "Georgia");
+ static const gha = Country(code: 'gha', countryCode: 'GH', fullName: "Ghana");
+ static const grc = Country(code: 'grc', countryCode: 'GR', fullName: "Greece");
+ static const grd = Country(code: 'grd', countryCode: 'GD', fullName: "Grenada");
+ static const grl = Country(code: 'grl', countryCode: 'GL', fullName: "Greenland");
+ static const gtm = Country(code: 'gtm', countryCode: 'GT', fullName: "Guatemala");
+ static const guy = Country(code: 'guy', countryCode: 'GY', fullName: "Guyana");
+ static const hkg = Country(code: 'hkg', countryCode: 'HK', fullName: "Hong Kong");
+ static const hrv = Country(code: 'hrv', countryCode: 'HR', fullName: "Croatia");
+ static const hun = Country(code: 'hun', countryCode: 'HU', fullName: "Hungary");
+ static const idn = Country(code: 'idn', countryCode: 'ID', fullName: "Indonesia");
+ static const ind = Country(code: 'ind', countryCode: 'IN', fullName: "India");
+ static const irl = Country(code: 'irl', countryCode: 'IE', fullName: "Ireland");
+ static const irn = Country(code: 'irn', countryCode: 'IR', fullName: "Iran");
+ static const isl = Country(code: 'isl', countryCode: 'IS', fullName: "Iceland");
+ static const isr = Country(code: 'isr', countryCode: 'IL', fullName: "Israel");
+ static const ita = Country(code: 'ita', countryCode: 'IT', fullName: "Italy");
+ static const jam = Country(code: 'jam', countryCode: 'JM', fullName: "Jamaica");
+ static const jor = Country(code: 'jor', countryCode: 'JO', fullName: "Jordan");
+ static const jpn = Country(code: 'jpn', countryCode: 'JP', fullName: "Japan");
+ static const kaz = Country(code: 'kaz', countryCode: 'KZ', fullName: "Kazakhstan");
+ static const ken = Country(code: 'ken', countryCode: 'KE', fullName: "Kenya");
+ static const kir = Country(code: 'kir', countryCode: 'KI', fullName: "Kiribati");
+ static const kor = Country(code: 'kor', countryCode: 'KR', fullName: "South Korea");
+ static const kwt = Country(code: 'kwt', countryCode: 'KW', fullName: "Kuwait");
+ static const lie = Country(code: 'lie', countryCode: 'LI', fullName: "Liechtenstein");
+ static const lka = Country(code: 'lka', countryCode: 'LK', fullName: "Sri Lanka");
+ static const ltu = Country(code: 'ltu', countryCode: 'LT', fullName: "Lithuania");
+ static const lux = Country(code: 'lux', countryCode: 'LU', fullName: "Luxembourg");
+ static const lva = Country(code: 'lva', countryCode: 'LV', fullName: "Latvia");
+ static const mar = Country(code: 'mar', countryCode: 'MA', fullName: "Morocco");
+ static const mex = Country(code: 'mex', countryCode: 'MX', fullName: "Mexico");
+ static const mlt = Country(code: 'mlt', countryCode: 'MT', fullName: "Malta");
+ static const mnp = Country(code: 'mnp', countryCode: 'MP', fullName: "Northern Mariana Islands");
+ static const mtq = Country(code: 'mtq', countryCode: 'MQ', fullName: "Martinique");
+ static const mys = Country(code: 'mys', countryCode: 'MY', fullName: "Malaysia");
+ static const mwi = Country(code: 'mwi', countryCode: 'MW', fullName: "Malawi");
+ static const nga = Country(code: 'nga', countryCode: 'NG', fullName: "Nigeria");
+ static const niu = Country(code: 'niu', countryCode: 'NU', fullName: "Niue");
+ static const nld = Country(code: 'nld', countryCode: 'NL', fullName: "Netherlands");
+ static const nor = Country(code: 'nor', countryCode: 'NO', fullName: "Norway");
+ static const nzl = Country(code: 'nzl', countryCode: 'NZ', fullName: "New Zealand");
+ static const omn = Country(code: 'omn', countryCode: 'OM', fullName: "Oman");
+ static const pak = Country(code: 'pak', countryCode: 'PK', fullName: "Pakistan");
+ static const per = Country(code: 'per', countryCode: 'PE', fullName: "Peru");
+ static const phl = Country(code: 'phl', countryCode: 'PH', fullName: "Philippines");
+ static const pol = Country(code: 'pol', countryCode: 'PL', fullName: "Poland");
+ static const pri = Country(code: 'pri', countryCode: 'PR', fullName: "Puerto Rico");
+ static const prt = Country(code: 'prt', countryCode: 'PT', fullName: "Portugal");
+ static const qat = Country(code: 'qat', countryCode: 'QA', fullName: "Qatar");
+ static const rou = Country(code: 'rou', countryCode: 'RO', fullName: "Romania");
+ static const rus = Country(code: 'rus', countryCode: 'RU', fullName: "Russia");
+ static const saf = Country(code: 'saf', countryCode: 'ZA', fullName: "South Africa");
+ static const sau = Country(code: 'sau', countryCode: 'SA', fullName: "Saudi Arabia");
+ static const sgp = Country(code: 'sgp', countryCode: 'SG', fullName: "Singapore");
+ static const slb = Country(code: 'slb', countryCode: 'SB', fullName: "Solomon Islands");
+ static const svk = Country(code: 'svk', countryCode: 'SK', fullName: "Slovakia");
+ static const svn = Country(code: 'svn', countryCode: 'SI', fullName: "Slovenia");
+ static const swe = Country(code: 'swe', countryCode: 'SE', fullName: "Sweden");
+ static const tha = Country(code: 'tha', countryCode: 'TH', fullName: "Thailand");
+ static const tkm = Country(code: 'tkm', countryCode: 'TM', fullName: "Turkmenistan");
+ static const ton = Country(code: 'ton', countryCode: 'TO', fullName: "Tonga");
+ static const tur = Country(code: 'tur', countryCode: 'TR', fullName: "Turkey");
+ static const tuv = Country(code: 'tuv', countryCode: 'TV', fullName: "Tuvalu");
+ static const twn = Country(code: 'twn', countryCode: 'TW', fullName: "Taiwan");
+ static const ukr = Country(code: 'ukr', countryCode: 'UA', fullName: "Ukraine");
+ static const ury = Country(code: 'ury', countryCode: 'UY', fullName: "Uruguay");
+ static const usa = Country(code: 'usa', countryCode: 'US', fullName: "USA");
+ static const ven = Country(code: 'ven', countryCode: 'VE', fullName: "Venezuela");
+ static const vnm = Country(code: 'vnm', countryCode: 'VN', fullName: "Vietnam");
+ static const vut = Country(code: 'vut', countryCode: 'VU', fullName: "Vanuatu");
+ static const btn = Country(code: 'btn', countryCode: 'BT', fullName: "Bhutan");
+ static const bgr = Country(code: 'bgr', countryCode: 'BG', fullName: "Bulgaria");
+ static const guf = Country(code: 'guf', countryCode: 'GF', fullName: "French Guiana");
+ static const bes = Country(code: 'bes', countryCode: 'BQ', fullName: "Caribbean Netherlands");
+ static const fro = Country(code: 'fro', countryCode: 'FO', fullName: "Faroe Islands");
+ static const cuw = Country(code: 'cuw', countryCode: 'CW', fullName: "Curacao");
+ static const msr = Country(code: 'msr', countryCode: 'MS', fullName: "Montserrat");
+ static const cpv = Country(code: 'cpv', countryCode: 'CV', fullName: "Cabo Verde");
+ static const nfk = Country(code: 'nfk', countryCode: 'NF', fullName: "Norfolk Island");
+ static const bmu = Country(code: 'bmu', countryCode: 'BM', fullName: "Bermuda");
+ static const vat = Country(code: 'vat', countryCode: 'VA', fullName: "Vatican City");
+ static const aia = Country(code: 'aia', countryCode: 'AI', fullName: "Anguilla");
+ static const gum = Country(code: 'gum', countryCode: 'GU', fullName: "Guam");
+ static const myt = Country(code: 'myt', countryCode: 'YT', fullName: "Mayotte");
+ static const mrt = Country(code: 'mrt', countryCode: 'MR', fullName: "Mauritania");
+ static const ggy = Country(code: 'ggy', countryCode: 'GG', fullName: "Guernsey");
+ static const cck = Country(code: 'cck', countryCode: 'CC', fullName: "Cocos (Keeling) Islands");
+ static const blz = Country(code: 'blz', countryCode: 'BZ', fullName: "Belize");
+ static const cxr = Country(code: 'cxr', countryCode: 'CX', fullName: "Christmas Island");
+ static const mco = Country(code: 'mco', countryCode: 'MC', fullName: "Monaco");
+ static const ner = Country(code: 'ner', countryCode: 'NE', fullName: "Niger");
+ static const jey = Country(code: 'jey', countryCode: 'JE', fullName: "Jersey");
+ static const asm = Country(code: 'asm', countryCode: 'AS', fullName: "American Samoa");
+ static const gmb = Country(code: 'gmb', countryCode: 'GM', fullName: "Gambia");
+ static const dma = Country(code: 'dma', countryCode: 'DM', fullName: "Dominica");
+ static const glp = Country(code: 'glp', countryCode: 'GP', fullName: "Guadeloupe");
+ static const ggi = Country(code: 'ggi', countryCode: 'GI', fullName: "Gibraltar");
+ static const cmr = Country(code: 'cmr', countryCode: 'CM', fullName: "Cameroon");
+ static const atg = Country(code: 'atg', countryCode: 'AG', fullName: "Antigua and Barbuda");
+ static const slv = Country(code: 'slv', countryCode: 'SV', fullName: "El Salvador");
+ static const pyf = Country(code: 'pyf', countryCode: 'PF', fullName: "French Polynesia");
+ static const iot =
+ Country(code: 'iot', countryCode: 'IO', fullName: "British Indian Ocean Territory");
+ static const vir = Country(code: 'vir', countryCode: 'VI', fullName: "Virgin Islands (U.S.)");
+ static const abw = Country(code: 'abw', countryCode: 'AW', fullName: "Aruba");
+ static const ago = Country(code: 'ago', countryCode: 'AO', fullName: "Angola");
+ static const afg = Country(code: 'afg', countryCode: 'AF', fullName: "Afghanistan");
+ static const lbn = Country(code: 'lbn', countryCode: 'LB', fullName: "Lebanon");
+ static const hmd =
+ Country(code: 'hmd', countryCode: 'HM', fullName: "Heard Island and McDonald Islands");
+ static const cok = Country(code: 'cok', countryCode: 'CK', fullName: "Cook Islands");
+ static const bvt = Country(code: 'bvt', countryCode: 'BV', fullName: "Bouvet Island");
+ static const atf =
+ Country(code: 'atf', countryCode: 'TF', fullName: "French Southern Territories");
+ static const eth = Country(code: 'eth', countryCode: 'ET', fullName: "Ethiopia");
+ static const plw = Country(code: 'plw', countryCode: 'PW', fullName: "Palau");
+ static const ata = Country(code: 'ata', countryCode: 'AQ', fullName: "Antarctica");
+
+ static final _all = {
+ Country.afghanistan.raw: Country.afghanistan,
+ Country.andorra.raw: Country.andorra,
+ Country.angola.raw: Country.angola,
+ Country.anguilla.raw: Country.anguilla,
+ Country.antigua_and_barbuda.raw: Country.antigua_and_barbuda,
+ Country.are.raw: Country.are,
+ Country.arg.raw: Country.arg,
+ Country.arm.raw: Country.arm,
+ Country.aruba.raw: Country.aruba,
+ Country.aus.raw: Country.aus,
+ Country.aut.raw: Country.aut,
+ Country.aze.raw: Country.aze,
+ Country.belize.raw: Country.belize,
+ Country.bfa.raw: Country.bfa,
+ Country.bel.raw: Country.bel,
+ Country.bgd.raw: Country.bgd,
+ Country.bhr.raw: Country.bhr,
+ Country.bhs.raw: Country.bhs,
+ Country.bhutan.raw: Country.bhutan,
+ Country.bol.raw: Country.bol,
+ Country.bra.raw: Country.bra,
+ Country.brn.raw: Country.brn,
+ Country.bwa.raw: Country.bwa,
+ Country.cad.raw: Country.cad,
+ Country.che.raw: Country.che,
+ Country.chl.raw: Country.chl,
+ Country.chn.raw: Country.chn,
+ Country.col.raw: Country.col,
+ Country.cri.raw: Country.cri,
+ Country.cyp.raw: Country.cyp,
+ Country.czk.raw: Country.czk,
+ Country.deu.raw: Country.deu,
+ Country.dji.raw: Country.dji,
+ Country.dnk.raw: Country.dnk,
+ Country.dza.raw: Country.dza,
+ Country.ecu.raw: Country.ecu,
+ Country.egy.raw: Country.egy,
+ Country.esp.raw: Country.esp,
+ Country.est.raw: Country.est,
+ Country.eur.raw: Country.eur,
+ Country.fin.raw: Country.fin,
+ Country.fji.raw: Country.fji,
+ Country.flk.raw: Country.flk,
+ Country.fra.raw: Country.fra,
+ Country.fsm.raw: Country.fsm,
+ Country.gab.raw: Country.gab,
+ Country.gbr.raw: Country.gbr,
+ Country.geo.raw: Country.geo,
+ Country.gha.raw: Country.gha,
+ Country.grc.raw: Country.grc,
+ Country.grd.raw: Country.grd,
+ Country.grl.raw: Country.grl,
+ Country.gtm.raw: Country.gtm,
+ Country.guy.raw: Country.guy,
+ Country.hkg.raw: Country.hkg,
+ Country.hrv.raw: Country.hrv,
+ Country.hun.raw: Country.hun,
+ Country.idn.raw: Country.idn,
+ Country.ind.raw: Country.ind,
+ Country.irl.raw: Country.irl,
+ Country.irn.raw: Country.irn,
+ Country.isl.raw: Country.isl,
+ Country.isr.raw: Country.isr,
+ Country.ita.raw: Country.ita,
+ Country.jam.raw: Country.jam,
+ Country.jor.raw: Country.jor,
+ Country.jpn.raw: Country.jpn,
+ Country.kaz.raw: Country.kaz,
+ Country.ken.raw: Country.ken,
+ Country.kir.raw: Country.kir,
+ Country.kor.raw: Country.kor,
+ Country.kwt.raw: Country.kwt,
+ Country.lie.raw: Country.lie,
+ Country.lka.raw: Country.lka,
+ Country.ltu.raw: Country.ltu,
+ Country.lux.raw: Country.lux,
+ Country.lva.raw: Country.lva,
+ Country.mar.raw: Country.mar,
+ Country.mex.raw: Country.mex,
+ Country.mlt.raw: Country.mlt,
+ Country.mnp.raw: Country.mnp,
+ Country.mtq.raw: Country.mtq,
+ Country.mys.raw: Country.mys,
+ Country.mwi.raw: Country.mwi,
+ Country.nga.raw: Country.nga,
+ Country.niu.raw: Country.niu,
+ Country.nld.raw: Country.nld,
+ Country.nor.raw: Country.nor,
+ Country.nzl.raw: Country.nzl,
+ Country.omn.raw: Country.omn,
+ Country.pak.raw: Country.pak,
+ Country.per.raw: Country.per,
+ Country.phl.raw: Country.phl,
+ Country.pol.raw: Country.pol,
+ Country.pri.raw: Country.pri,
+ Country.prt.raw: Country.prt,
+ Country.qat.raw: Country.qat,
+ Country.rou.raw: Country.rou,
+ Country.rus.raw: Country.rus,
+ Country.saf.raw: Country.saf,
+ Country.sau.raw: Country.sau,
+ Country.sgp.raw: Country.sgp,
+ Country.slb.raw: Country.slb,
+ Country.svk.raw: Country.svk,
+ Country.svn.raw: Country.svn,
+ Country.swe.raw: Country.swe,
+ Country.tha.raw: Country.tha,
+ Country.tkm.raw: Country.tkm,
+ Country.ton.raw: Country.ton,
+ Country.tur.raw: Country.tur,
+ Country.tuv.raw: Country.tuv,
+ Country.twn.raw: Country.twn,
+ Country.ukr.raw: Country.ukr,
+ Country.ury.raw: Country.ury,
+ Country.usa.raw: Country.usa,
+ Country.ven.raw: Country.ven,
+ Country.vnm.raw: Country.vnm,
+ Country.vut.raw: Country.vut,
+ Country.btn.raw: Country.btn,
+ Country.bgr.raw: Country.bgr,
+ Country.guf.raw: Country.guf,
+ Country.bes.raw: Country.bes,
+ Country.fro.raw: Country.fro,
+ Country.cuw.raw: Country.cuw,
+ Country.msr.raw: Country.msr,
+ Country.cpv.raw: Country.cpv,
+ Country.nfk.raw: Country.nfk,
+ Country.bmu.raw: Country.bmu,
+ Country.vat.raw: Country.vat,
+ Country.aia.raw: Country.aia,
+ Country.gum.raw: Country.gum,
+ Country.myt.raw: Country.myt,
+ Country.mrt.raw: Country.mrt,
+ Country.ggy.raw: Country.ggy,
+ Country.cck.raw: Country.cck,
+ Country.blz.raw: Country.blz,
+ Country.cxr.raw: Country.cxr,
+ Country.mco.raw: Country.mco,
+ Country.ner.raw: Country.ner,
+ Country.jey.raw: Country.jey,
+ Country.asm.raw: Country.asm,
+ Country.gmb.raw: Country.gmb,
+ Country.dma.raw: Country.dma,
+ Country.glp.raw: Country.glp,
+ Country.ggi.raw: Country.ggi,
+ Country.cmr.raw: Country.cmr,
+ Country.atg.raw: Country.atg,
+ Country.slv.raw: Country.slv,
+ Country.pyf.raw: Country.pyf,
+ Country.iot.raw: Country.iot,
+ Country.vir.raw: Country.vir,
+ Country.abw.raw: Country.abw,
+ Country.ago.raw: Country.ago,
+ Country.afg.raw: Country.afg,
+ Country.lbn.raw: Country.lbn,
+ Country.hmd.raw: Country.hmd,
+ Country.cok.raw: Country.cok,
+ Country.bvt.raw: Country.bvt,
+ Country.atf.raw: Country.atf,
+ Country.eth.raw: Country.eth,
+ Country.plw.raw: Country.plw,
+ Country.ata.raw: Country.ata,
+ };
+
+ static final Map _cakePayNames = {
+ 'Slovak Republic': 'Slovakia',
+ 'Brunei Darussalam': 'Brunei',
+ 'Federated States of Micronesia': 'Micronesia',
+ 'Sri lanka': 'Sri Lanka',
+ 'UAE': 'United Arab Emirates',
+ 'UK': 'United Kingdom',
+ 'Curaçao': "Curacao",
+ };
+
+ static Country deserialize({required String raw}) => _all[raw]!;
+
+ static final Map countryByName = {
+ for (var country in _all.values) country.fullName: country,
+ };
+
+ static Country? fromCakePayName(String name) {
+ final normalizedName = _cakePayNames[name] ?? name;
+ return countryByName[normalizedName];
+ }
+
+ static String getCakePayName(Country country) {
+ return _cakePayNames.entries
+ .firstWhere(
+ (entry) => entry.value == country.fullName,
+ orElse: () => MapEntry(country.fullName, country.fullName),
+ )
+ .key;
+ }
+
+ static Country? fromCode(String countryCode) {
+ return _all.values.firstWhereOrNull((element) => element.raw == countryCode.toLowerCase());
+ }
+
+ @override
+ bool operator ==(Object other) => other is Country && other.raw == raw;
+
+ @override
+ int get hashCode => raw.hashCode ^ title.hashCode;
+
+ String get iconPath => "assets/images/flags/$raw.png";
+}
diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart
index a8e2829d8..1a031ee82 100644
--- a/lib/entities/fiat_currency.dart
+++ b/lib/entities/fiat_currency.dart
@@ -114,7 +114,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl
FiatCurrency.tur.raw: FiatCurrency.tur,
};
- static FiatCurrency deserialize({required String raw}) => _all[raw]!;
+ static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd;
@override
bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart
index 4fbe358e5..0bb526e5d 100644
--- a/lib/entities/preferences_key.dart
+++ b/lib/entities/preferences_key.dart
@@ -12,6 +12,7 @@ class PreferencesKey {
static const currentBananoNodeIdKey = 'current_node_id_banano';
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
static const currentFiatCurrencyKey = 'current_fiat_currency';
+ static const currentCakePayCountry = 'current_cake_pay_country';
static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
static const currentSolanaNodeIdKey = 'current_node_id_sol';
static const currentTronNodeIdKey = 'current_node_id_trx';
@@ -25,7 +26,9 @@ class PreferencesKey {
static const disableBulletinKey = 'disable_bulletin';
static const defaultBuyProvider = 'default_buy_provider';
static const walletListOrder = 'wallet_list_order';
+ static const contactListOrder = 'contact_list_order';
static const walletListAscending = 'wallet_list_ascending';
+ static const contactListAscending = 'contact_list_ascending';
static const currentFiatApiModeKey = 'current_fiat_api_mode';
static const failedTotpTokenTrials = 'failed_token_trials';
static const disableExchangeKey = 'disable_exchange';
@@ -52,6 +55,7 @@ class PreferencesKey {
static const mwebEnabled = 'mwebEnabled';
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
static const mwebAlwaysScan = 'mwebAlwaysScan';
+ static const mwebNodeUri = 'mwebNodeUri';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning';
diff --git a/lib/entities/wallet_list_order_types.dart b/lib/entities/wallet_list_order_types.dart
index f848170f4..751569e9e 100644
--- a/lib/entities/wallet_list_order_types.dart
+++ b/lib/entities/wallet_list_order_types.dart
@@ -1,6 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
-enum WalletListOrderType {
+enum FilterListOrderType {
CreationDate,
Alphabetical,
GroupByType,
@@ -9,13 +9,13 @@ enum WalletListOrderType {
@override
String toString() {
switch (this) {
- case WalletListOrderType.CreationDate:
+ case FilterListOrderType.CreationDate:
return S.current.creation_date;
- case WalletListOrderType.Alphabetical:
+ case FilterListOrderType.Alphabetical:
return S.current.alphabetical;
- case WalletListOrderType.GroupByType:
+ case FilterListOrderType.GroupByType:
return S.current.group_by_type;
- case WalletListOrderType.Custom:
+ case FilterListOrderType.Custom:
return S.current.custom_drag;
}
}
diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart
index 8f2d5c241..5eeb6f9cf 100644
--- a/lib/exchange/provider/exolix_exchange_provider.dart
+++ b/lib/exchange/provider/exolix_exchange_provider.dart
@@ -141,8 +141,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
'coinTo': _normalizeCurrency(request.toCurrency),
'networkFrom': _networkFor(request.fromCurrency),
'networkTo': _networkFor(request.toCurrency),
- 'withdrawalAddress': request.toAddress,
- 'refundAddress': request.refundAddress,
+ 'withdrawalAddress': _normalizeAddress(request.toAddress),
+ 'refundAddress': _normalizeAddress(request.refundAddress),
'rateType': _getRateType(isFixedRateMode),
'apiToken': apiKey,
};
@@ -275,4 +275,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
return tag;
}
}
+
+ String _normalizeAddress(String address) =>
+ address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}
diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart
index be52b73fe..2a72ac793 100644
--- a/lib/exchange/provider/simpleswap_exchange_provider.dart
+++ b/lib/exchange/provider/simpleswap_exchange_provider.dart
@@ -129,8 +129,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
"currency_to": _normalizeCurrency(request.toCurrency),
"amount": request.fromAmount,
"fixed": isFixedRateMode,
- "user_refund_address": request.refundAddress,
- "address_to": request.toAddress
+ "user_refund_address": _normalizeAddress(request.refundAddress),
+ "address_to": _normalizeAddress(request.toAddress)
};
final uri = Uri.https(apiAuthority, createExchangePath, params);
@@ -243,4 +243,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
return currency.title.toLowerCase();
}
}
+
+ String _normalizeAddress(String address) =>
+ address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}
diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart
index 601735595..123f759ef 100644
--- a/lib/exchange/provider/stealth_ex_exchange_provider.dart
+++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart
@@ -129,8 +129,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
if (isFixedRateMode) 'rate_id': rateId,
'amount':
isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
- 'address': request.toAddress,
- 'refund_address': request.refundAddress,
+ 'address': _normalizeAddress(request.toAddress),
+ 'refund_address': _normalizeAddress(request.refundAddress),
'additional_fee_percent': _additionalFeePercent,
};
@@ -296,4 +296,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
return currency.tag!.toLowerCase();
}
+
+ String _normalizeAddress(String address) =>
+ address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}
diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart
index 897c2fdb9..99b9dcf9f 100644
--- a/lib/exchange/provider/thorchain_exchange.provider.dart
+++ b/lib/exchange/provider/thorchain_exchange.provider.dart
@@ -116,9 +116,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
required bool isFixedRateMode,
required bool isSendAll,
}) async {
- String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
- ? request.toAddress.replaceFirst('bitcoincash:', '')
- : request.toAddress;
+
final formattedFromAmount = double.parse(request.fromAmount);
@@ -126,11 +124,11 @@ class ThorChainExchangeProvider extends ExchangeProvider {
'from_asset': _normalizeCurrency(request.fromCurrency),
'to_asset': _normalizeCurrency(request.toCurrency),
'amount': _doubleToThorChainString(formattedFromAmount),
- 'destination': formattedToAddress,
+ 'destination': _normalizeAddress(request.toAddress),
'affiliate': _affiliateName,
'affiliate_bps': _affiliateBps,
'refund_address':
- isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '',
+ isRefundAddressSupported.contains(request.fromCurrency) ? _normalizeAddress(request.refundAddress) : '',
};
final responseJSON = await _getSwapQuote(params);
@@ -288,4 +286,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
return currentState;
}
+
+ String _normalizeAddress(String address) =>
+ address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}
diff --git a/lib/router.dart b/lib/router.dart
index 3b4c38546..6c234ff80 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -72,6 +72,8 @@ import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settin
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
+import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
+import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_settings.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@@ -461,6 +463,14 @@ Route createRoute(RouteSettings settings) {
return CupertinoPageRoute(
fullscreenDialog: true, builder: (_) => getIt.get());
+ case Routes.mwebLogs:
+ return CupertinoPageRoute(
+ fullscreenDialog: true, builder: (_) => getIt.get());
+
+ case Routes.mwebNode:
+ return CupertinoPageRoute(
+ fullscreenDialog: true, builder: (_) => getIt.get());
+
case Routes.connectionSync:
return CupertinoPageRoute(
fullscreenDialog: true, builder: (_) => getIt.get());
diff --git a/lib/routes.dart b/lib/routes.dart
index 0529d7c6f..5f11b11a3 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -74,6 +74,8 @@ class Routes {
static const webViewPage = '/web_view_page';
static const silentPaymentsSettings = '/silent_payments_settings';
static const mwebSettings = '/mweb_settings';
+ static const mwebLogs = '/mweb_logs';
+ static const mwebNode = '/mweb_node';
static const connectionSync = '/connection_sync_page';
static const securityBackupPage = '/security_and_backup_page';
static const privacyPage = '/privacy_page';
diff --git a/lib/src/screens/InfoPage.dart b/lib/src/screens/InfoPage.dart
index 5398df22c..84b9e8632 100644
--- a/lib/src/screens/InfoPage.dart
+++ b/lib/src/screens/InfoPage.dart
@@ -21,6 +21,7 @@ abstract class InfoPage extends BasePage {
String get pageTitle;
String get pageDescription;
String get buttonText;
+ Key? get buttonKey;
void Function(BuildContext) get onPressed;
@override
@@ -39,15 +40,14 @@ abstract class InfoPage extends BasePage {
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: ConstrainedBox(
- constraints: BoxConstraints(
- maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
+ constraints:
+ BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ConstrainedBox(
- constraints: BoxConstraints(
- maxHeight: MediaQuery.of(context).size.height * 0.3),
+ constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image),
),
),
@@ -61,14 +61,13 @@ abstract class InfoPage extends BasePage {
height: 1.7,
fontSize: 14,
fontWeight: FontWeight.normal,
- color: Theme.of(context)
- .extension()!
- .secondaryTextColor,
+ color: Theme.of(context).extension()!.secondaryTextColor,
),
),
),
),
PrimaryButton(
+ key: buttonKey,
onPressed: () => onPressed(context),
text: buttonText,
color: Theme.of(context).primaryColor,
diff --git a/lib/src/screens/cake_pay/cards/cake_pay_cards_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_cards_page.dart
index 35a58ce0a..31eaa23ff 100644
--- a/lib/src/screens/cake_pay/cards/cake_pay_cards_page.dart
+++ b/lib/src/screens/cake_pay/cards/cake_pay_cards_page.dart
@@ -1,5 +1,6 @@
import 'package:cake_wallet/cake_pay/cake_pay_states.dart';
import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart';
+import 'package:cake_wallet/entities/country.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -8,6 +9,7 @@ import 'package:cake_wallet/src/screens/cake_pay/widgets/card_menu.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_widget.dart';
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
+import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
@@ -20,6 +22,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:mobx/mobx.dart';
class CakePayCardsPage extends BasePage {
CakePayCardsPage(this._cardsListViewModel) : searchFocusNode = FocusNode() {
@@ -80,9 +83,25 @@ class CakePayCardsPage extends BasePage {
@override
Widget body(BuildContext context) {
+
+ if (_cardsListViewModel.settingsStore.selectedCakePayCountry == null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ reaction((_) => _cardsListViewModel.shouldShowCountryPicker, (bool shouldShowCountryPicker) async {
+ if (shouldShowCountryPicker) {
+ _cardsListViewModel.storeInitialFilterStates();
+ await showCountryPicker(context, _cardsListViewModel);
+ if (_cardsListViewModel.hasFiltersChanged) {
+ _cardsListViewModel.resetLoadingNextPageState();
+ _cardsListViewModel.getVendors();
+ }
+ }
+ });
+ });
+ }
+
final filterButton = Semantics(
label: S.of(context).filter_by,
- child: InkWell(
+ child: GestureDetector(
onTap: () async {
_cardsListViewModel.storeInitialFilterStates();
await showFilterWidget(context);
@@ -92,50 +111,87 @@ class CakePayCardsPage extends BasePage {
}
},
child: Container(
- width: 32,
- padding: EdgeInsets.all(8),
- decoration: BoxDecoration(
- color: Theme.of(context).extension()!.syncedBackgroundColor,
- border: Border.all(
- color: Colors.white.withOpacity(0.2),
+ width: 32,
+ padding: EdgeInsets.all(8),
+ decoration: BoxDecoration(
+ color: Theme.of(context).extension()!.syncedBackgroundColor,
+ border: Border.all(
+ color: Colors.transparent,
+ ),
+ borderRadius: BorderRadius.circular(10),
),
- borderRadius: BorderRadius.circular(10),
+ child: Image.asset(
+ 'assets/images/filter.png',
+ color: Theme.of(context).extension()!.iconColor,
+ ))),
+ );
+ final _countryPicker = Semantics(
+ label: S.of(context).filter_by,
+ child: GestureDetector(
+ onTap: () async {
+ _cardsListViewModel.storeInitialFilterStates();
+ await showCountryPicker(context, _cardsListViewModel);
+ if (_cardsListViewModel.hasFiltersChanged) {
+ _cardsListViewModel.resetLoadingNextPageState();
+ _cardsListViewModel.getVendors();
+ }
+ },
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 6),
+ decoration: BoxDecoration(
+ color: Theme.of(context).extension()!.syncedBackgroundColor,
+ border: Border.all(color: Colors.transparent),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Container(
+ margin: EdgeInsets.symmetric(vertical: 2),
+ child: Row(
+ children: [
+ Image.asset(
+ _cardsListViewModel.selectedCountry.iconPath,
+ width: 24,
+ height: 24,
+ errorBuilder: (context, error, stackTrace) => Container(
+ width: 24,
+ height: 24,
+ ),
+ ),
+ SizedBox(width: 6),
+ Text(
+ _cardsListViewModel.selectedCountry.countryCode,
+ style: TextStyle(
+ color: Theme.of(context).extension()!.textColor,
+ fontSize: 16,
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ],
),
- child: Image.asset(
- 'assets/images/filter.png',
- color: Theme.of(context).extension()!.iconColor,
- ),
- )),
+ ),
+ ),
+ ),
);
return Padding(
- padding: const EdgeInsets.all(14.0),
- child: Column(
- children: [
+ padding: const EdgeInsets.all(14.0),
+ child: Column(children: [
Container(
- padding: EdgeInsets.only(left: 2, right: 22),
- height: 32,
- child: Row(
- children: [
+ padding: EdgeInsets.only(left: 2, right: 22),
+ height: 32,
+ child: Row(children: [
Expanded(
child: _SearchWidget(
controller: _searchController,
focusNode: searchFocusNode,
)),
- SizedBox(width: 10),
- filterButton
- ],
- ),
- ),
+ SizedBox(width: 5),
+ filterButton,
+ SizedBox(width: 5),
+ _countryPicker
+ ])),
SizedBox(height: 8),
- Expanded(
- child: CakePayCardsPageBody(
- cardsListViewModel: _cardsListViewModel,
- ),
- ),
- ],
- ),
- );
+ Expanded(child: CakePayCardsPageBody(cardsListViewModel: _cardsListViewModel))
+ ]));
}
Future showFilterWidget(BuildContext context) async {
@@ -148,6 +204,32 @@ class CakePayCardsPage extends BasePage {
}
}
+
+Future showCountryPicker(BuildContext context, CakePayCardsListViewModel cardsListViewModel) async {
+ await showPopUp(
+ context: context,
+ builder: (_) => Picker(
+ title: S.of(context).select_your_country,
+ items: cardsListViewModel.availableCountries,
+ images: cardsListViewModel.availableCountries
+ .map((e) => Image.asset(
+ e.iconPath,
+ errorBuilder: (context, error, stackTrace) => Container(
+ width: 58,
+ height: 58,
+ ),
+ ))
+ .toList(),
+ selectedAtIndex: cardsListViewModel.availableCountries
+ .indexOf(cardsListViewModel.selectedCountry),
+ onItemSelected: (Country country) =>
+ cardsListViewModel.setSelectedCountry(country),
+ isSeparated: false,
+ hintText: S.of(context).search,
+ matchingCriteria: (Country country, String searchText) =>
+ country.fullName.toLowerCase().contains(searchText.toLowerCase())));
+}
+
class CakePayCardsPageBody extends StatefulWidget {
const CakePayCardsPageBody({
Key? key,
@@ -304,15 +386,9 @@ class _SearchWidget extends StatelessWidget {
alignLabelWithHint: true,
floatingLabelBehavior: FloatingLabelBehavior.never,
suffixIcon: searchIcon,
- border: OutlineInputBorder(
- borderSide: BorderSide(
- color: Colors.white.withOpacity(0.2),
- ),
- borderRadius: BorderRadius.circular(10),
- ),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
- color: Colors.white.withOpacity(0.2),
+ color: Colors.transparent,
),
borderRadius: BorderRadius.circular(10),
),
diff --git a/lib/src/screens/cake_pay/widgets/card_item.dart b/lib/src/screens/cake_pay/widgets/card_item.dart
index ce804adc2..1234c0a1f 100644
--- a/lib/src/screens/cake_pay/widgets/card_item.dart
+++ b/lib/src/screens/cake_pay/widgets/card_item.dart
@@ -9,7 +9,7 @@ class CardItem extends StatelessWidget {
required this.backgroundColor,
required this.titleColor,
required this.subtitleColor,
- this.hideBorder = false,
+ this.hideBorder = true,
this.discount = 0.0,
this.isAmount = false,
this.discountBackground,
diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart
index 99f2aa251..130380b09 100644
--- a/lib/src/screens/contact/contact_list_page.dart
+++ b/lib/src/screens/contact/contact_list_page.dart
@@ -1,21 +1,24 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/contact_record.dart';
+import 'package:cake_wallet/entities/wallet_list_order_types.dart';
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/routes.dart';
+import 'package:cake_wallet/src/screens/base_page.dart';
+import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
+import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:flutter_slidable/flutter_slidable.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
-import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
-import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_slidable/flutter_slidable.dart';
class ContactListPage extends BasePage {
ContactListPage(this.contactListViewModel, this.authService);
@@ -74,45 +77,101 @@ class ContactListPage extends BasePage {
}
@override
- Widget body(BuildContext context) {
- return Container(
- padding: EdgeInsets.all(20.0),
- child: Observer(builder: (_) {
- final contacts = contactListViewModel.contactsToShow;
- final walletContacts = contactListViewModel.walletContactsToShow;
- return CollapsibleSectionList(
- sectionCount: 2,
- sectionTitleBuilder: (int sectionIndex) {
- var title = S.current.contact_list_contacts;
+ Widget body(BuildContext context) => ContactPageBody(contactListViewModel: contactListViewModel);
+}
- if (sectionIndex == 0) {
- title = S.current.contact_list_wallets;
- }
+class ContactPageBody extends StatefulWidget {
+ const ContactPageBody({required this.contactListViewModel});
- return Container(
- padding: EdgeInsets.only(bottom: 10),
- child: Text(title, style: TextStyle(fontSize: 36)));
- },
- itemCounter: (int sectionIndex) =>
- sectionIndex == 0 ? walletContacts.length : contacts.length,
- itemBuilder: (int sectionIndex, index) {
- if (sectionIndex == 0) {
- final walletInfo = walletContacts[index];
- return generateRaw(context, walletInfo);
- }
+ final ContactListViewModel contactListViewModel;
- final contact = contacts[index];
- final content = generateRaw(context, contact);
- return contactListViewModel.isEditable
- ? Slidable(
- key: Key('${contact.key}'),
- endActionPane: _actionPane(context, contact),
- child: content,
- )
- : content;
- },
- );
- }));
+ @override
+ State createState() => _ContactPageBodyState();
+}
+
+class _ContactPageBodyState extends State with SingleTickerProviderStateMixin {
+ late TabController _tabController;
+
+ @override
+ void initState() {
+ super.initState();
+ _tabController = TabController(length: 2, vsync: this);
+ }
+
+ @override
+ void dispose() {
+ _tabController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(left: 24),
+ child: Column(
+ children: [
+ Align(
+ alignment: Alignment.centerLeft,
+ child: TabBar(
+ controller: _tabController,
+ splashFactory: NoSplash.splashFactory,
+ indicatorSize: TabBarIndicatorSize.label,
+ isScrollable: true,
+ labelStyle: TextStyle(
+ fontSize: 18,
+ fontFamily: 'Lato',
+ fontWeight: FontWeight.w600,
+ color: Theme.of(context).appBarTheme.titleTextStyle!.color,
+ ),
+ unselectedLabelStyle: TextStyle(
+ fontSize: 18,
+ fontFamily: 'Lato',
+ fontWeight: FontWeight.w600,
+ color: Theme.of(context).appBarTheme.titleTextStyle!.color?.withOpacity(0.5)),
+ labelColor: Theme.of(context).appBarTheme.titleTextStyle!.color,
+ indicatorColor: Theme.of(context).appBarTheme.titleTextStyle!.color,
+ indicatorPadding: EdgeInsets.zero,
+ labelPadding: EdgeInsets.only(right: 24),
+ tabAlignment: TabAlignment.center,
+ dividerColor: Colors.transparent,
+ padding: EdgeInsets.zero,
+ tabs: [
+ Tab(text: S.of(context).wallets),
+ Tab(text: S.of(context).contact_list_contacts),
+ ],
+ ),
+ ),
+ Expanded(
+ child: TabBarView(
+ controller: _tabController,
+ children: [
+ _buildWalletContacts(context),
+ ContactListBody(
+ contactListViewModel: widget.contactListViewModel,
+ tabController: _tabController),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildWalletContacts(BuildContext context) {
+ final walletContacts = widget.contactListViewModel.walletContactsToShow;
+
+ return ListView.builder(
+ shrinkWrap: true,
+ itemCount: walletContacts.length * 2,
+ itemBuilder: (context, index) {
+ if (index.isOdd) {
+ return StandardListSeparator();
+ } else {
+ final walletInfo = walletContacts[index ~/ 2];
+ return generateRaw(context, walletInfo);
+ }
+ },
+ );
}
Widget generateRaw(BuildContext context, ContactBase contact) {
@@ -123,7 +182,7 @@ class ContactListPage extends BasePage {
return GestureDetector(
onTap: () async {
- if (!contactListViewModel.isEditable) {
+ if (!widget.contactListViewModel.isEditable) {
Navigator.of(context).pop(contact);
return;
}
@@ -143,8 +202,7 @@ class ContactListPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.start,
children: [
currencyIcon,
- Expanded(
- child: Padding(
+ Padding(
padding: EdgeInsets.only(left: 12),
child: Text(
contact.name,
@@ -154,13 +212,215 @@ class ContactListPage extends BasePage {
color: Theme.of(context).extension()!.titleColor,
),
),
- ))
+ ),
],
),
),
);
}
+ Future showNameAndAddressDialog(BuildContext context, String name, String address) async {
+ return await showPopUp(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertWithTwoActions(
+ alertTitle: name,
+ alertContent: address,
+ rightButtonText: S.of(context).copy,
+ leftButtonText: S.of(context).cancel,
+ actionRightButton: () => Navigator.of(context).pop(true),
+ actionLeftButton: () => Navigator.of(context).pop(false));
+ }) ??
+ false;
+ }
+}
+
+class ContactListBody extends StatefulWidget {
+ ContactListBody({required this.contactListViewModel, required this.tabController});
+
+ final ContactListViewModel contactListViewModel;
+ final TabController tabController;
+
+ @override
+ State createState() => _ContactListBodyState();
+}
+
+class _ContactListBodyState extends State {
+ bool _isContactsTabActive = false;
+
+ @override
+ void initState() {
+ super.initState();
+ widget.tabController.addListener(_handleTabChange);
+ }
+
+ void _handleTabChange() {
+ setState(() {
+ _isContactsTabActive = widget.tabController.index == 1;
+ });
+ }
+
+ @override
+ void dispose() {
+ widget.tabController.removeListener(_handleTabChange);
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final contacts = widget.contactListViewModel.contacts;
+ return Scaffold(
+ body: Container(
+ child: FilteredList(
+ list: contacts,
+ updateFunction: widget.contactListViewModel.reorderAccordingToContactList,
+ canReorder: widget.contactListViewModel.isEditable,
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final contact = contacts[index];
+ final contactContent =
+ generateContactRaw(context, contact, contacts.length == index + 1);
+ return GestureDetector(
+ key: Key('${contact.name}'),
+ onTap: () async {
+ if (!widget.contactListViewModel.isEditable) {
+ Navigator.of(context).pop(contact);
+ return;
+ }
+
+ final isCopied =
+ await showNameAndAddressDialog(context, contact.name, contact.address);
+
+ if (isCopied) {
+ await Clipboard.setData(ClipboardData(text: contact.address));
+ await showBar(context, S.of(context).copied_to_clipboard);
+ }
+ },
+ behavior: HitTestBehavior.opaque,
+ child: widget.contactListViewModel.isEditable
+ ? Slidable(
+ key: Key('${contact.key}'),
+ endActionPane: _actionPane(context, contact),
+ child: contactContent)
+ : contactContent,
+ );
+ },
+ ),
+ ),
+ floatingActionButton:
+ _isContactsTabActive ? filterButtonWidget(context, widget.contactListViewModel) : null,
+ );
+ }
+
+ Widget generateContactRaw(BuildContext context, ContactRecord contact, bool isLast) {
+ final image = contact.type.iconPath;
+ final currencyIcon = image != null
+ ? Image.asset(image, height: 24, width: 24)
+ : const SizedBox(height: 24, width: 24);
+ return Column(
+ children: [
+ Container(
+ key: Key('${contact.name}'),
+ padding: const EdgeInsets.only(top: 16, bottom: 16, right: 24),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ currencyIcon,
+ Expanded(
+ child: Padding(
+ padding: EdgeInsets.only(left: 12),
+ child: Text(
+ contact.name,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ color: Theme.of(context).extension()!.titleColor,
+ ),
+ ),
+ ))
+ ],
+ ),
+ ),
+ StandardListSeparator()
+ ],
+ );
+ }
+
+ ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
+ motion: const ScrollMotion(),
+ extentRatio: 0.4,
+ children: [
+ SlidableAction(
+ onPressed: (_) async => await Navigator.of(context)
+ .pushNamed(Routes.addressBookAddContact, arguments: contact),
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ icon: Icons.edit,
+ label: S.of(context).edit,
+ ),
+ SlidableAction(
+ onPressed: (_) async {
+ final isDelete = await showAlertDialog(context);
+
+ if (isDelete) {
+ await widget.contactListViewModel.delete(contact);
+ }
+ },
+ backgroundColor: Colors.red,
+ foregroundColor: Colors.white,
+ icon: CupertinoIcons.delete,
+ label: S.of(context).delete,
+ ),
+ ],
+ );
+
+ Widget filterButtonWidget(BuildContext context, ContactListViewModel contactListViewModel) {
+ final filterIcon = Image.asset('assets/images/filter_icon.png',
+ color: Theme.of(context).appBarTheme.titleTextStyle!.color);
+ return MergeSemantics(
+ child: SizedBox(
+ height: 58,
+ width: 58,
+ child: ButtonTheme(
+ minWidth: double.minPositive,
+ child: Semantics(
+ container: true,
+ child: GestureDetector(
+ onTap: () async {
+ await showPopUp(
+ context: context,
+ builder: (context) => FilterListWidget(
+ initalType: contactListViewModel.orderType,
+ initalAscending: contactListViewModel.ascending,
+ onClose: (bool ascending, FilterListOrderType type) async {
+ contactListViewModel.setAscending(ascending);
+ await contactListViewModel.setOrderType(type);
+ },
+ ),
+ );
+ },
+ child: Semantics(
+ label: 'Transaction Filter',
+ button: true,
+ enabled: true,
+ child: Container(
+ height: 36,
+ width: 36,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context).extension()!.buttonBackgroundColor,
+ ),
+ child: filterIcon,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
Future showAlertDialog(BuildContext context) async {
return await showPopUp(
context: context,
@@ -190,32 +450,4 @@ class ContactListPage extends BasePage {
}) ??
false;
}
-
- ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
- motion: const ScrollMotion(),
- extentRatio: 0.4,
- children: [
- SlidableAction(
- onPressed: (_) async => await Navigator.of(context)
- .pushNamed(Routes.addressBookAddContact, arguments: contact),
- backgroundColor: Colors.blue,
- foregroundColor: Colors.white,
- icon: Icons.edit,
- label: S.of(context).edit,
- ),
- SlidableAction(
- onPressed: (_) async {
- final isDelete = await showAlertDialog(context);
-
- if (isDelete) {
- await contactListViewModel.delete(contact);
- }
- },
- backgroundColor: Colors.red,
- foregroundColor: Colors.white,
- icon: CupertinoIcons.delete,
- label: S.of(context).delete,
- ),
- ],
- );
}
diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart
index e111bb588..2ea9ddbe6 100644
--- a/lib/src/screens/dashboard/dashboard_page.dart
+++ b/lib/src/screens/dashboard/dashboard_page.dart
@@ -140,7 +140,7 @@ class _DashboardPageView extends BasePage {
bool get resizeToAvoidBottomInset => false;
@override
- Widget get endDrawer => MenuWidget(dashboardViewModel);
+ Widget get endDrawer => MenuWidget(dashboardViewModel, ValueKey('dashboard_page_drawer_menu_widget_key'));
@override
Widget leading(BuildContext context) {
diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
index f8cbe9120..0fb629685 100644
--- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
@@ -10,7 +10,6 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
-import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -100,6 +99,11 @@ class _DesktopWalletSelectionDropDownState extends State element.isSelected,
+ orElse: () => dropDownItems.first,
+ );
+
return DropdownButton(
items: dropDownItems
.map(
@@ -115,7 +119,7 @@ class _DesktopWalletSelectionDropDownState extends State()!.backgroundColor,
style: TextStyle(color: themeData.extension()!.titleColor),
selectedItemBuilder: (context) => dropDownItems.map((item) => item.child).toList(),
- value: dropDownItems.firstWhere((element) => element.isSelected),
+ value: selectedItem,
underline: const SizedBox(),
focusColor: Colors.transparent,
borderRadius: BorderRadius.circular(15.0),
diff --git a/lib/src/screens/dashboard/pages/nft_details_page.dart b/lib/src/screens/dashboard/pages/nft_details_page.dart
index 15d2a2b5c..b8352a672 100644
--- a/lib/src/screens/dashboard/pages/nft_details_page.dart
+++ b/lib/src/screens/dashboard/pages/nft_details_page.dart
@@ -28,7 +28,10 @@ class NFTDetailsPage extends BasePage {
bool get resizeToAvoidBottomInset => false;
@override
- Widget get endDrawer => MenuWidget(dashboardViewModel);
+ Widget get endDrawer => MenuWidget(
+ dashboardViewModel,
+ ValueKey('nft_details_page_menu_widget_key'),
+ );
@override
Widget trailing(BuildContext context) {
diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart
index b6d1c286b..0db9ac35b 100644
--- a/lib/src/screens/dashboard/pages/transactions_page.dart
+++ b/lib/src/screens/dashboard/pages/transactions_page.dart
@@ -1,11 +1,13 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
+import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
+import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
@@ -14,9 +16,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/header_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/date_section_raw.dart';
-import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transaction_raw.dart';
-import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
import 'package:intl/intl.dart';
@@ -49,6 +49,7 @@ class TransactionsPage extends StatelessWidget {
return Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
child: DashBoardRoundedCardWidget(
+ key: ValueKey('transactions_page_syncing_alert_card_key'),
onTap: () {
try {
final uri = Uri.parse(
@@ -64,82 +65,93 @@ class TransactionsPage extends StatelessWidget {
return Container();
}
}),
- HeaderRow(dashboardViewModel: dashboardViewModel),
- Expanded(child: Observer(builder: (_) {
- final items = dashboardViewModel.items;
+ HeaderRow(
+ dashboardViewModel: dashboardViewModel,
+ key: ValueKey('transactions_page_header_row_key'),
+ ),
+ Expanded(
+ child: Observer(
+ builder: (_) {
+ final items = dashboardViewModel.items;
- return items.isNotEmpty
- ? ListView.builder(
- itemCount: items.length,
- itemBuilder: (context, index) {
- final item = items[index];
+ return items.isNotEmpty
+ ? ListView.builder(
+ key: ValueKey('transactions_page_list_view_builder_key'),
+ itemCount: items.length,
+ itemBuilder: (context, index) {
+ final item = items[index];
- if (item is DateSectionItem) {
- return DateSectionRaw(date: item.date);
- }
-
- if (item is TransactionListItem) {
- if (item.hasTokens && item.assetOfTransaction == null) {
- return Container();
- }
-
- final transaction = item.transaction;
- final transactionType = dashboardViewModel.getTransactionType(transaction);
-
- List tags = [];
- if (dashboardViewModel.type == WalletType.bitcoin) {
- if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
- tags.add(S.of(context).silent_payment);
+ if (item is DateSectionItem) {
+ return DateSectionRaw(date: item.date, key: item.key);
}
- }
- if (dashboardViewModel.type == WalletType.litecoin) {
- if (bitcoin!.txIsMweb(transaction)) {
- tags.add("MWEB");
+
+ if (item is TransactionListItem) {
+ if (item.hasTokens && item.assetOfTransaction == null) {
+ return Container();
+ }
+
+ final transaction = item.transaction;
+ final transactionType =
+ dashboardViewModel.getTransactionType(transaction);
+
+ List tags = [];
+ if (dashboardViewModel.type == WalletType.bitcoin) {
+ if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
+ tags.add(S.of(context).silent_payment);
+ }
+ }
+ if (dashboardViewModel.type == WalletType.litecoin) {
+ if (bitcoin!.txIsMweb(transaction)) {
+ tags.add("MWEB");
+ }
+ }
+
+ return Observer(
+ builder: (_) => TransactionRow(
+ key: item.key,
+ onTap: () => Navigator.of(context)
+ .pushNamed(Routes.transactionDetails, arguments: transaction),
+ direction: transaction.direction,
+ formattedDate: DateFormat('HH:mm').format(transaction.date),
+ formattedAmount: item.formattedCryptoAmount,
+ formattedFiatAmount:
+ dashboardViewModel.balanceViewModel.isFiatDisabled
+ ? ''
+ : item.formattedFiatAmount,
+ isPending: transaction.isPending,
+ title:
+ item.formattedTitle + item.formattedStatus + transactionType,
+ tags: tags,
+ ),
+ );
}
- }
- return Observer(
- builder: (_) => TransactionRow(
- onTap: () => Navigator.of(context)
- .pushNamed(Routes.transactionDetails, arguments: transaction),
- direction: transaction.direction,
- formattedDate: DateFormat('HH:mm').format(transaction.date),
- formattedAmount: item.formattedCryptoAmount,
- formattedFiatAmount:
- dashboardViewModel.balanceViewModel.isFiatDisabled
- ? ''
- : item.formattedFiatAmount,
- isPending: transaction.isPending,
- title:
- item.formattedTitle + item.formattedStatus + transactionType,
- tags: tags,
- ),
- );
- }
+ if (item is AnonpayTransactionListItem) {
+ final transactionInfo = item.transaction;
- if (item is AnonpayTransactionListItem) {
- final transactionInfo = item.transaction;
+ return AnonpayTransactionRow(
+ key: item.key,
+ onTap: () => Navigator.of(context).pushNamed(
+ Routes.anonPayDetailsPage,
+ arguments: transactionInfo),
+ currency: transactionInfo.fiatAmount != null
+ ? transactionInfo.fiatEquiv ?? ''
+ : CryptoCurrency.fromFullName(transactionInfo.coinTo)
+ .name
+ .toUpperCase(),
+ provider: transactionInfo.provider,
+ amount: transactionInfo.fiatAmount?.toString() ??
+ (transactionInfo.amountTo?.toString() ?? ''),
+ createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
+ );
+ }
- return AnonpayTransactionRow(
- onTap: () => Navigator.of(context)
- .pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
- currency: transactionInfo.fiatAmount != null
- ? transactionInfo.fiatEquiv ?? ''
- : CryptoCurrency.fromFullName(transactionInfo.coinTo)
- .name
- .toUpperCase(),
- provider: transactionInfo.provider,
- amount: transactionInfo.fiatAmount?.toString() ??
- (transactionInfo.amountTo?.toString() ?? ''),
- createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
- );
- }
+ if (item is TradeListItem) {
+ final trade = item.trade;
- if (item is TradeListItem) {
- final trade = item.trade;
-
- return Observer(
- builder: (_) => TradeRow(
+ return Observer(
+ builder: (_) => TradeRow(
+ key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
provider: trade.provider,
@@ -148,36 +160,44 @@ class TransactionsPage extends StatelessWidget {
createdAtFormattedDate: trade.createdAt != null
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
- formattedAmount: item.tradeFormattedAmount));
- }
+ formattedAmount: item.tradeFormattedAmount,
+ ),
+ );
+ }
- if (item is OrderListItem) {
- final order = item.order;
+ if (item is OrderListItem) {
+ final order = item.order;
- return Observer(
- builder: (_) => OrderRow(
- onTap: () => Navigator.of(context)
- .pushNamed(Routes.orderDetails, arguments: order),
- provider: order.provider,
- from: order.from!,
- to: order.to!,
- createdAtFormattedDate:
- DateFormat('HH:mm').format(order.createdAt),
- formattedAmount: item.orderFormattedAmount,
- ));
- }
+ return Observer(
+ builder: (_) => OrderRow(
+ key: item.key,
+ onTap: () => Navigator.of(context)
+ .pushNamed(Routes.orderDetails, arguments: order),
+ provider: order.provider,
+ from: order.from!,
+ to: order.to!,
+ createdAtFormattedDate:
+ DateFormat('HH:mm').format(order.createdAt),
+ formattedAmount: item.orderFormattedAmount,
+ ),
+ );
+ }
- return Container(color: Colors.transparent, height: 1);
- })
- : Center(
- child: Text(
- S.of(context).placeholder_transactions,
- style: TextStyle(
- fontSize: 14,
- color: Theme.of(context).extension()!.color),
- ),
- );
- }))
+ return Container(color: Colors.transparent, height: 1);
+ })
+ : Center(
+ child: Text(
+ key: ValueKey('transactions_page_placeholder_transactions_text_key'),
+ S.of(context).placeholder_transactions,
+ style: TextStyle(
+ fontSize: 14,
+ color: Theme.of(context).extension()!.color,
+ ),
+ ),
+ );
+ },
+ ),
+ )
],
),
),
diff --git a/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart b/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart
index cb8bef0b7..64d4bfe85 100644
--- a/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart
+++ b/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart
@@ -9,6 +9,7 @@ class AnonpayTransactionRow extends StatelessWidget {
required this.currency,
required this.onTap,
required this.amount,
+ super.key,
});
final VoidCallback? onTap;
diff --git a/lib/src/screens/dashboard/widgets/date_section_raw.dart b/lib/src/screens/dashboard/widgets/date_section_raw.dart
index 73f9f03a1..02fcef7f4 100644
--- a/lib/src/screens/dashboard/widgets/date_section_raw.dart
+++ b/lib/src/screens/dashboard/widgets/date_section_raw.dart
@@ -1,42 +1,27 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
-import 'package:intl/intl.dart';
-import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
class DateSectionRaw extends StatelessWidget {
- DateSectionRaw({required this.date});
+ DateSectionRaw({required this.date, super.key});
final DateTime date;
@override
Widget build(BuildContext context) {
- final nowDate = DateTime.now();
- final diffDays = date.difference(nowDate).inDays;
- final isToday = nowDate.day == date.day &&
- nowDate.month == date.month &&
- nowDate.year == date.year;
- final dateSectionDateFormat = DateFormatter.withCurrentLocal(hasTime: false);
- var title = "";
-
- if (isToday) {
- title = S.of(context).today;
- } else if (diffDays == 0) {
- title = S.of(context).yesterday;
- } else if (diffDays > -7 && diffDays < 0) {
- final dateFormat = DateFormat.EEEE();
- title = dateFormat.format(date);
- } else {
- title = dateSectionDateFormat.format(date);
- }
+ final title = DateFormatter.convertDateTimeToReadableString(date);
return Container(
- height: 35,
- alignment: Alignment.center,
- color: Colors.transparent,
- child: Text(title,
- style: TextStyle(
- fontSize: 12,
- color: Theme.of(context).extension()!.dateSectionRowColor)));
+ height: 35,
+ alignment: Alignment.center,
+ color: Colors.transparent,
+ child: Text(
+ title,
+ style: TextStyle(
+ fontSize: 12,
+ color: Theme.of(context).extension()!.dateSectionRowColor,
+ ),
+ ),
+ );
}
}
diff --git a/lib/src/screens/dashboard/widgets/filter_list_widget.dart b/lib/src/screens/dashboard/widgets/filter_list_widget.dart
index cda4f5f10..8e95de2af 100644
--- a/lib/src/screens/dashboard/widgets/filter_list_widget.dart
+++ b/lib/src/screens/dashboard/widgets/filter_list_widget.dart
@@ -18,9 +18,9 @@ class FilterListWidget extends StatefulWidget {
required this.onClose,
});
- final WalletListOrderType? initalType;
+ final FilterListOrderType? initalType;
final bool initalAscending;
- final Function(bool, WalletListOrderType) onClose;
+ final Function(bool, FilterListOrderType) onClose;
@override
FilterListWidgetState createState() => FilterListWidgetState();
@@ -28,7 +28,7 @@ class FilterListWidget extends StatefulWidget {
class FilterListWidgetState extends State {
late bool ascending;
- late WalletListOrderType? type;
+ late FilterListOrderType? type;
@override
void initState() {
@@ -37,7 +37,7 @@ class FilterListWidgetState extends State {
type = widget.initalType;
}
- void setSelectedOrderType(WalletListOrderType? orderType) {
+ void setSelectedOrderType(FilterListOrderType? orderType) {
setState(() {
type = orderType;
});
@@ -72,7 +72,7 @@ class FilterListWidgetState extends State {
),
),
),
- if (type != WalletListOrderType.Custom) ...[
+ if (type != FilterListOrderType.Custom) ...[
sectionDivider,
SettingsChoicesCell(
ChoicesListItem(
@@ -89,10 +89,10 @@ class FilterListWidgetState extends State {
],
sectionDivider,
RadioListTile(
- value: WalletListOrderType.CreationDate,
+ value: FilterListOrderType.CreationDate,
groupValue: type,
title: Text(
- WalletListOrderType.CreationDate.toString(),
+ FilterListOrderType.CreationDate.toString(),
style: TextStyle(
color: Theme.of(context).extension()!.titleColor,
fontSize: 16,
@@ -104,10 +104,10 @@ class FilterListWidgetState extends State {
activeColor: Theme.of(context).primaryColor,
),
RadioListTile(
- value: WalletListOrderType.Alphabetical,
+ value: FilterListOrderType.Alphabetical,
groupValue: type,
title: Text(
- WalletListOrderType.Alphabetical.toString(),
+ FilterListOrderType.Alphabetical.toString(),
style: TextStyle(
color: Theme.of(context).extension()!.titleColor,
fontSize: 16,
@@ -119,10 +119,10 @@ class FilterListWidgetState extends State {
activeColor: Theme.of(context).primaryColor,
),
RadioListTile(
- value: WalletListOrderType.GroupByType,
+ value: FilterListOrderType.GroupByType,
groupValue: type,
title: Text(
- WalletListOrderType.GroupByType.toString(),
+ FilterListOrderType.GroupByType.toString(),
style: TextStyle(
color: Theme.of(context).extension()!.titleColor,
fontSize: 16,
@@ -134,10 +134,10 @@ class FilterListWidgetState extends State {
activeColor: Theme.of(context).primaryColor,
),
RadioListTile(
- value: WalletListOrderType.Custom,
+ value: FilterListOrderType.Custom,
groupValue: type,
title: Text(
- WalletListOrderType.Custom.toString(),
+ FilterListOrderType.Custom.toString(),
style: TextStyle(
color: Theme.of(context).extension()!.titleColor,
fontSize: 16,
diff --git a/lib/src/screens/dashboard/widgets/header_row.dart b/lib/src/screens/dashboard/widgets/header_row.dart
index cb4f67fc2..f1f3f0889 100644
--- a/lib/src/screens/dashboard/widgets/header_row.dart
+++ b/lib/src/screens/dashboard/widgets/header_row.dart
@@ -7,7 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
class HeaderRow extends StatelessWidget {
- HeaderRow({required this.dashboardViewModel});
+ HeaderRow({required this.dashboardViewModel, super.key});
final DashboardViewModel dashboardViewModel;
@@ -34,6 +34,7 @@ class HeaderRow extends StatelessWidget {
Semantics(
container: true,
child: GestureDetector(
+ key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
onTap: () {
showPopUp(
context: context,
diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart
index 6d8379b29..f0e330f04 100644
--- a/lib/src/screens/dashboard/widgets/menu_widget.dart
+++ b/lib/src/screens/dashboard/widgets/menu_widget.dart
@@ -9,7 +9,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class MenuWidget extends StatefulWidget {
- MenuWidget(this.dashboardViewModel);
+ MenuWidget(this.dashboardViewModel, Key? key);
final DashboardViewModel dashboardViewModel;
@@ -193,6 +193,7 @@ class MenuWidgetState extends State {
final isLastTile = index == itemCount - 1;
return SettingActionButton(
+ key: item.key,
isLastTile: isLastTile,
tileHeight: tileHeight,
selectionActive: false,
diff --git a/lib/src/screens/dashboard/widgets/order_row.dart b/lib/src/screens/dashboard/widgets/order_row.dart
index 8adc6e0d5..221ea5689 100644
--- a/lib/src/screens/dashboard/widgets/order_row.dart
+++ b/lib/src/screens/dashboard/widgets/order_row.dart
@@ -12,7 +12,10 @@ class OrderRow extends StatelessWidget {
required this.to,
required this.createdAtFormattedDate,
this.onTap,
- this.formattedAmount});
+ this.formattedAmount,
+ super.key,
+ });
+
final VoidCallback? onTap;
final BuyProviderDescription provider;
final String from;
@@ -22,8 +25,7 @@ class OrderRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final iconColor =
- Theme.of(context).extension()!.iconColor;
+ final iconColor = Theme.of(context).extension()!.iconColor;
final providerIcon = getBuyProviderIcon(provider, iconColor: iconColor);
@@ -36,46 +38,42 @@ class OrderRow extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
- if (providerIcon != null) Padding(
- padding: EdgeInsets.only(right: 12),
- child: providerIcon,
- ),
+ if (providerIcon != null)
+ Padding(
+ padding: EdgeInsets.only(right: 12),
+ child: providerIcon,
+ ),
Expanded(
child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text('$from → $to',
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- color: Theme.of(context).extension()!.textColor
- )),
- formattedAmount != null
- ? Text(formattedAmount! + ' ' + to,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- color: Theme.of(context).extension()!.textColor
- ))
- : Container()
- ]),
- SizedBox(height: 5),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(createdAtFormattedDate,
- style: TextStyle(
- fontSize: 14,
- color: Theme.of(context).extension()!.dateSectionRowColor))
- ])
- ],
- )
- )
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Text('$from → $to',
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).extension()!.textColor)),
+ formattedAmount != null
+ ? Text(formattedAmount! + ' ' + to,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color:
+ Theme.of(context).extension()!.textColor))
+ : Container()
+ ]),
+ SizedBox(height: 5),
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Text(createdAtFormattedDate,
+ style: TextStyle(
+ fontSize: 14,
+ color:
+ Theme.of(context).extension()!.dateSectionRowColor))
+ ])
+ ],
+ ))
],
),
));
}
-}
\ No newline at end of file
+}
diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart
index 7c809aa9d..84a5d2beb 100644
--- a/lib/src/screens/dashboard/widgets/trade_row.dart
+++ b/lib/src/screens/dashboard/widgets/trade_row.dart
@@ -13,6 +13,7 @@ class TradeRow extends StatelessWidget {
required this.createdAtFormattedDate,
this.onTap,
this.formattedAmount,
+ super.key,
});
final VoidCallback? onTap;
diff --git a/lib/src/screens/dashboard/widgets/transaction_raw.dart b/lib/src/screens/dashboard/widgets/transaction_raw.dart
index b18131f3d..2d7cbb809 100644
--- a/lib/src/screens/dashboard/widgets/transaction_raw.dart
+++ b/lib/src/screens/dashboard/widgets/transaction_raw.dart
@@ -14,6 +14,7 @@ class TransactionRow extends StatelessWidget {
required this.tags,
required this.title,
required this.onTap,
+ super.key,
});
final VoidCallback onTap;
@@ -28,33 +29,36 @@ class TransactionRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
- onTap: onTap,
- child: Container(
- padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
- color: Colors.transparent,
- child: Row(
- mainAxisSize: MainAxisSize.max,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Container(
- height: 36,
- width: 36,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: Theme.of(context).extension()!.rowsColor),
- child: Image.asset(direction == TransactionDirection.incoming
- ? 'assets/images/down_arrow.png'
- : 'assets/images/up_arrow.png'),
- ),
- SizedBox(width: 12),
- Expanded(
- child: Column(
+ onTap: onTap,
+ child: Container(
+ padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
+ color: Colors.transparent,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ height: 36,
+ width: 36,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context).extension()!.rowsColor),
+ child: Image.asset(direction == TransactionDirection.incoming
+ ? 'assets/images/down_arrow.png'
+ : 'assets/images/up_arrow.png'),
+ ),
+ SizedBox(width: 12),
+ Expanded(
+ child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
- Row(
- children: [
- Text(title,
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Text(
+ title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@@ -65,28 +69,39 @@ class TransactionRow extends StatelessWidget {
),
Text(formattedAmount,
style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- color: Theme.of(context).extension()!.textColor))
- ]),
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).extension()!.textColor,
+ ),
+ )
+ ],
+ ),
SizedBox(height: 5),
- Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
- Text(formattedDate,
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(formattedDate,
+ style: TextStyle(
+ fontSize: 14,
+ color: Theme.of(context)
+ .extension()!
+ .dateSectionRowColor)),
+ Text(
+ formattedFiatAmount,
style: TextStyle(
- fontSize: 14,
- color:
- Theme.of(context).extension()!.dateSectionRowColor)),
- Text(formattedFiatAmount,
- style: TextStyle(
- fontSize: 14,
- color:
- Theme.of(context).extension()!.dateSectionRowColor))
- ])
+ fontSize: 14,
+ color: Theme.of(context).extension