mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-04-01 12:09:04 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into cw_linux_direct_input_password
Conflicts: assets/text/Monerocom_Release_Notes.txt assets/text/Release_Notes.txt lib/di.dart lib/store/settings_store.dart lib/view_model/settings/other_settings_view_model.dart res/values/strings_ar.arb res/values/strings_bg.arb res/values/strings_cs.arb res/values/strings_de.arb res/values/strings_en.arb res/values/strings_es.arb res/values/strings_fr.arb res/values/strings_ha.arb res/values/strings_hi.arb res/values/strings_hr.arb res/values/strings_id.arb res/values/strings_it.arb res/values/strings_ja.arb res/values/strings_ko.arb res/values/strings_my.arb res/values/strings_nl.arb res/values/strings_pl.arb res/values/strings_pt.arb res/values/strings_ru.arb res/values/strings_th.arb res/values/strings_tl.arb res/values/strings_tr.arb res/values/strings_uk.arb res/values/strings_ur.arb res/values/strings_yo.arb res/values/strings_zh.arb scripts/android/app_env.sh scripts/ios/app_env.sh scripts/macos/app_env.sh
This commit is contained in:
commit
76d1a7bdc6
130 changed files with 4395 additions and 879 deletions
.github
assets
Logo_CakeWallet.pngNOTICE.txtapp-store-badge.svgdevices.pngf-droid-badge.pnggoogle-play-badge.pnglinux-badge.svgmac-store-badge.svg
workflows
assets
cw_bitcoin
lib
bitcoin_transaction_credentials.dartbitcoin_transaction_priority.dartelectrum_transaction_info.dartelectrum_wallet.dartelectrum_wallet_addresses.dartpending_bitcoin_transaction.dart
pubspec.lockpubspec.yamlcw_bitcoin_cash
cw_core/lib
cw_evm/lib
cw_nano/lib
cw_solana/lib
lib
bitcoin
buy/moonpay
core
di.dartentities
ethereum
exchange/provider
main.dartnano
polygon
reactions
router.dartroutes.dartsolana
src
screens
dashboard
nano
nodes
receive
send
settings
transaction_details
rbf_details_list_fee_picker_item.dartrbf_details_page.darttransaction_details_page.darttransaction_expandable_list_item.dart
wallet_connect/widgets
widgets
store
utils
view_model
dashboard
node_list
send
settings
transaction_details_view_model.dartwallet_address_list
wallet_keys_view_model.dartres/values
BIN
.github/assets/Logo_CakeWallet.png
vendored
Normal file
BIN
.github/assets/Logo_CakeWallet.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 156 KiB |
48
.github/assets/NOTICE.txt
vendored
Normal file
48
.github/assets/NOTICE.txt
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
Notice for linux-badge.svg:
|
||||
|
||||
1:
|
||||
This is the Linux-penguin again...
|
||||
|
||||
Originally drewn by Larry Ewing (http://www.isc.tamu.edu/~lewing/)
|
||||
(with the GIMP) the Linux Logo has been vectorized by me (Simon Budig,
|
||||
http://www.home.unix-ag.org/simon/).
|
||||
|
||||
This happened quite some time ago with Corel Draw 4. But luckily
|
||||
meanwhile there are tools available to handle vector graphics with
|
||||
Linux. Bernhard Herzog (bernhard@users.sourceforge.net) deserves kudos
|
||||
for creating Sketch (http://sketch.sourceforge.net), a powerful free
|
||||
tool for creating vector graphics. He converted the Corel Draw file to
|
||||
the Sketch native format. Since I am unable to maintain the Corel Draw
|
||||
file any longer, the Sketch version now is the "official" one.
|
||||
|
||||
Anja Gerwinski (anja@gerwinski.de) has created an alternate version of
|
||||
the penguin (penguin-variant.sk) with a thinner mouth line and slightly
|
||||
altered gradients. It also features a nifty drop shadow.
|
||||
|
||||
The third bird (penguin-flat.sk) is a version reduced to three colors
|
||||
(black/white/yellow) for e.g. silk screen printing. I made this version
|
||||
for a mug, available at the friendly folks at
|
||||
http://www.kernelconcepts.de/ - they do good stuff, mail Petra
|
||||
(pinguin@kernelconcepts.de) if you need something special or don't
|
||||
understand the german :-)
|
||||
|
||||
These drawings are copyrighted by Larry Ewing and Simon Budig
|
||||
(penguin-variant.sk also by Anja Gerwinski), redistribution is free but
|
||||
has to include this README/Copyright notice.
|
||||
|
||||
The use of these drawings is free. However I am happy about a sample of
|
||||
your mug/t-shirt/whatever with this penguin on it...
|
||||
|
||||
Have fun
|
||||
Simon Budig
|
||||
|
||||
|
||||
Simon.Budig@unix-ag.org
|
||||
http://www.home.unix-ag.org/simon/
|
||||
|
||||
Simon Budig
|
||||
Am Hardtkoeppel 2
|
||||
D-61279 Graevenwiesbach
|
||||
|
||||
2:
|
||||
Attribution: lewing@isc.tamu.edu Larry Ewing and The GIMP
|
46
.github/assets/app-store-badge.svg
vendored
Executable file
46
.github/assets/app-store-badge.svg
vendored
Executable file
|
@ -0,0 +1,46 @@
|
|||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
|
||||
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
|
||||
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
|
||||
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
|
||||
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
|
||||
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
|
||||
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After (image error) Size: 11 KiB |
BIN
.github/assets/devices.png
vendored
Normal file
BIN
.github/assets/devices.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 72 KiB |
BIN
.github/assets/f-droid-badge.png
vendored
Normal file
BIN
.github/assets/f-droid-badge.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 16 KiB |
BIN
.github/assets/google-play-badge.png
vendored
Normal file
BIN
.github/assets/google-play-badge.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 22 KiB |
1071
.github/assets/linux-badge.svg
vendored
Executable file
1071
.github/assets/linux-badge.svg
vendored
Executable file
File diff suppressed because it is too large
Load diff
After (image error) Size: 67 KiB |
51
.github/assets/mac-store-badge.svg
vendored
Executable file
51
.github/assets/mac-store-badge.svg
vendored
Executable file
|
@ -0,0 +1,51 @@
|
|||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="156.10054" height="40" viewBox="0 0 156.10054 40">
|
||||
<title>Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M146.57123,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.4378,6.4378,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01514.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27446,6.27446,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H146.57123c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.60205-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72184,5.72184,0,0,1-.543-1.6572,12.41339,12.41339,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37032,12.37032,0,0,1,.16553-1.87207,5.75552,5.75552,0,0,1,.54346-1.6621A5.3735,5.3735,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875h139.205l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73127,12.73127,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M46.14895,30.49609V21.35645H46.0884l-3.74316,9.04492H40.91652l-3.75293-9.04492H37.104v9.13965H35.34816v-12.418h2.22949l4.01855,9.80176h.06836l4.01074-9.80176h2.2373v12.418Z" style="fill: #fff"/>
|
||||
<path d="M49.396,27.92285c0-1.583,1.21289-2.53906,3.36523-2.668l2.47852-.1377v-.68848c0-1.00684-.66309-1.5752-1.791-1.5752a1.73035,1.73035,0,0,0-1.90137,1.27441H49.8091c.05176-1.63574,1.5752-2.79687,3.69141-2.79687,2.16016,0,3.58887,1.17871,3.58887,2.96v6.20508H55.30813V29.00684h-.043a3.23683,3.23683,0,0,1-2.85742,1.64453A2.74447,2.74447,0,0,1,49.396,27.92285Zm5.84375-.81738V26.4082l-2.22949.1377c-1.11035.06934-1.73828.55078-1.73828,1.3252,0,.792.6543,1.30859,1.65234,1.30859A2.17046,2.17046,0,0,0,55.23977,27.10547Z" style="fill: #fff"/>
|
||||
<path d="M64.89309,24.55762a1.99909,1.99909,0,0,0-2.13379-1.66895c-1.42871,0-2.375,1.19629-2.375,3.08105,0,1.92773.95508,3.08887,2.3916,3.08887a1.94829,1.94829,0,0,0,2.11719-1.626h1.79A3.61835,3.61835,0,0,1,62.7593,30.6084c-2.582,0-4.26855-1.76465-4.26855-4.63867,0-2.81445,1.68652-4.63867,4.251-4.63867a3.63931,3.63931,0,0,1,3.9248,3.22656Z" style="fill: #fff"/>
|
||||
<path d="M78.7593,27.13965H74.0259l-1.13672,3.35645H70.8843l4.4834-12.418h2.083l4.4834,12.418H79.895Zm-4.24316-1.54883h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
|
||||
<path d="M91.61672,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438H83.0884V21.44238h1.79883v1.50586h.03418a3.21161,3.21161,0,0,1,2.88281-1.60059C90.10207,21.34766,91.61672,23.16406,91.61672,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C88.7593,29.01563,89.70656,27.81934,89.70656,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M101.58156,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C100.06691,21.34766,101.58156,23.16406,101.58156,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C98.72414,29.01563,99.67141,27.81934,99.67141,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M108.1675,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52149.75684-2.52149,1.8584c0,.87793.65431,1.39453,2.25489,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
|
||||
<path d="M119.80324,19.2998v2.14258h1.72168v1.47168h-1.72168v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406h-1.31641V21.44238h1.31641V19.2998Z" style="fill: #fff"/>
|
||||
<path d="M122.521,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C124.18215,30.6084,122.521,28.82617,122.521,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S129.21633,27.93164,129.21633,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M132.64309,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.598,2.598,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93651,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
|
||||
<path d="M145.84035,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,139.55813,25.13477Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_5" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57521,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M61.21779,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M66.40041,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.40041,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29494,13.03955Z" style="fill: #fff"/>
|
||||
<path d="M71.34768,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.07131,14.772,71.34768,13.87061,71.34768,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72072,10.91846,72.26564,11.49707,72.26564,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M79.22951,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.124,13.99463,82.56252,13.42432,82.56252,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After (image error) Size: 12 KiB |
2
.github/workflows/pr_test_build.yml
vendored
2
.github/workflows/pr_test_build.yml
vendored
|
@ -139,7 +139,9 @@ jobs:
|
|||
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||
|
|
38
README.md
38
README.md
|
@ -1,15 +1,35 @@
|
|||
# Cake Wallet for Mobile and Desktop
|
||||
<div align="center">
|
||||
|
||||
## Open Source Multi-Currency Wallet
|
||||
<img height="100" src=".github/assets/Logo_CakeWallet.png">
|
||||
|
||||
## Links
|
||||
</div>
|
||||
|
||||
* Website: https://cakewallet.com
|
||||
* App Store (iOS / MacOS): https://cakewallet.com/ios
|
||||
* Google Play: https://cakewallet.com/gp
|
||||
* F-Droid: https://fdroid.cakelabs.com
|
||||
* APK: https://github.com/cake-tech/cake_wallet/releases
|
||||
* Linux: https://github.com/cake-tech/cake_wallet/releases
|
||||

|
||||
|
||||
<div align="center">
|
||||
|
||||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=iphone)
|
||||
[<img height="42" src=".github/assets/google-play-badge.png">](https://play.google.com/store/apps/details?id=com.cakewallet.cake_wallet)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://fdroid.cakelabs.com)
|
||||
[<img height="42" src=".github/assets/mac-store-badge.svg">](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=mac)
|
||||
[<img height="42" src=".github/assets/linux-badge.svg">](https://github.com/cake-tech/cake_wallet/releases)
|
||||
|
||||
</div>
|
||||
|
||||
# Cake Wallet
|
||||
|
||||
Cake Wallet 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)
|
||||
* Bitcoin (BTC)
|
||||
* Ethereum (ETH)
|
||||
* Litecoin (LTC)
|
||||
* Bitcoin Cash (BCH)
|
||||
* Polygon (MATIC)
|
||||
* Solana (SOL)
|
||||
* Nano (XNO)
|
||||
* Haven (XHV)
|
||||
|
||||
## Features
|
||||
|
||||
|
|
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you need to report a vulnerability, please either:
|
||||
|
||||
* Open a security advisory: https://github.com/cake-tech/cake_wallet/security/advisories/new
|
||||
* Send an email to `dev@cakewallet.com` with details on the vulnerability
|
||||
|
||||
## 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.
|
|
@ -3,4 +3,26 @@
|
|||
useSSL: true
|
||||
is_default: true
|
||||
-
|
||||
uri: node.perish.co:9076
|
||||
uri: node.nautilus.io
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: app.natrium.io
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: rainstorm.city
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: node.somenano.com
|
||||
path: /proxy
|
||||
useSSL: true
|
||||
-
|
||||
uri: nanoslo.0x.no
|
||||
path: /proxy
|
||||
useSSL: true
|
||||
-
|
||||
uri: www.bitrequest.app
|
||||
port: 8020
|
||||
useSSL: true
|
|
@ -1,2 +1,2 @@
|
|||
Exchange flow enhancements and fixes
|
||||
Generic enhancements and bug fixes
|
||||
UI enhancements
|
||||
Bug fixes
|
|
@ -1,6 +1,7 @@
|
|||
Exchange flow enhancements and fixes
|
||||
Add MoonPay to Buy options
|
||||
Add THORChain to Exchange providers
|
||||
Improve Bitcoin fee calculations
|
||||
Fixes and enhancements for Solana
|
||||
Generic enhancements and bug fixes
|
||||
Add Replace-By-Fee to boost pending Bitcoin transactions
|
||||
Enable WalletConnect for Solana
|
||||
WalletConnect Enhancements
|
||||
Enhancements for ERC-20 tokens and Solana tokens
|
||||
Enhancements for Nano wallet
|
||||
UI enhancements
|
||||
Bug fixes
|
|
@ -2,7 +2,8 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class BitcoinTransactionCredentials {
|
||||
BitcoinTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
|
||||
BitcoinTransactionCredentials(this.outputs,
|
||||
{required this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final BitcoinTransactionPriority? priority;
|
||||
|
|
|
@ -4,13 +4,15 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
const BitcoinTransactionPriority({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow];
|
||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow, custom];
|
||||
static const BitcoinTransactionPriority slow =
|
||||
BitcoinTransactionPriority(title: 'Slow', raw: 0);
|
||||
static const BitcoinTransactionPriority medium =
|
||||
BitcoinTransactionPriority(title: 'Medium', raw: 1);
|
||||
static const BitcoinTransactionPriority fast =
|
||||
BitcoinTransactionPriority(title: 'Fast', raw: 2);
|
||||
static const BitcoinTransactionPriority custom =
|
||||
BitcoinTransactionPriority(title: 'Custom', raw: 3);
|
||||
|
||||
static BitcoinTransactionPriority deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
|
@ -20,6 +22,8 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
return medium;
|
||||
case 2:
|
||||
return fast;
|
||||
case 3:
|
||||
return custom;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize');
|
||||
}
|
||||
|
@ -39,7 +43,10 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
break;
|
||||
case BitcoinTransactionPriority.fast:
|
||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
||||
label = 'Fast';
|
||||
break; // S.current.transaction_priority_fast;
|
||||
case BitcoinTransactionPriority.custom:
|
||||
label = 'Custom';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -48,7 +55,10 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
return label;
|
||||
}
|
||||
|
||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
||||
String labelWithRate(int rate, int? customRate) {
|
||||
final rateValue = this == custom ? customRate ??= 0 : rate;
|
||||
return '${toString()} ($rateValue ${units}/byte)';
|
||||
}
|
||||
}
|
||||
|
||||
class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
||||
|
|
|
@ -11,12 +11,11 @@ import 'package:cw_core/wallet_type.dart';
|
|||
|
||||
class ElectrumTransactionBundle {
|
||||
ElectrumTransactionBundle(this.originalTransaction,
|
||||
{required this.ins, required this.confirmations, this.time, required this.height});
|
||||
{required this.ins, required this.confirmations, this.time});
|
||||
final BtcTransaction originalTransaction;
|
||||
final List<BtcTransaction> ins;
|
||||
final int? time;
|
||||
final int confirmations;
|
||||
final int height;
|
||||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
|
@ -25,6 +24,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
required int height,
|
||||
required int amount,
|
||||
int? fee,
|
||||
List<String>? inputAddresses,
|
||||
List<String>? outputAddresses,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
|
@ -32,6 +33,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
this.id = id;
|
||||
this.height = height;
|
||||
this.amount = amount;
|
||||
this.inputAddresses = inputAddresses;
|
||||
this.outputAddresses = outputAddresses;
|
||||
this.fee = fee;
|
||||
this.direction = direction;
|
||||
this.date = date;
|
||||
|
@ -100,6 +103,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
var amount = 0;
|
||||
var inputAmount = 0;
|
||||
var totalOutAmount = 0;
|
||||
List<String> inputAddresses = [];
|
||||
List<String> outputAddresses = [];
|
||||
|
||||
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||
final input = bundle.originalTransaction.inputs[i];
|
||||
|
@ -108,6 +113,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
inputAmount += outTransaction.amount.toInt();
|
||||
if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
||||
direction = TransactionDirection.outgoing;
|
||||
inputAddresses.add(addressFromOutputScript(outTransaction.scriptPubKey, network));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +121,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
for (final out in bundle.originalTransaction.outputs) {
|
||||
totalOutAmount += out.amount.toInt();
|
||||
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
|
||||
outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network));
|
||||
|
||||
if (addressExists) {
|
||||
receivedAmounts.add(out.amount.toInt());
|
||||
|
@ -137,6 +144,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
id: bundle.originalTransaction.txId(),
|
||||
height: height,
|
||||
isPending: bundle.confirmations == 0,
|
||||
inputAddresses: inputAddresses,
|
||||
outputAddresses: outputAddresses,
|
||||
fee: fee,
|
||||
direction: direction,
|
||||
amount: amount,
|
||||
|
@ -187,6 +196,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
inputAddresses: data['inputAddresses'] as List<String>,
|
||||
outputAddresses: data['outputAddresses'] as List<String>,
|
||||
confirmations: data['confirmations'] as int);
|
||||
}
|
||||
|
||||
|
@ -218,6 +229,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
direction: direction,
|
||||
date: date,
|
||||
isPending: isPending,
|
||||
inputAddresses: inputAddresses,
|
||||
outputAddresses: outputAddresses,
|
||||
confirmations: info.confirmations);
|
||||
}
|
||||
|
||||
|
@ -231,6 +244,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
m['isPending'] = isPending;
|
||||
m['confirmations'] = confirmations;
|
||||
m['fee'] = fee;
|
||||
m['inputAddresses'] = inputAddresses;
|
||||
m['outputAddresses'] = outputAddresses;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:cw_core/encryption_file_utils.dart';
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
|
@ -198,11 +199,9 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
int _getDustAmount() {
|
||||
return 546;
|
||||
}
|
||||
int get _dustAmount => 546;
|
||||
|
||||
bool _isBelowDust(int amount) => amount <= _getDustAmount() && network != BitcoinNetwork.testnet;
|
||||
bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet;
|
||||
|
||||
Future<EstimatedTxResult> estimateSendAllTx(
|
||||
List<BitcoinOutput> outputs,
|
||||
|
@ -428,7 +427,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
// Estimate to user how much is needed to send to cover the fee
|
||||
final maxAmountWithReturningChange = allInputsAmount - _getDustAmount() - fee - 1;
|
||||
final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1;
|
||||
throw BitcoinTransactionNoDustOnChangeException(
|
||||
bitcoinAmountToString(amount: maxAmountWithReturningChange),
|
||||
bitcoinAmountToString(amount: estimatedSendAll.amount),
|
||||
|
@ -542,6 +541,7 @@ abstract class ElectrumWalletBase
|
|||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
outputOrdering: BitcoinOrdering.none,
|
||||
enableRBF: true,
|
||||
);
|
||||
} else {
|
||||
txb = BitcoinTransactionBuilder(
|
||||
|
@ -551,9 +551,12 @@ abstract class ElectrumWalletBase
|
|||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
outputOrdering: BitcoinOrdering.none,
|
||||
enableRBF: true,
|
||||
);
|
||||
}
|
||||
|
||||
bool hasTaprootInputs = false;
|
||||
|
||||
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||
final key = estimatedTx.privateKeys
|
||||
.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
|
||||
|
@ -563,21 +566,25 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
if (utxo.utxo.isP2tr()) {
|
||||
hasTaprootInputs = true;
|
||||
return key.signTapRoot(txDigest, sighash: sighash);
|
||||
} else {
|
||||
return key.signInput(txDigest, sigHash: sighash);
|
||||
}
|
||||
});
|
||||
|
||||
return PendingBitcoinTransaction(transaction, type,
|
||||
electrumClient: electrumClient,
|
||||
amount: estimatedTx.amount,
|
||||
fee: estimatedTx.fee,
|
||||
feeRate: feeRateInt.toString(),
|
||||
network: network,
|
||||
hasChange: estimatedTx.hasChange,
|
||||
isSendAll: estimatedTx.isSendAll)
|
||||
..addListener((transaction) async {
|
||||
return PendingBitcoinTransaction(
|
||||
transaction,
|
||||
type,
|
||||
electrumClient: electrumClient,
|
||||
amount: estimatedTx.amount,
|
||||
fee: estimatedTx.fee,
|
||||
feeRate: feeRateInt.toString(),
|
||||
network: network,
|
||||
hasChange: estimatedTx.hasChange,
|
||||
isSendAll: estimatedTx.isSendAll,
|
||||
hasTaprootInputs: hasTaprootInputs,
|
||||
)..addListener((transaction) async {
|
||||
transactionHistory.addOne(transaction);
|
||||
await updateBalance();
|
||||
});
|
||||
|
@ -798,8 +805,180 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
||||
{required String hash, required int height}) async {
|
||||
Future<bool> canReplaceByFee(String hash) async {
|
||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||
final transactionHex = verboseTransaction['hex'] as String?;
|
||||
|
||||
if (confirmations > 0) return false;
|
||||
|
||||
if (transactionHex == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final original = bitcoin.Transaction.fromHex(transactionHex);
|
||||
|
||||
return original.ins
|
||||
.any((element) => element.sequence != null && element.sequence! < 4294967293);
|
||||
}
|
||||
|
||||
Future<bool> isChangeSufficientForFee(String txId, int newFee) async {
|
||||
final bundle = await getTransactionExpanded(hash: txId);
|
||||
final outputs = bundle.originalTransaction.outputs;
|
||||
|
||||
final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden);
|
||||
|
||||
// look for a change address in the outputs
|
||||
final changeOutput = outputs.firstWhereOrNull((output) => changeAddresses.any(
|
||||
(element) => element.address == addressFromOutputScript(output.scriptPubKey, network)));
|
||||
|
||||
var allInputsAmount = 0;
|
||||
|
||||
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||
final input = bundle.originalTransaction.inputs[i];
|
||||
final inputTransaction = bundle.ins[i];
|
||||
final vout = input.txIndex;
|
||||
final outTransaction = inputTransaction.outputs[vout];
|
||||
allInputsAmount += outTransaction.amount.toInt();
|
||||
}
|
||||
|
||||
int totalOutAmount = bundle.originalTransaction.outputs
|
||||
.fold<int>(0, (previousValue, element) => previousValue + element.amount.toInt());
|
||||
|
||||
var currentFee = allInputsAmount - totalOutAmount;
|
||||
|
||||
int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee;
|
||||
|
||||
return changeOutput != null && changeOutput.amount.toInt() - remainingFee >= 0;
|
||||
}
|
||||
|
||||
Future<PendingBitcoinTransaction> replaceByFee(String hash, int newFee) async {
|
||||
try {
|
||||
final bundle = await getTransactionExpanded(hash: hash);
|
||||
|
||||
final utxos = <UtxoWithAddress>[];
|
||||
List<ECPrivate> privateKeys = [];
|
||||
|
||||
var allInputsAmount = 0;
|
||||
|
||||
// Add inputs
|
||||
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||
final input = bundle.originalTransaction.inputs[i];
|
||||
final inputTransaction = bundle.ins[i];
|
||||
final vout = input.txIndex;
|
||||
final outTransaction = inputTransaction.outputs[vout];
|
||||
final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
|
||||
allInputsAmount += outTransaction.amount.toInt();
|
||||
|
||||
final addressRecord =
|
||||
walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
||||
|
||||
final btcAddress = addressTypeFromStr(addressRecord.address, network);
|
||||
final privkey = generateECPrivate(
|
||||
hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
|
||||
index: addressRecord.index,
|
||||
network: network);
|
||||
|
||||
privateKeys.add(privkey);
|
||||
|
||||
utxos.add(
|
||||
UtxoWithAddress(
|
||||
utxo: BitcoinUtxo(
|
||||
txHash: input.txId,
|
||||
value: outTransaction.amount,
|
||||
vout: vout,
|
||||
scriptType: _getScriptType(btcAddress),
|
||||
),
|
||||
ownerDetails:
|
||||
UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: btcAddress),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int totalOutAmount = bundle.originalTransaction.outputs
|
||||
.fold<int>(0, (previousValue, element) => previousValue + element.amount.toInt());
|
||||
|
||||
var currentFee = allInputsAmount - totalOutAmount;
|
||||
int remainingFee = newFee - currentFee;
|
||||
|
||||
final outputs = <BitcoinOutput>[];
|
||||
|
||||
// Add outputs and deduct the fees from it
|
||||
for (int i = bundle.originalTransaction.outputs.length - 1; i >= 0; i--) {
|
||||
final out = bundle.originalTransaction.outputs[i];
|
||||
final address = addressFromOutputScript(out.scriptPubKey, network);
|
||||
final btcAddress = addressTypeFromStr(address, network);
|
||||
|
||||
int newAmount;
|
||||
if (out.amount.toInt() >= remainingFee) {
|
||||
newAmount = out.amount.toInt() - remainingFee;
|
||||
remainingFee = 0;
|
||||
|
||||
// if new amount of output is less than dust amount, then don't add this output as well
|
||||
if (newAmount <= _dustAmount) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
remainingFee -= out.amount.toInt();
|
||||
continue;
|
||||
}
|
||||
|
||||
outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount)));
|
||||
}
|
||||
|
||||
final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden);
|
||||
|
||||
// look for a change address in the outputs
|
||||
final changeOutput = outputs.firstWhereOrNull((output) =>
|
||||
changeAddresses.any((element) => element.address == output.address.toAddress(network)));
|
||||
|
||||
// deduct the change amount from the output amount
|
||||
if (changeOutput != null) {
|
||||
totalOutAmount -= changeOutput.value.toInt();
|
||||
}
|
||||
|
||||
final txb = BitcoinTransactionBuilder(
|
||||
utxos: utxos,
|
||||
outputs: outputs,
|
||||
fee: BigInt.from(newFee),
|
||||
network: network,
|
||||
enableRBF: true,
|
||||
);
|
||||
|
||||
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||
final key =
|
||||
privateKeys.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
|
||||
|
||||
if (key == null) {
|
||||
throw Exception("Cannot find private key");
|
||||
}
|
||||
|
||||
if (utxo.utxo.isP2tr()) {
|
||||
return key.signTapRoot(txDigest, sighash: sighash);
|
||||
} else {
|
||||
return key.signInput(txDigest, sigHash: sighash);
|
||||
}
|
||||
});
|
||||
|
||||
return PendingBitcoinTransaction(
|
||||
transaction,
|
||||
type,
|
||||
electrumClient: electrumClient,
|
||||
amount: totalOutAmount,
|
||||
fee: newFee,
|
||||
network: network,
|
||||
hasChange: changeOutput != null,
|
||||
feeRate: newFee.toString(),
|
||||
)..addListener((transaction) async {
|
||||
transactionHistory.addOne(transaction);
|
||||
await updateBalance();
|
||||
});
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
||||
String transactionHex;
|
||||
int? time;
|
||||
int confirmations = 0;
|
||||
|
@ -830,8 +1009,12 @@ abstract class ElectrumWalletBase
|
|||
ins.add(tx);
|
||||
}
|
||||
|
||||
return ElectrumTransactionBundle(original,
|
||||
ins: ins, time: time, confirmations: confirmations, height: height);
|
||||
return ElectrumTransactionBundle(
|
||||
original,
|
||||
ins: ins,
|
||||
time: time,
|
||||
confirmations: confirmations,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||
|
@ -841,7 +1024,7 @@ abstract class ElectrumWalletBase
|
|||
bool? retryOnFailure}) async {
|
||||
try {
|
||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||
await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
|
||||
await getTransactionExpanded(hash: hash), walletInfo.type, network,
|
||||
addresses: myAddresses, height: height);
|
||||
} catch (e) {
|
||||
if (e is FormatException && retryOnFailure == true) {
|
||||
|
|
|
@ -241,6 +241,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final index = _addresses.indexOf(addressRecord);
|
||||
_addresses.remove(addressRecord);
|
||||
_addresses.insert(index, addressRecord);
|
||||
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -8,15 +8,18 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class PendingBitcoinTransaction with PendingTransaction {
|
||||
PendingBitcoinTransaction(this._tx, this.type,
|
||||
{required this.electrumClient,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.feeRate,
|
||||
this.network,
|
||||
required this.hasChange,
|
||||
required this.isSendAll})
|
||||
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
PendingBitcoinTransaction(
|
||||
this._tx,
|
||||
this.type, {
|
||||
required this.electrumClient,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.feeRate,
|
||||
this.network,
|
||||
required this.hasChange,
|
||||
this.isSendAll = false,
|
||||
this.hasTaprootInputs = false,
|
||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
|
||||
final WalletType type;
|
||||
final BtcTransaction _tx;
|
||||
|
@ -27,6 +30,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
final BasedUtxoNetwork? network;
|
||||
final bool hasChange;
|
||||
final bool isSendAll;
|
||||
final bool hasTaprootInputs;
|
||||
|
||||
@override
|
||||
String get id => _tx.txId();
|
||||
|
|
|
@ -70,8 +70,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
resolved-ref: "57b78afb85bd2c30d3cdb9f7884f3878a62be442"
|
||||
url: "https://github.com/cake-tech/bitbox-flutter.git"
|
||||
source: git
|
||||
version: "1.0.1"
|
||||
|
|
|
@ -26,7 +26,7 @@ dependencies:
|
|||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
ref: master
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
rxdart: ^0.27.5
|
||||
unorm_dart: ^0.2.0
|
||||
cryptography: ^2.0.5
|
||||
|
|
|
@ -28,7 +28,7 @@ dependencies:
|
|||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
ref: master
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
|
|
31
cw_core/lib/n2_node.dart
Normal file
31
cw_core/lib/n2_node.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
class N2Node {
|
||||
N2Node({
|
||||
this.weight,
|
||||
this.uptime,
|
||||
this.score,
|
||||
this.account,
|
||||
this.alias,
|
||||
});
|
||||
|
||||
String? uptime;
|
||||
double? weight;
|
||||
int? score;
|
||||
String? account;
|
||||
String? alias;
|
||||
|
||||
factory N2Node.fromJson(Map<String, dynamic> json) => N2Node(
|
||||
weight: double.tryParse((json['weight'] as num?).toString()),
|
||||
uptime: json['uptime'] as String?,
|
||||
score: json['score'] as int?,
|
||||
account: json['rep_address'] as String?,
|
||||
alias: json['alias'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'uptime': uptime,
|
||||
'weight': weight,
|
||||
'score': score,
|
||||
'rep_address': account,
|
||||
'alias': alias,
|
||||
};
|
||||
}
|
|
@ -21,6 +21,7 @@ class Node extends HiveObject with Keyable {
|
|||
this.trusted = false,
|
||||
this.socksProxyAddress,
|
||||
String? uri,
|
||||
String? path,
|
||||
WalletType? type,
|
||||
}) {
|
||||
if (uri != null) {
|
||||
|
@ -29,10 +30,14 @@ class Node extends HiveObject with Keyable {
|
|||
if (type != null) {
|
||||
this.type = type;
|
||||
}
|
||||
if (path != null) {
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
|
||||
Node.fromMap(Map<String, Object?> map)
|
||||
: uriRaw = map['uri'] as String? ?? '',
|
||||
path = map['path'] as String? ?? '',
|
||||
login = map['login'] as String?,
|
||||
password = map['password'] as String?,
|
||||
useSSL = map['useSSL'] as bool?,
|
||||
|
@ -63,6 +68,9 @@ class Node extends HiveObject with Keyable {
|
|||
@HiveField(6)
|
||||
String? socksProxyAddress;
|
||||
|
||||
@HiveField(7, defaultValue: '')
|
||||
String? path;
|
||||
|
||||
bool get isSSL => useSSL ?? false;
|
||||
|
||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||
|
@ -79,9 +87,9 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
if (isSSL) {
|
||||
return Uri.https(uriRaw, '');
|
||||
return Uri.https(uriRaw, path ?? '');
|
||||
} else {
|
||||
return Uri.http(uriRaw, '');
|
||||
return Uri.http(uriRaw, path ?? '');
|
||||
}
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
|
@ -103,7 +111,8 @@ class Node extends HiveObject with Keyable {
|
|||
other.typeRaw == typeRaw &&
|
||||
other.useSSL == useSSL &&
|
||||
other.trusted == trusted &&
|
||||
other.socksProxyAddress == socksProxyAddress);
|
||||
other.socksProxyAddress == socksProxyAddress &&
|
||||
other.path == path);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -113,7 +122,8 @@ class Node extends HiveObject with Keyable {
|
|||
typeRaw.hashCode ^
|
||||
useSSL.hashCode ^
|
||||
trusted.hashCode ^
|
||||
socksProxyAddress.hashCode;
|
||||
socksProxyAddress.hashCode ^
|
||||
path.hashCode;
|
||||
|
||||
@override
|
||||
dynamic get keyIndex {
|
||||
|
|
|
@ -16,6 +16,8 @@ abstract class TransactionInfo extends Object with Keyable {
|
|||
void changeFiatAmount(String amount);
|
||||
String? to;
|
||||
String? from;
|
||||
List<String>? inputAddresses;
|
||||
List<String>? outputAddresses;
|
||||
|
||||
@override
|
||||
dynamic get keyIndex => id;
|
||||
|
|
|
@ -41,5 +41,6 @@ abstract class WalletAddresses {
|
|||
}
|
||||
}
|
||||
|
||||
bool containsAddress(String address) => allAddressesMap.containsKey(address);
|
||||
bool containsAddress(String address) =>
|
||||
addressesMap.containsKey(address) || allAddressesMap.containsKey(address);
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount);
|
||||
|
||||
|
||||
// void fetchTransactionsAsync(
|
||||
// void Function(TransactionType transaction) onTransactionLoaded,
|
||||
// {void Function() onFinished});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cw_core/node.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:cw_evm/evm_erc20_balance.dart';
|
|||
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
||||
import 'package:cw_evm/pending_evm_chain_transaction.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_priority.dart';
|
||||
import 'package:cw_evm/.secrets.g.dart' as secrets;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
|
@ -211,26 +213,61 @@ abstract class EVMChainClient {
|
|||
return EVMChainERC20Balance(balance, exponent: exponent);
|
||||
}
|
||||
|
||||
Future<Erc20Token?> getErc20Token(String contractAddress) async {
|
||||
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
|
||||
try {
|
||||
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
||||
final name = await erc20.name();
|
||||
final symbol = await erc20.symbol();
|
||||
final decimal = await erc20.decimals();
|
||||
final uri = Uri.https(
|
||||
'deep-index.moralis.io',
|
||||
'/api/v2.2/erc20/metadata',
|
||||
{
|
||||
"chain": chainName,
|
||||
"addresses": contractAddress,
|
||||
},
|
||||
);
|
||||
|
||||
final response = await httpClient.get(
|
||||
uri,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-API-Key": secrets.moralisApiKey,
|
||||
},
|
||||
);
|
||||
|
||||
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
|
||||
|
||||
final name = decodedResponse['name'] ?? '';
|
||||
final symbol = decodedResponse['symbol'] ?? '';
|
||||
final decimal = decodedResponse['decimals'] ?? '0';
|
||||
final iconPath = decodedResponse['logo'] ?? '';
|
||||
|
||||
return Erc20Token(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
decimal: int.tryParse(decimal) ?? 0,
|
||||
iconPath: iconPath,
|
||||
);
|
||||
} catch (e) {
|
||||
try {
|
||||
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
||||
final name = await erc20.name();
|
||||
final symbol = await erc20.symbol();
|
||||
final decimal = await erc20.decimals();
|
||||
|
||||
return Erc20Token(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List hexToBytes(String hexString) {
|
||||
return Uint8List.fromList(hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
|
||||
return Uint8List.fromList(
|
||||
hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
|
|
|
@ -445,11 +445,16 @@ abstract class EVMChainWalletBase
|
|||
|
||||
Future<void> addErc20Token(Erc20Token token) async {
|
||||
String? iconPath;
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
|
||||
if (token.iconPath == null || token.iconPath!.isEmpty) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
final newToken = createNewErc20TokenObject(token, iconPath);
|
||||
|
||||
|
@ -472,8 +477,8 @@ abstract class EVMChainWalletBase
|
|||
_updateBalance();
|
||||
}
|
||||
|
||||
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||
await _client.getErc20Token(contractAddress);
|
||||
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
|
||||
await _client.getErc20Token(contractAddress, chainName);
|
||||
|
||||
void _onNewTransaction() {
|
||||
_updateBalance();
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/nano_account_info_response.dart';
|
||||
import 'package:cw_core/n2_node.dart';
|
||||
import 'package:cw_nano/nano_balance.dart';
|
||||
import 'package:cw_nano/nano_transaction_model.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
@ -16,6 +17,8 @@ class NanoClient {
|
|||
"nano-app": "cake-wallet"
|
||||
};
|
||||
|
||||
static const String N2_REPS_ENDPOINT = "https://rpc.nano.to";
|
||||
|
||||
NanoClient() {
|
||||
SharedPreferences.getInstance().then((value) => prefs = value);
|
||||
}
|
||||
|
@ -418,7 +421,7 @@ class NanoClient {
|
|||
body: jsonEncode({
|
||||
"action": "account_history",
|
||||
"account": address,
|
||||
"count": "250", // TODO: pick a number
|
||||
"count": "100",
|
||||
// "raw": true,
|
||||
}));
|
||||
final data = await jsonDecode(response.body);
|
||||
|
@ -434,4 +437,37 @@ class NanoClient {
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<N2Node>> getN2Reps() async {
|
||||
final response = await http.post(
|
||||
Uri.parse(N2_REPS_ENDPOINT),
|
||||
headers: CAKE_HEADERS,
|
||||
body: jsonEncode({"action": "reps"}),
|
||||
);
|
||||
try {
|
||||
final List<N2Node> nodes = (json.decode(response.body) as List<dynamic>)
|
||||
.map((dynamic e) => N2Node.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
return nodes;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getRepScore(String rep) async {
|
||||
final response = await http.post(
|
||||
Uri.parse(N2_REPS_ENDPOINT),
|
||||
headers: CAKE_HEADERS,
|
||||
body: jsonEncode({
|
||||
"action": "rep_info",
|
||||
"account": rep,
|
||||
}),
|
||||
);
|
||||
try {
|
||||
final N2Node node = N2Node.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
return node.score ?? 100;
|
||||
} catch (error) {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/n2_node.dart';
|
||||
import 'package:cw_nano/nano_balance.dart';
|
||||
import 'package:cw_nano/nano_client.dart';
|
||||
import 'package:cw_nano/nano_transaction_credentials.dart';
|
||||
|
@ -73,9 +74,11 @@ abstract class NanoWalletBase
|
|||
String? _privateKey;
|
||||
String? _publicAddress;
|
||||
String? _hexSeed;
|
||||
Timer? _receiveTimer;
|
||||
|
||||
String? _representativeAddress;
|
||||
Timer? _receiveTimer;
|
||||
int repScore = 100;
|
||||
bool get isRepOk => repScore >= 90;
|
||||
|
||||
late final NanoClient _client;
|
||||
bool _isTransactionUpdating;
|
||||
|
@ -442,6 +445,8 @@ abstract class NanoWalletBase
|
|||
_representativeAddress = await _client.getRepFromPrefs();
|
||||
throw Exception("Failed to get representative address $e");
|
||||
}
|
||||
|
||||
repScore = await _client.getRepScore(_representativeAddress!);
|
||||
}
|
||||
|
||||
Future<void> regenerateAddress() async {
|
||||
|
@ -478,6 +483,10 @@ abstract class NanoWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<N2Node>> getN2Reps() async {
|
||||
return _client.getN2Reps();
|
||||
}
|
||||
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
|
|
|
@ -533,4 +533,21 @@ class SolanaWalletClient {
|
|||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getIconImageFromTokenUri(String uri) async {
|
||||
try {
|
||||
final response = await httpClient.get(Uri.parse(uri));
|
||||
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return jsonResponse['image'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error occurred while fetching token image: \n${e.toString()}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -472,11 +472,17 @@ abstract class SolanaWalletBase
|
|||
return null;
|
||||
}
|
||||
|
||||
String? iconPath;
|
||||
try {
|
||||
iconPath = await _client.getIconImageFromTokenUri(token.uri);
|
||||
} catch (_) {}
|
||||
|
||||
return SPLToken.fromMetadata(
|
||||
name: token.name,
|
||||
mint: token.mint,
|
||||
symbol: token.symbol,
|
||||
mintAddress: mintAddress,
|
||||
iconPath: iconPath,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
|
|
|
@ -55,6 +55,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
|
|||
required String mint,
|
||||
required String symbol,
|
||||
required String mintAddress,
|
||||
String? iconPath
|
||||
}) {
|
||||
return SPLToken(
|
||||
name: name,
|
||||
|
@ -62,7 +63,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
|
|||
mintAddress: mintAddress,
|
||||
decimal: 0,
|
||||
mint: mint,
|
||||
iconPath: '',
|
||||
iconPath: iconPath,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -74,22 +74,26 @@ class CWBitcoin extends Bitcoin {
|
|||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentials(List<Output> outputs,
|
||||
{required TransactionPriority priority, int? feeRate}) =>
|
||||
BitcoinTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: feeRate);
|
||||
{required TransactionPriority priority, int? feeRate}) {
|
||||
final bitcoinFeeRate =
|
||||
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
|
||||
return BitcoinTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: bitcoinFeeRate
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
|
||||
|
@ -172,8 +176,9 @@ class CWBitcoin extends Bitcoin {
|
|||
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
|
||||
|
||||
@override
|
||||
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) =>
|
||||
(priority as BitcoinTransactionPriority).labelWithRate(rate);
|
||||
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate,
|
||||
{int? customRate}) =>
|
||||
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
|
||||
|
||||
@override
|
||||
List<BitcoinUnspent> getUnspents(Object wallet) {
|
||||
|
@ -199,6 +204,9 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
|
||||
|
||||
@override
|
||||
TransactionPriority getBitcoinTransactionPriorityCustom() => BitcoinTransactionPriority.custom;
|
||||
|
||||
@override
|
||||
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
|
||||
|
||||
|
@ -239,4 +247,48 @@ class CWBitcoin extends Bitcoin {
|
|||
return SegwitAddresType.p2wpkh;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasTaprootInput(PendingTransaction pendingTransaction) {
|
||||
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingBitcoinTransaction> replaceByFee(
|
||||
Object wallet, String transactionHash, String fee) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return await bitcoinWallet.replaceByFee(transactionHash, int.parse(fee));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> canReplaceByFee(Object wallet, String transactionHash) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.canReplaceByFee(transactionHash);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isChangeSufficientForFee(Object wallet, String txId, String newFee) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.isChangeSufficientForFee(txId, int.parse(newFee));
|
||||
}
|
||||
|
||||
@override
|
||||
int getFeeAmountForPriority(
|
||||
Object wallet, TransactionPriority priority, int inputsCount, int outputsCount,
|
||||
{int? size}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.feeAmountForPriority(
|
||||
priority as BitcoinTransactionPriority, inputsCount, outputsCount);
|
||||
}
|
||||
|
||||
@override
|
||||
int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount,
|
||||
{int? size}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.feeAmountWithFeeRate(
|
||||
feeRate,
|
||||
inputsCount,
|
||||
outputsCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ class MoonPayProvider extends BuyProvider {
|
|||
'baseCurrencyAmount': amount ?? '0',
|
||||
'currencyCode': currencyCode,
|
||||
'walletAddress': walletAddress,
|
||||
'lockAmount': 'true',
|
||||
'lockAmount': 'false',
|
||||
'showAllCurrencies': 'false',
|
||||
'showWalletAddressForm': 'false',
|
||||
'enabledPaymentMethods':
|
||||
|
@ -256,44 +256,44 @@ class MoonPayProvider extends BuyProvider {
|
|||
|
||||
@override
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||
// try {
|
||||
late final Uri uri;
|
||||
if (isBuyAction ?? true) {
|
||||
uri = await requestBuyMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
walletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
} else {
|
||||
uri = await requestSellMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
refundWalletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
}
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
|
||||
try {
|
||||
late final Uri uri;
|
||||
if (isBuyAction ?? true) {
|
||||
uri = await requestBuyMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
walletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
uri = await requestSellMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
refundWalletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw Exception('Could not launch URL');
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} else {
|
||||
throw Exception('Could not launch URL');
|
||||
}
|
||||
} catch (e) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: 'MoonPay',
|
||||
alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
// } catch (e) {
|
||||
// await showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (BuildContext context) {
|
||||
// return AlertWithOneAction(
|
||||
// alertTitle: 'MoonPay',
|
||||
// alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
// buttonText: S.of(context).ok,
|
||||
// buttonAction: () => Navigator.of(context).pop(),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
|
|
|
@ -14,4 +14,13 @@ class FailureState extends ExecutionState {
|
|||
FailureState(this.error);
|
||||
|
||||
final String error;
|
||||
}
|
||||
|
||||
class AwaitingConfirmationState extends ExecutionState {
|
||||
AwaitingConfirmationState({this.title, this.message, this.onConfirm, this.onCancel});
|
||||
|
||||
final String? title;
|
||||
final String? message;
|
||||
final Function()? onConfirm;
|
||||
final Function()? onCancel;
|
||||
}
|
|
@ -8,3 +8,8 @@ class NodeAddressValidator extends TextValidator {
|
|||
pattern:
|
||||
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.\-]+\$');
|
||||
}
|
||||
|
||||
class NodePathValidator extends TextValidator {
|
||||
NodePathValidator()
|
||||
: super(errorMessage: S.current.error_text_node_address, pattern: '^([/0-9a-zA-Z.\-]+)?\$');
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import 'solana_chain_service.dart';
|
|||
|
||||
enum SolanaChainId {
|
||||
mainnet,
|
||||
testnet,
|
||||
devnet,
|
||||
// testnet,
|
||||
// devnet,
|
||||
}
|
||||
|
||||
extension SolanaChainIdX on SolanaChainId {
|
||||
|
@ -13,13 +13,16 @@ extension SolanaChainIdX on SolanaChainId {
|
|||
switch (this) {
|
||||
case SolanaChainId.mainnet:
|
||||
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
|
||||
// solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
|
||||
break;
|
||||
case SolanaChainId.testnet:
|
||||
name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
|
||||
break;
|
||||
case SolanaChainId.devnet:
|
||||
name = '';
|
||||
break;
|
||||
// case SolanaChainId.devnet:
|
||||
// name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
|
||||
// // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1
|
||||
// break;
|
||||
// case SolanaChainId.testnet:
|
||||
// name = '';
|
||||
// // solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z
|
||||
// break;
|
||||
}
|
||||
|
||||
return '${SolanaChainServiceImpl.namespace}:$name';
|
||||
|
|
|
@ -43,7 +43,7 @@ class SolanaChainServiceImpl implements ChainService {
|
|||
SolanaClient(
|
||||
rpcUrl: rpcUrl,
|
||||
websocketUrl: Uri.parse(webSocketUrl),
|
||||
timeout: const Duration(minutes: 2),
|
||||
timeout: const Duration(minutes: 5),
|
||||
) {
|
||||
for (final String event in getEvents()) {
|
||||
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||
|
@ -72,7 +72,7 @@ class SolanaChainServiceImpl implements ChainService {
|
|||
|
||||
@override
|
||||
List<String> getEvents() {
|
||||
return [''];
|
||||
return ['chainChanged', 'accountsChanged'];
|
||||
}
|
||||
|
||||
Future<String?> requestAuthorization(String? text) async {
|
||||
|
@ -100,8 +100,7 @@ class SolanaChainServiceImpl implements ChainService {
|
|||
Future<String> solanaSignTransaction(String topic, dynamic parameters) async {
|
||||
log('received solana sign transaction request $parameters');
|
||||
|
||||
final solanaSignTx =
|
||||
SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
|
||||
final solanaSignTx = SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
|
||||
|
||||
final String? authError = await requestAuthorization('Confirm request to sign transaction?');
|
||||
|
||||
|
@ -122,10 +121,13 @@ class SolanaChainServiceImpl implements ChainService {
|
|||
return '';
|
||||
}
|
||||
|
||||
String signature = sign.signatures.first.toBase58();
|
||||
String signature = await solanaClient.sendAndConfirmTransaction(
|
||||
message: message,
|
||||
signers: [ownerKeyPair!],
|
||||
commitment: Commitment.confirmed,
|
||||
);
|
||||
|
||||
print(signature);
|
||||
print(signature.runtimeType);
|
||||
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
|
@ -19,6 +21,7 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'chain_service/solana/solana_chain_id.dart';
|
||||
|
@ -32,6 +35,7 @@ class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService;
|
|||
|
||||
abstract class Web3WalletServiceBase with Store {
|
||||
final AppStore appStore;
|
||||
final SharedPreferences sharedPreferences;
|
||||
final BottomSheetService _bottomSheetHandler;
|
||||
final WalletConnectKeyService walletKeyService;
|
||||
|
||||
|
@ -52,7 +56,8 @@ abstract class Web3WalletServiceBase with Store {
|
|||
@observable
|
||||
ObservableList<StoredCacao> auth;
|
||||
|
||||
Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore)
|
||||
Web3WalletServiceBase(
|
||||
this._bottomSheetHandler, this.walletKeyService, this.appStore, this.sharedPreferences)
|
||||
: pairings = ObservableList<PairingInfo>(),
|
||||
sessions = ObservableList<SessionData>(),
|
||||
auth = ObservableList<StoredCacao>(),
|
||||
|
@ -133,13 +138,27 @@ abstract class Web3WalletServiceBase with Store {
|
|||
if (appStore.wallet!.type == WalletType.solana) {
|
||||
for (final cId in SolanaChainId.values) {
|
||||
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
|
||||
final rpcUri = node.uri;
|
||||
final webSocketUri = 'wss://${node.uriRaw}/ws${node.uri.path}';
|
||||
|
||||
Uri? rpcUri;
|
||||
String webSocketUrl;
|
||||
bool isModifiedNodeUri = false;
|
||||
|
||||
if (node.uriRaw == 'rpc.ankr.com') {
|
||||
isModifiedNodeUri = true;
|
||||
|
||||
//A better way to handle this instead of adding this to the general secrets?
|
||||
String ankrApiKey = secrets.ankrApiKey;
|
||||
|
||||
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
|
||||
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
|
||||
} else {
|
||||
webSocketUrl = 'wss://${node.uriRaw}';
|
||||
}
|
||||
|
||||
SolanaChainServiceImpl(
|
||||
reference: cId,
|
||||
rpcUrl: rpcUri,
|
||||
webSocketUrl: webSocketUri,
|
||||
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
|
||||
webSocketUrl: webSocketUrl,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
wallet: _web3Wallet,
|
||||
|
@ -177,13 +196,6 @@ abstract class Web3WalletServiceBase with Store {
|
|||
_refreshPairings();
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
pairings.clear();
|
||||
final allPairings = _web3Wallet.pairings.getAll();
|
||||
pairings.addAll(allPairings);
|
||||
}
|
||||
|
||||
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
|
||||
log(args.toString());
|
||||
}
|
||||
|
@ -246,14 +258,37 @@ abstract class Web3WalletServiceBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
print('Refreshing pairings');
|
||||
pairings.clear();
|
||||
|
||||
final allPairings = _web3Wallet.pairings.getAll();
|
||||
|
||||
final keyForWallet = getKeyForStoringTopicsForWallet();
|
||||
|
||||
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
|
||||
|
||||
final filteredPairings =
|
||||
allPairings.where((pairing) => currentTopicsForWallet.contains(pairing.topic)).toList();
|
||||
|
||||
pairings.addAll(filteredPairings);
|
||||
}
|
||||
|
||||
void _onPairingCreate(PairingEvent? args) {
|
||||
log('Pairing Create Event: $args');
|
||||
}
|
||||
|
||||
@action
|
||||
void _onSessionConnect(SessionConnect? args) {
|
||||
Future<void> _onSessionConnect(SessionConnect? args) async {
|
||||
if (args != null) {
|
||||
log('Session Connected $args');
|
||||
|
||||
await savePairingTopicToLocalStorage(args.session.pairingTopic);
|
||||
|
||||
sessions.add(args.session);
|
||||
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,4 +356,53 @@ abstract class Web3WalletServiceBase with Store {
|
|||
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
|
||||
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
|
||||
}
|
||||
|
||||
String getKeyForStoringTopicsForWallet() {
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||
|
||||
final keyForPairingTopic =
|
||||
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
|
||||
|
||||
return keyForPairingTopic;
|
||||
}
|
||||
|
||||
List<String> getPairingTopicsForWallet(String key) {
|
||||
// Get the JSON-encoded string from shared preferences
|
||||
final jsonString = sharedPreferences.getString(key);
|
||||
|
||||
// If the string is null, return an empty list
|
||||
if (jsonString == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decode the JSON string to a list of strings
|
||||
final List<dynamic> jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
|
||||
// Cast each item to a string
|
||||
return jsonList.map((item) => item as String).toList();
|
||||
}
|
||||
|
||||
Future<void> savePairingTopicToLocalStorage(String pairingTopic) async {
|
||||
// Get key specific to the current wallet
|
||||
final key = getKeyForStoringTopicsForWallet();
|
||||
|
||||
// Get all pairing topics attached to this key
|
||||
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
|
||||
|
||||
print(pairingTopicsForWallet);
|
||||
|
||||
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
|
||||
print('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
|
||||
|
||||
if (!isPairingTopicAlreadySaved) {
|
||||
// Update the list with the most recent pairing topic
|
||||
pairingTopicsForWallet.add(pairingTopic);
|
||||
|
||||
// Convert the list of updated pairing topics to a JSON-encoded string
|
||||
final jsonString = jsonEncode(pairingTopicsForWallet);
|
||||
|
||||
// Save the encoded string to shared preferences
|
||||
await sharedPreferences.setString(key, jsonString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
lib/di.dart
11
lib/di.dart
|
@ -14,6 +14,8 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
|||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
|
@ -493,6 +495,7 @@ Future<void> setup({
|
|||
getIt.get<BottomSheetService>(),
|
||||
getIt.get<WalletConnectKeyService>(),
|
||||
appStore,
|
||||
getIt.get<SharedPreferences>()
|
||||
);
|
||||
web3WalletService.create();
|
||||
return web3WalletService;
|
||||
|
@ -921,7 +924,8 @@ Future<void> setup({
|
|||
transactionInfo: transactionInfo,
|
||||
transactionDescriptionBox: _transactionDescriptionBox,
|
||||
wallet: wallet,
|
||||
settingsStore: getIt.get<SettingsStore>());
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
sendViewModel: getIt.get<SendViewModel>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||
|
@ -1144,6 +1148,11 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
|
||||
transactionDetailsViewModel:
|
||||
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
|
||||
|
||||
getIt.registerFactory(() => AnonPayApi(
|
||||
useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
|
||||
wallet: getIt.get<AppStore>().wallet!));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io' show Directory, File, Platform;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
|
@ -211,6 +212,14 @@ Future<void> defaultSettingsMigration(
|
|||
await changeDefaultBitcoinNode(nodes, sharedPreferences);
|
||||
break;
|
||||
|
||||
case 30:
|
||||
await disableServiceStatusFiatDisabled(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 31:
|
||||
await updateNanoNodeList(nodes: nodes);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -225,6 +234,44 @@ Future<void> defaultSettingsMigration(
|
|||
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
|
||||
}
|
||||
|
||||
Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultNanoNodes();
|
||||
var listOfNewEndpoints = <String>[
|
||||
"app.natrium.io",
|
||||
"rainstorm.city",
|
||||
"node.somenano.com",
|
||||
"nanoslo.0x.no",
|
||||
"www.bitrequest.app",
|
||||
];
|
||||
// add new nodes:
|
||||
for (final node in nodeList) {
|
||||
if (listOfNewEndpoints.contains(node.uriRaw)) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// update the nautilus node:
|
||||
final nautilusNode =
|
||||
nodes.values.firstWhereOrNull((element) => element.uriRaw == "node.perish.co");
|
||||
if (nautilusNode != null) {
|
||||
nautilusNode.uriRaw = "node.nautilus.io";
|
||||
nautilusNode.path = "/api";
|
||||
nautilusNode.useSSL = true;
|
||||
await nautilusNode.save();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> disableServiceStatusFiatDisabled(SharedPreferences sharedPreferences) async {
|
||||
final currentFiat = await sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? -1;
|
||||
if (currentFiat == -1 || currentFiat == FiatApiMode.enabled.raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentFiat == FiatApiMode.disabled.raw || currentFiat == FiatApiMode.torOnly.raw) {
|
||||
await sharedPreferences.setBool(PreferencesKey.disableBulletinKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateMoneroPriority(SharedPreferences sharedPreferences) async {
|
||||
final currentPriority =
|
||||
await sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority) ??
|
||||
|
|
|
@ -42,8 +42,10 @@ class PreferencesKey {
|
|||
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
|
||||
static const polygonTransactionPriority = 'current_fee_priority_polygon';
|
||||
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||
static const shouldShowRepWarning = 'should_show_rep_warning';
|
||||
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
|
||||
static const syncModeKey = 'sync_mode';
|
||||
static const syncAllKey = 'sync_all';
|
||||
|
@ -74,4 +76,7 @@ class PreferencesKey {
|
|||
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
|
||||
static const isNewInstall = 'is_new_install';
|
||||
static const serviceStatusShaKey = 'service_status_sha_key';
|
||||
static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list';
|
||||
static String walletConnectPairingTopicsListForWallet(String publicKey) =>
|
||||
'${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}';
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ class CWEthereum extends Ethereum {
|
|||
@override
|
||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||
final ethereumWallet = wallet as EthereumWallet;
|
||||
return await ethereumWallet.getErc20Token(contractAddress);
|
||||
return await ethereumWallet.getErc20Token(contractAddress, 'eth');
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -32,7 +32,17 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
|||
'Exolix',
|
||||
'Godex',
|
||||
'Exch',
|
||||
'CoinCraddle'
|
||||
'CoinCraddle',
|
||||
'Alfacash',
|
||||
'LocalMonero',
|
||||
'XChange',
|
||||
'NeroSwap',
|
||||
'Changee',
|
||||
'BitcoinVN',
|
||||
'EasyBit',
|
||||
'WizardSwap',
|
||||
'Quantex',
|
||||
'SwapSpace',
|
||||
];
|
||||
|
||||
static const List<CryptoCurrency> _notSupported = [
|
||||
|
|
|
@ -151,25 +151,26 @@ Future<void> initializeAppConfigs() async {
|
|||
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||
|
||||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
walletInfoSource: walletInfoSource,
|
||||
contactSource: contacts,
|
||||
tradesSource: trades,
|
||||
ordersSource: orders,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
// fiatConvertationService: fiatConvertationService,
|
||||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 29);
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
walletInfoSource: walletInfoSource,
|
||||
contactSource: contacts,
|
||||
tradesSource: trades,
|
||||
ordersSource: orders,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
// fiatConvertationService: fiatConvertationService,
|
||||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 31,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initialSetup(
|
||||
{required SharedPreferences sharedPreferences,
|
||||
{required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
required Box<Node> powNodes,
|
||||
required Box<WalletInfo> walletInfoSource,
|
||||
|
|
|
@ -186,6 +186,16 @@ class CWNano extends Nano {
|
|||
String getRepresentative(Object wallet) {
|
||||
return (wallet as NanoWallet).representative;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<N2Node>> getN2Reps(Object wallet) async {
|
||||
return (wallet as NanoWallet).getN2Reps();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isRepOk(Object wallet) {
|
||||
return (wallet as NanoWallet).isRepOk;
|
||||
}
|
||||
}
|
||||
|
||||
class CWNanoUtil extends NanoUtil {
|
||||
|
|
|
@ -130,7 +130,7 @@ class CWPolygon extends Polygon {
|
|||
@override
|
||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||
final polygonWallet = wallet as PolygonWallet;
|
||||
return await polygonWallet.getErc20Token(contractAddress);
|
||||
return await polygonWallet.getErc20Token(contractAddress, 'polygon');
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -16,6 +16,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
|
|||
switch (walletType) {
|
||||
case WalletType.polygon:
|
||||
case WalletType.ethereum:
|
||||
case WalletType.solana:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -54,6 +54,7 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart
|
|||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
|
@ -269,6 +270,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
builder: (_) =>
|
||||
getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
|
||||
|
||||
case Routes.bumpFeePage:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) =>
|
||||
getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
|
||||
|
||||
case Routes.newSubaddress:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
|
||||
|
|
|
@ -12,6 +12,7 @@ class Routes {
|
|||
static const dashboard = '/dashboard';
|
||||
static const send = '/send';
|
||||
static const transactionDetails = '/transaction_info';
|
||||
static const bumpFeePage = '/bump_fee_page';
|
||||
static const receive = '/receive';
|
||||
static const newSubaddress = '/new_subaddress';
|
||||
static const walletEdit = '/walletEdit';
|
||||
|
|
|
@ -87,6 +87,7 @@ class CWSolana extends Solana {
|
|||
decimal: token.decimals,
|
||||
mint: token.name.toUpperCase(),
|
||||
enabled: token.enabled,
|
||||
iconPath: token.iconPath,
|
||||
);
|
||||
|
||||
await (wallet as SolanaWallet).addSPLToken(splToken);
|
||||
|
|
|
@ -51,14 +51,25 @@ class DashboardPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
return Scaffold(
|
||||
body: Observer(
|
||||
builder: (_) {
|
||||
final dashboardPageView = _DashboardPageView(
|
||||
balancePage: balancePage,
|
||||
bottomSheetService: bottomSheetService,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
final dashboardPageView = RefreshIndicator(
|
||||
displacement: screenHeight * 0.1,
|
||||
onRefresh: () async => await dashboardViewModel.refreshDashboard(),
|
||||
child: SingleChildScrollView(
|
||||
physics: AlwaysScrollableScrollPhysics(),
|
||||
child: Container(
|
||||
height: screenHeight,
|
||||
child: _DashboardPageView(
|
||||
balancePage: balancePage,
|
||||
bottomSheetService: bottomSheetService,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (DeviceInfo.instance.isDesktop) {
|
||||
|
@ -106,10 +117,10 @@ class _DashboardPageView extends BasePage {
|
|||
Widget leading(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
if (dashboardViewModel.isEnabledBulletinAction) {
|
||||
return ServicesUpdatesWidget(dashboardViewModel.getServicesStatus());
|
||||
}
|
||||
return const SizedBox();
|
||||
return ServicesUpdatesWidget(
|
||||
dashboardViewModel.getServicesStatus(),
|
||||
enabled: dashboardViewModel.isEnabledBulletinAction,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -107,7 +107,10 @@ class DesktopSidebarWrapper extends BasePage {
|
|||
: unselectedIconPath,
|
||||
),
|
||||
SideMenuItem(
|
||||
widget: ServicesUpdatesWidget(dashboardViewModel.getServicesStatus()),
|
||||
widget: ServicesUpdatesWidget(
|
||||
dashboardViewModel.getServicesStatus(),
|
||||
enabled: dashboardViewModel.isEnabledBulletinAction,
|
||||
),
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.status,
|
||||
onTap: () {},
|
||||
),
|
||||
|
|
|
@ -59,6 +59,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
|||
final TextEditingController _tokenNameController = TextEditingController();
|
||||
final TextEditingController _tokenSymbolController = TextEditingController();
|
||||
final TextEditingController _tokenDecimalController = TextEditingController();
|
||||
final TextEditingController _tokenIconPathController = TextEditingController();
|
||||
|
||||
final FocusNode _contractAddressFocusNode = FocusNode();
|
||||
final FocusNode _tokenNameFocusNode = FocusNode();
|
||||
|
@ -83,6 +84,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
|||
_tokenNameController.text = widget.token!.name;
|
||||
_tokenSymbolController.text = widget.token!.title;
|
||||
_tokenDecimalController.text = widget.token!.decimals.toString();
|
||||
_tokenIconPathController.text = widget.token?.iconPath ?? '';
|
||||
}
|
||||
|
||||
if (widget.initialContractAddress != null) {
|
||||
|
@ -200,6 +202,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
|||
name: _tokenNameController.text,
|
||||
title: _tokenSymbolController.text.toUpperCase(),
|
||||
decimals: int.parse(_tokenDecimalController.text),
|
||||
iconPath: _tokenIconPathController.text,
|
||||
),
|
||||
contractAddress: _contractAddressController.text,
|
||||
);
|
||||
|
@ -228,6 +231,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
|||
if (token != null) {
|
||||
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
||||
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title;
|
||||
if (_tokenIconPathController.text.isEmpty)
|
||||
_tokenIconPathController.text = token.iconPath ?? '';
|
||||
if (_tokenDecimalController.text.isEmpty)
|
||||
_tokenDecimalController.text = token.decimals.toString();
|
||||
}
|
||||
|
@ -305,10 +310,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
|||
if (text?.isEmpty ?? true) {
|
||||
return S.of(context).field_required;
|
||||
}
|
||||
|
||||
if (int.tryParse(text!) == null) {
|
||||
return S.of(context).invalid_input;
|
||||
}
|
||||
|
||||
if (int.tryParse(text) == 0) {
|
||||
return S.current.decimals_cannot_be_zero;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
|
|
@ -129,25 +129,29 @@ class HomeSettingsPage extends BasePage {
|
|||
'token': token,
|
||||
});
|
||||
},
|
||||
leading: CakeImageWidget(
|
||||
imageUrl: token.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
token.title.substring(0, min(token.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
leading: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: token.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
token.title.substring(0, min(token.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
|
|
|
@ -163,12 +163,7 @@ class AddressPage extends BasePage {
|
|||
if (addressListViewModel.hasAddressList) {
|
||||
return SelectButton(
|
||||
text: addressListViewModel.buttonTitle,
|
||||
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
|
||||
(WalletType.monero == addressListViewModel.wallet.type ||
|
||||
WalletType.haven == addressListViewModel.wallet.type)
|
||||
? await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
|
||||
: Navigator.of(context).pushNamed(Routes.receive),
|
||||
onTap: () async => Navigator.of(context).pushNamed(Routes.receive),
|
||||
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
|
@ -176,17 +171,11 @@ class AddressPage extends BasePage {
|
|||
textSize: 14,
|
||||
height: 50,
|
||||
);
|
||||
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled ||
|
||||
addressListViewModel.isElectrumWallet) {
|
||||
return Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return const SizedBox();
|
||||
}
|
||||
})
|
||||
}),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/introducing_card.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||
|
@ -183,6 +184,22 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
return Container();
|
||||
},
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
if (!dashboardViewModel.showRepWarning) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
title: S.current.rep_warning,
|
||||
subTitle: S.current.rep_warning_sub,
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
|
||||
onClose: () {
|
||||
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ListView.separated(
|
||||
|
@ -323,7 +340,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
],
|
||||
|
@ -334,24 +351,28 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
|
@ -410,9 +431,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.balanceAmountColor,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.balanceAmountColor,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_ro
|
|||
import 'package:cake_wallet/src/screens/dashboard/widgets/order_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/device_info.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';
|
||||
|
@ -20,6 +21,7 @@ import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class TransactionsPage extends StatelessWidget {
|
||||
TransactionsPage({required this.dashboardViewModel});
|
||||
|
@ -46,11 +48,17 @@ class TransactionsPage extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [
|
||||
'',
|
||||
Uri.parse(
|
||||
'https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/')
|
||||
]),
|
||||
onTap: () {
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/");
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['', uri]);
|
||||
} else {
|
||||
launchUrl(uri);
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
title: S.of(context).syncing_wallet_alert_title,
|
||||
subTitle: S.of(context).syncing_wallet_alert_content,
|
||||
),
|
||||
|
|
|
@ -5,10 +5,12 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.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/address_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/n2_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -21,9 +23,7 @@ class NanoChangeRepPage extends BasePage {
|
|||
: _wallet = wallet,
|
||||
_settingsStore = settingsStore,
|
||||
_addressController = TextEditingController(),
|
||||
_formKey = GlobalKey<FormState>() {
|
||||
_addressController.text = nano!.getRepresentative(wallet);
|
||||
}
|
||||
_formKey = GlobalKey<FormState>() {}
|
||||
|
||||
final TextEditingController _addressController;
|
||||
final WalletBase _wallet;
|
||||
|
@ -34,105 +34,314 @@ class NanoChangeRepPage extends BasePage {
|
|||
@override
|
||||
String get title => S.current.change_rep;
|
||||
|
||||
N2Node getCurrentRepNode(List<N2Node> nodes) {
|
||||
final currentRepAccount = nano!.getRepresentative(_wallet);
|
||||
final currentNode = nodes.firstWhere(
|
||||
(node) => node.account == currentRepAccount,
|
||||
orElse: () => N2Node(
|
||||
account: currentRepAccount,
|
||||
alias: currentRepAccount,
|
||||
score: 0,
|
||||
uptime: "???",
|
||||
weight: 0,
|
||||
),
|
||||
);
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24.0),
|
||||
content: Container(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: AddressTextField(
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
],
|
||||
buttonColor: Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
|
||||
validator: AddressValidator(type: CryptoCurrency.nano),
|
||||
child: FutureBuilder(
|
||||
future: nano!.getN2Reps(_wallet),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
topSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
topSection: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: AddressTextField(
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
],
|
||||
buttonColor:
|
||||
Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
|
||||
validator: AddressValidator(type: CryptoCurrency.nano),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
child: Text(
|
||||
S.current.nano_current_rep,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
_buildSingleRepresentative(
|
||||
context,
|
||||
getCurrentRepNode(snapshot.data as List<N2Node>),
|
||||
isList: false,
|
||||
),
|
||||
Divider(height: 20),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
child: Text(
|
||||
S.current.nano_pick_new_rep,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: Container(
|
||||
child: Column(
|
||||
children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>),
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
bottomSection: Observer(
|
||||
builder: (_) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0),
|
||||
child: LoadingPrimaryButton(
|
||||
onPressed: () => _onSubmit(context),
|
||||
text: S.of(context).change,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(BuildContext context) async {
|
||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_rep,
|
||||
alertContent: S.of(context).change_rep_message,
|
||||
rightButtonText: S.of(context).change,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () => Navigator.pop(context, true),
|
||||
actionLeftButton: () => Navigator.pop(context, false));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
_settingsStore.defaultNanoRep = _addressController.text;
|
||||
|
||||
await nano!.changeRep(_wallet, _addressController.text);
|
||||
|
||||
// reset this flag whenever we successfully change reps:
|
||||
_settingsStore.shouldShowRepWarning = true;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).successful,
|
||||
alertContent: S.of(context).change_rep_successful,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.pop(context));
|
||||
});
|
||||
} catch (e) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: e.toString(),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.pop(context));
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _getRepresentativeWidgets(BuildContext context, List<N2Node>? list) {
|
||||
if (list == null) {
|
||||
return [];
|
||||
}
|
||||
final List<Widget> ret = [];
|
||||
for (final N2Node node in list) {
|
||||
if (node.alias != null && node.alias!.trim().isNotEmpty) {
|
||||
ret.add(_buildSingleRepresentative(context, node));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
if (isList)
|
||||
Divider(
|
||||
height: 2,
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: () async {
|
||||
if (!isList) {
|
||||
return;
|
||||
}
|
||||
_addressController.text = rep.account!;
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 24),
|
||||
width: MediaQuery.of(context).size.width * 0.50,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
_sanitizeAlias(rep.alias),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 7),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: "${S.current.voting_weight}: ${rep.weight.toString()}%",
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: '',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "${S.current.uptime}: ",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.secondaryTextColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: rep.uptime,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.secondaryTextColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsetsDirectional.only(end: 24, start: 14),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.verified,
|
||||
color: Theme.of(context).primaryColor,
|
||||
size: 50,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(13),
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: const AlignmentDirectional(-0.03, 0.03),
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: Text(
|
||||
(rep.score).toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
bottomSection: Observer(
|
||||
builder: (_) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0),
|
||||
child: LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState != null &&
|
||||
!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_rep,
|
||||
alertContent: S.of(context).change_rep_message,
|
||||
rightButtonText: S.of(context).change,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () => Navigator.pop(context, true),
|
||||
actionLeftButton: () => Navigator.pop(context, false));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
if (confirmed) {
|
||||
try {
|
||||
_settingsStore.defaultNanoRep = _addressController.text;
|
||||
|
||||
await nano!.changeRep(_wallet, _addressController.text);
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).successful,
|
||||
alertContent: S.of(context).change_rep_successful,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.pop(context));
|
||||
});
|
||||
} catch (e) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: e.toString(),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.pop(context));
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
text: S.of(context).change,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _sanitizeAlias(String? alias) {
|
||||
if (alias != null) {
|
||||
return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ class NodeCreateOrEditPage extends BasePage {
|
|||
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_addressController = TextEditingController(),
|
||||
_pathController = TextEditingController(),
|
||||
_portController = TextEditingController(),
|
||||
_loginController = TextEditingController(),
|
||||
_passwordController = TextEditingController() {
|
||||
|
@ -49,6 +50,8 @@ class NodeCreateOrEditPage extends BasePage {
|
|||
|
||||
_addressController.addListener(
|
||||
() => nodeCreateOrEditViewModel.address = _addressController.text);
|
||||
_pathController.addListener(
|
||||
() => nodeCreateOrEditViewModel.path = _pathController.text);
|
||||
_portController.addListener(
|
||||
() => nodeCreateOrEditViewModel.port = _portController.text);
|
||||
_loginController.addListener(
|
||||
|
@ -59,6 +62,7 @@ class NodeCreateOrEditPage extends BasePage {
|
|||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final TextEditingController _addressController;
|
||||
final TextEditingController _pathController;
|
||||
final TextEditingController _portController;
|
||||
final TextEditingController _loginController;
|
||||
final TextEditingController _passwordController;
|
||||
|
|
|
@ -16,13 +16,15 @@ class NodeForm extends StatelessWidget {
|
|||
required this.formKey,
|
||||
this.editingNode,
|
||||
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
|
||||
_pathController = TextEditingController(text: editingNode?.path.toString()),
|
||||
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
|
||||
_loginController = TextEditingController(text: editingNode?.login),
|
||||
_passwordController = TextEditingController(text: editingNode?.password),
|
||||
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
|
||||
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) {
|
||||
if (editingNode != null) {
|
||||
nodeViewModel
|
||||
..setAddress((editingNode!.uri.host.toString()))
|
||||
..setPath((editingNode!.path.toString()))
|
||||
..setPort((editingNode!.uri.port.toString()))
|
||||
..setPassword((editingNode!.password ?? ''))
|
||||
..setLogin((editingNode!.login ?? ''))
|
||||
|
@ -57,10 +59,12 @@ class NodeForm extends StatelessWidget {
|
|||
});
|
||||
|
||||
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
|
||||
_pathController.addListener(() => nodeViewModel.path = _pathController.text);
|
||||
_portController.addListener(() => nodeViewModel.port = _portController.text);
|
||||
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
|
||||
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
|
||||
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
|
||||
_socksAddressController
|
||||
.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
|
||||
}
|
||||
|
||||
final NodeCreateOrEditViewModel nodeViewModel;
|
||||
|
@ -68,6 +72,7 @@ class NodeForm extends StatelessWidget {
|
|||
final Node? editingNode;
|
||||
|
||||
final TextEditingController _addressController;
|
||||
final TextEditingController _pathController;
|
||||
final TextEditingController _portController;
|
||||
final TextEditingController _loginController;
|
||||
final TextEditingController _passwordController;
|
||||
|
@ -91,6 +96,18 @@ class NodeForm extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _pathController,
|
||||
hintText: "/path",
|
||||
validator: NodePathValidator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
|
@ -103,6 +120,26 @@ class NodeForm extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: nodeViewModel.useSSL,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) => nodeViewModel.useSSL = value,
|
||||
caption: S.of(context).use_ssl,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
if (nodeViewModel.hasAuthCredentials) ...[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
|
@ -123,25 +160,6 @@ class NodeForm extends StatelessWidget {
|
|||
))
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: nodeViewModel.useSSL,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) => nodeViewModel.useSSL = value,
|
||||
caption: S.of(context).use_ssl,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
|
@ -163,44 +181,44 @@ class NodeForm extends StatelessWidget {
|
|||
),
|
||||
Observer(
|
||||
builder: (_) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
StandardCheckbox(
|
||||
value: nodeViewModel.useSocksProxy,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) {
|
||||
if (!value) {
|
||||
_socksAddressController.text = '';
|
||||
}
|
||||
nodeViewModel.useSocksProxy = value;
|
||||
},
|
||||
caption: 'SOCKS Proxy',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (nodeViewModel.useSocksProxy) ...[
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
StandardCheckbox(
|
||||
value: nodeViewModel.useSocksProxy,
|
||||
gradientBackground: true,
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
iconColor: Colors.white,
|
||||
onChanged: (value) {
|
||||
if (!value) {
|
||||
_socksAddressController.text = '';
|
||||
}
|
||||
nodeViewModel.useSocksProxy = value;
|
||||
},
|
||||
caption: 'SOCKS Proxy',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (nodeViewModel.useSocksProxy) ...[
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _socksAddressController,
|
||||
hintText: '[<ip>:]<port>',
|
||||
validator: SocksProxyNodeAddressValidator(),
|
||||
))
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
)),
|
||||
]
|
||||
],
|
||||
),
|
||||
|
|
|
@ -99,12 +99,7 @@ class ReceivePage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final isElectrumWallet = addressListViewModel.isElectrumWallet;
|
||||
return (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven ||
|
||||
addressListViewModel.type == WalletType.nano ||
|
||||
isElectrumWallet)
|
||||
? KeyboardActions(
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
|
@ -213,32 +208,6 @@ class ReceivePage extends BasePage {
|
|||
})),
|
||||
],
|
||||
),
|
||||
))
|
||||
: Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: QRWidget(
|
||||
formKey: _formKey,
|
||||
heroTag: _heroTag,
|
||||
addressListViewModel: addressListViewModel,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
isLight: currentTheme.type == ThemeType.light),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SizedBox(),
|
||||
),
|
||||
Text(S.of(context).electrum_address_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
@ -81,41 +82,45 @@ class AddressCell extends StatelessWidget {
|
|||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisAlignment: name.isNotEmpty ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (isChange)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Container(
|
||||
height: 20,
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: textColor),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).unspent_change,
|
||||
style: TextStyle(
|
||||
color: backgroundColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
Row(
|
||||
children: [
|
||||
if (isChange)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Container(
|
||||
height: 20,
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: textColor),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).unspent_change,
|
||||
style: TextStyle(
|
||||
color: backgroundColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (name.isNotEmpty)
|
||||
Text(
|
||||
'$name - ',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
if (name.isNotEmpty)
|
||||
Text(
|
||||
'$name',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: AutoSizeText(
|
||||
formattedAddress,
|
||||
responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
|
|
|
@ -100,7 +100,10 @@ class SendPage extends BasePage {
|
|||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
double _sendCardHeight(BuildContext context) {
|
||||
final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465;
|
||||
double initialHeight = 450;
|
||||
if (sendViewModel.hasCoinControl) {
|
||||
initialHeight += 35;
|
||||
}
|
||||
|
||||
if (!responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
return initialHeight - 66;
|
||||
|
@ -190,7 +193,7 @@ class SendPage extends BasePage {
|
|||
},
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
|
||||
child: Container(
|
||||
height: 10,
|
||||
child: Observer(
|
||||
|
@ -456,7 +459,7 @@ class SendPage extends BasePage {
|
|||
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : '';
|
||||
|
||||
final newContactMessage = newContactAddress != null
|
||||
? '\n${S.of(context).add_contact_to_address_book}' : '';
|
||||
? '\n${S.of(_dialogContext).add_contact_to_address_book}' : '';
|
||||
|
||||
final alertContent =
|
||||
"$successMessage$waitMessage$newContactMessage";
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -456,7 +457,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: sendViewModel.hasFeesPriority
|
||||
? () => _setTransactionPriority(context)
|
||||
? () => pickTransactionPriority(context)
|
||||
: () {},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
|
@ -669,22 +670,41 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
Future<void> _setTransactionPriority(BuildContext context) async {
|
||||
Future<void> pickTransactionPriority(BuildContext context) async {
|
||||
final items = priorityForWalletType(sendViewModel.walletType);
|
||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
||||
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
||||
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
selectedAtIndex: selectedItem,
|
||||
title: S.of(context).please_select,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
int selectedIdx = selectedItem;
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (TransactionPriority priority) =>
|
||||
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: customItemIndex,
|
||||
title: S.of(context).please_select,
|
||||
headerEnabled: !isBitcoinWallet,
|
||||
closeOnItemSelected: !isBitcoinWallet,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: customFeeRate,
|
||||
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
|
||||
onItemSelected: (TransactionPriority priority) {
|
||||
sendViewModel.setTransactionPriority(priority);
|
||||
setState(() => selectedIdx = items.indexOf(priority));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isBitcoinWallet) sendViewModel.customBitcoinFeeRate = customFeeRate!.round();
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) {
|
||||
|
|
|
@ -2,11 +2,12 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.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/settings/widgets/setting_priority_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -27,13 +28,23 @@ class OtherSettingsPage extends BasePage {
|
|||
child: Column(
|
||||
children: [
|
||||
if (_otherSettingsViewModel.displayTransactionPriority)
|
||||
SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
||||
),
|
||||
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
|
||||
SettingsPriorityPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
|
||||
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
|
||||
) :
|
||||
SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
||||
),
|
||||
if (_otherSettingsViewModel.changeRepresentativeEnabled)
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.change_rep,
|
||||
|
|
|
@ -55,7 +55,9 @@ class PrivacyPage extends BasePage {
|
|||
}),
|
||||
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.auto_generate_subaddresses,
|
||||
title: _privacySettingsViewModel.isMoneroWallet
|
||||
? S.current.auto_generate_subaddresses
|
||||
: S.current.auto_generate_addresses,
|
||||
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
|
||||
onValueChange: (BuildContext _, bool value) {
|
||||
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
|
||||
class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
|
||||
SettingsPriorityPickerCell(
|
||||
{required String title,
|
||||
required this.selectedItem,
|
||||
required this.items,
|
||||
this.displayItem,
|
||||
this.images,
|
||||
this.searchHintText,
|
||||
this.isGridView = false,
|
||||
this.matchingCriteria,
|
||||
this.customValue,
|
||||
this.customItemIndex,
|
||||
this.onItemSelected})
|
||||
: super(
|
||||
title: title,
|
||||
isSelected: false,
|
||||
onTap: (BuildContext context) async {
|
||||
var selectedAtIndex = items.indexOf(selectedItem);
|
||||
double sliderValue = customValue ?? 0.0;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Picker(
|
||||
items: items,
|
||||
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
|
||||
selectedAtIndex: selectedAtIndex,
|
||||
customItemIndex: customItemIndex,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: sliderValue,
|
||||
onSliderChanged: (double newValue) =>
|
||||
setState(() => sliderValue = newValue),
|
||||
onItemSelected: (ItemType priority) {
|
||||
setState(() => selectedAtIndex = items.indexOf(priority));
|
||||
onItemSelected?.call(priority, sliderValue);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
onItemSelected?.call(items[selectedAtIndex], sliderValue);
|
||||
});
|
||||
|
||||
final ItemType selectedItem;
|
||||
final List<ItemType> items;
|
||||
final void Function(ItemType item, double customValue)? onItemSelected;
|
||||
final String Function(ItemType item, int value)? displayItem;
|
||||
final List<Image>? images;
|
||||
final String? searchHintText;
|
||||
final bool isGridView;
|
||||
final bool Function(ItemType, String)? matchingCriteria;
|
||||
double? customValue;
|
||||
int? customItemIndex;
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) {
|
||||
return Text(
|
||||
displayItem?.call(selectedItem,customValue?.round() ?? 0) ?? selectedItem.toString(),
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class StandardPickerListItem<T> extends TransactionDetailsListItem {
|
||||
StandardPickerListItem(
|
||||
{required String title,
|
||||
required String value,
|
||||
required this.items,
|
||||
required this.displayItem,
|
||||
required this.onSliderChanged,
|
||||
required this.onItemSelected,
|
||||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
required this.customValue})
|
||||
: super(title: title, value: value);
|
||||
|
||||
final List<T> items;
|
||||
final String Function(T item, double sliderValue) displayItem;
|
||||
final Function(double) onSliderChanged;
|
||||
final Function(T) onItemSelected;
|
||||
final int selectedIdx;
|
||||
final int customItemIndex;
|
||||
double customValue;
|
||||
}
|
199
lib/src/screens/transaction_details/rbf_details_page.dart
Normal file
199
lib/src/screens/transaction_details/rbf_details_page.dart
Normal file
|
@ -0,0 +1,199 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class RBFDetailsPage extends BasePage {
|
||||
RBFDetailsPage({required this.transactionDetailsViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.bump_fee;
|
||||
|
||||
final TransactionDetailsViewModel transactionDetailsViewModel;
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.RBFListItems[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: '${item.title}: ${item.expandableItems.length}',
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString());
|
||||
},
|
||||
text: S.of(context).send,
|
||||
isLoading:
|
||||
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
if (state is AwaitingConfirmationState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: state.title ?? '',
|
||||
alertContent: state.message ?? '',
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return ConfirmSendingAlert(
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction();
|
||||
// transactionStatePopup();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop(),
|
||||
feeFiatAmount:
|
||||
transactionDetailsViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
fiatAmountValue:
|
||||
transactionDetailsViewModel.pendingTransactionFiatAmountValueFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
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/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class TransactionDetailsPage extends BasePage {
|
||||
TransactionDetailsPage({required this.transactionDetailsViewModel});
|
||||
|
@ -21,41 +24,62 @@ class TransactionDetailsPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.items.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.items[index];
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.items.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.items[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context,
|
||||
S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child:
|
||||
ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is BlockExplorerListItem) {
|
||||
return GestureDetector(
|
||||
onTap: item.onTap,
|
||||
child:
|
||||
ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is BlockExplorerListItem) {
|
||||
return GestureDetector(
|
||||
onTap: item.onTap,
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
});
|
||||
return Container();
|
||||
}),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (transactionDetailsViewModel.canReplaceByFee) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SelectButton(
|
||||
text: S.of(context).bump_fee,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.bumpFeePage,
|
||||
arguments: transactionDetailsViewModel.transactionInfo);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class StandardExpandableListItem<T> extends TransactionDetailsListItem {
|
||||
StandardExpandableListItem({required String title, required this.expandableItems})
|
||||
: super(title: title, value: '');
|
||||
final List<T> expandableItems;
|
||||
}
|
|
@ -30,7 +30,7 @@ class PairingItemWidget extends StatelessWidget {
|
|||
leading: CakeImageWidget(
|
||||
imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0]: null,
|
||||
displayOnError: CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/images/default_icon.png'),
|
||||
backgroundImage: AssetImage('assets/images/walletconnect_logo.png'),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
|
|
|
@ -18,7 +18,7 @@ class CakeImageWidget extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
try {
|
||||
if (imageUrl == null) return _displayOnError!;
|
||||
if (imageUrl == null || imageUrl!.isEmpty) return _displayOnError!;
|
||||
|
||||
if (imageUrl!.contains('assets/images')) {
|
||||
return Image.asset(
|
||||
|
|
|
@ -31,7 +31,6 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
|
|||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 24.0,
|
||||
|
|
|
@ -4,15 +4,15 @@ import 'package:flutter/material.dart';
|
|||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
|
||||
class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||
|
||||
|
||||
DashBoardRoundedCardWidget({
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
this.onClose,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onClose;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
|
@ -26,7 +26,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
|||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
|
@ -35,32 +35,40 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
|||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
),
|
||||
),
|
||||
child:
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
],
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (onClose != null)
|
||||
Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: onClose,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,13 @@ class Picker<Item> extends StatefulWidget {
|
|||
this.isGridView = false,
|
||||
this.isSeparated = true,
|
||||
this.hintText,
|
||||
this.headerEnabled = true,
|
||||
this.closeOnItemSelected = true,
|
||||
this.sliderValue,
|
||||
this.customItemIndex,
|
||||
this.isWrapped = true,
|
||||
this.borderColor,
|
||||
this.onSliderChanged,
|
||||
this.matchingCriteria,
|
||||
}) : assert(hintText == null ||
|
||||
matchingCriteria !=
|
||||
|
@ -40,6 +47,13 @@ class Picker<Item> extends StatefulWidget {
|
|||
final bool isGridView;
|
||||
final bool isSeparated;
|
||||
final String? hintText;
|
||||
final bool headerEnabled;
|
||||
final bool closeOnItemSelected;
|
||||
final double? sliderValue;
|
||||
final int? customItemIndex;
|
||||
final bool isWrapped;
|
||||
final Color? borderColor;
|
||||
final Function(double)? onSliderChanged;
|
||||
final bool Function(Item, String)? matchingCriteria;
|
||||
|
||||
@override
|
||||
|
@ -124,8 +138,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
containerHeight = height * 0.75;
|
||||
}
|
||||
|
||||
return PickerWrapperWidget(
|
||||
hasTitle: widget.title?.isNotEmpty ?? false,
|
||||
final content = Column (
|
||||
children: [
|
||||
if (widget.title?.isNotEmpty ?? false)
|
||||
Container(
|
||||
|
@ -144,61 +157,71 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: containerHeight,
|
||||
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.hintText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SearchBarWidget(searchController: searchController),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: widget.borderColor ?? Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: containerHeight,
|
||||
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.hintText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SearchBarWidget(
|
||||
searchController: searchController, hintText: widget.hintText),
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
height: 1,
|
||||
),
|
||||
Divider(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
height: 1,
|
||||
),
|
||||
if (widget.selectedAtIndex != -1) buildSelectedItem(widget.selectedAtIndex),
|
||||
Flexible(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
filteredItems.length > 3
|
||||
? Scrollbar(
|
||||
controller: controller,
|
||||
child: itemsList(),
|
||||
)
|
||||
: itemsList(),
|
||||
(widget.description?.isNotEmpty ?? false)
|
||||
? Positioned(
|
||||
bottom: padding,
|
||||
left: padding,
|
||||
right: padding,
|
||||
child: Text(
|
||||
widget.description!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
if (widget.selectedAtIndex != -1 && widget.headerEnabled)
|
||||
buildSelectedItem(widget.selectedAtIndex),
|
||||
Flexible(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
filteredItems.length > 3
|
||||
? Scrollbar(
|
||||
controller: controller,
|
||||
child: itemsList(),
|
||||
)
|
||||
: itemsList(),
|
||||
(widget.description?.isNotEmpty ?? false)
|
||||
? Positioned(
|
||||
bottom: padding,
|
||||
left: padding,
|
||||
right: padding,
|
||||
child: Text(
|
||||
widget.description!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato',
|
||||
decoration: TextDecoration.none,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
)
|
||||
: Offstage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -206,9 +229,23 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
)
|
||||
],
|
||||
);
|
||||
|
||||
if (widget.isWrapped) {
|
||||
return PickerWrapperWidget(
|
||||
hasTitle: widget.title?.isNotEmpty ?? false,
|
||||
children: [content],
|
||||
);
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
Widget itemsList() {
|
||||
final itemCount = !widget.headerEnabled
|
||||
? items.length
|
||||
: filteredItems.isEmpty
|
||||
? 0
|
||||
: filteredItems.length;
|
||||
return Container(
|
||||
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
child: widget.isGridView
|
||||
|
@ -216,13 +253,16 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
padding: EdgeInsets.zero,
|
||||
controller: controller,
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
|
||||
itemCount: itemCount,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 2,
|
||||
childAspectRatio: 3,
|
||||
),
|
||||
itemBuilder: (context, index) => buildItem(index),
|
||||
itemBuilder: (context, index) =>
|
||||
!widget.headerEnabled && widget.selectedAtIndex == index
|
||||
? buildSelectedItem(index)
|
||||
: buildItem(index),
|
||||
)
|
||||
: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
|
@ -234,83 +274,97 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
height: 1,
|
||||
)
|
||||
: const SizedBox(),
|
||||
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
|
||||
itemBuilder: (context, index) => buildItem(index),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) =>
|
||||
!widget.headerEnabled && widget.selectedAtIndex == index
|
||||
? buildSelectedItem(index)
|
||||
: buildItem(index),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildItem(int index) {
|
||||
final item = filteredItems[index];
|
||||
final item = widget.headerEnabled ? filteredItems[index] : items[index];
|
||||
|
||||
final tag = item is Currency ? item.tag : null;
|
||||
final icon = _getItemIcon(item);
|
||||
|
||||
final image = images.isNotEmpty ? filteredImages[index] : icon;
|
||||
|
||||
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
||||
|
||||
final itemContent = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||
onItemSelected(item!);
|
||||
},
|
||||
child: Container(
|
||||
height: 55,
|
||||
height: isCustomItem ? 95 : 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color:
|
||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: isCustomItem
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
itemContent,
|
||||
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
|
||||
],
|
||||
)
|
||||
: itemContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -323,69 +377,80 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
|
||||
final image = images.isNotEmpty ? images[index] : icon;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
height: 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
|
||||
|
||||
final itemContent = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.mainAxisAlignment,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: image != null ? 12 : 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.displayItem?.call(item) ?? item.toString(),
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color:
|
||||
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
|
||||
],
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.closeOnItemSelected) Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
height: isCustomItem ? 95 : 55,
|
||||
color: Theme.of(context).dialogTheme.backgroundColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: isCustomItem
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
itemContent,
|
||||
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
|
||||
],
|
||||
)
|
||||
: itemContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -418,4 +483,20 @@ class _PickerState<Item> extends State<Picker<Item>> {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget buildSlider({required int index, required bool isActivated}) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: widget.sliderValue ?? 1,
|
||||
onChanged: isActivated ? widget.onSliderChanged : null,
|
||||
min: 1,
|
||||
max: 100,
|
||||
divisions: 100,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,21 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScrollableWithBottomSection extends StatefulWidget {
|
||||
ScrollableWithBottomSection(
|
||||
{required this.content,
|
||||
required this.bottomSection,
|
||||
this.contentPadding,
|
||||
this.bottomSectionPadding});
|
||||
ScrollableWithBottomSection({
|
||||
required this.content,
|
||||
required this.bottomSection,
|
||||
this.topSection,
|
||||
this.contentPadding,
|
||||
this.bottomSectionPadding,
|
||||
this.topSectionPadding,
|
||||
});
|
||||
|
||||
final Widget content;
|
||||
final Widget bottomSection;
|
||||
final Widget? topSection;
|
||||
final EdgeInsets? contentPadding;
|
||||
final EdgeInsets? bottomSectionPadding;
|
||||
final EdgeInsets? topSectionPadding;
|
||||
|
||||
@override
|
||||
ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState();
|
||||
|
@ -22,6 +27,12 @@ class ScrollableWithBottomSectionState extends State<ScrollableWithBottomSection
|
|||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
if (widget.topSection != null)
|
||||
Padding(
|
||||
padding: widget.topSectionPadding?.copyWith(top: 10) ??
|
||||
EdgeInsets.only(top: 10, bottom: 20, right: 20, left: 20),
|
||||
child: widget.topSection,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
|
|
|
@ -19,7 +19,7 @@ class SearchBarWidget extends StatelessWidget {
|
|||
controller: searchController,
|
||||
style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText ?? S.of(context).search_currency,
|
||||
hintText: hintText ?? S.of(context).search,
|
||||
hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
prefixIcon: Image.asset("assets/images/search_icon.png",
|
||||
color: Theme.of(context).extension<PickerTheme>()!.searchIconColor),
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/service_status.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/service_status_tile.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
@ -12,8 +15,9 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
|
||||
class ServicesUpdatesWidget extends StatefulWidget {
|
||||
final Future<ServicesResponse> servicesResponse;
|
||||
final bool enabled;
|
||||
|
||||
const ServicesUpdatesWidget(this.servicesResponse, {super.key});
|
||||
const ServicesUpdatesWidget(this.servicesResponse, {super.key, required this.enabled});
|
||||
|
||||
@override
|
||||
State<ServicesUpdatesWidget> createState() => _ServicesUpdatesWidgetState();
|
||||
|
@ -24,6 +28,27 @@ class _ServicesUpdatesWidgetState extends State<ServicesUpdatesWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.enabled) {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.service_health_disabled,
|
||||
alertContent: S.current.service_health_disabled_message,
|
||||
buttonText: S.current.ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
});
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
"assets/images/notification_icon.svg",
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
||||
width: 30,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FutureBuilder<ServicesResponse>(
|
||||
|
|
58
lib/src/widgets/standard_expandable_list.dart
Normal file
58
lib/src/widgets/standard_expandable_list.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardExpandableList<T> extends StatelessWidget {
|
||||
StandardExpandableList({
|
||||
required this.title,
|
||||
required this.expandableItems,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<T> expandableItems;
|
||||
final Decoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: decoration ??
|
||||
BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
iconColor: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
collapsedIconColor:
|
||||
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
children: expandableItems.map((item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, bottom: 8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
item.toString(),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
81
lib/src/widgets/standard_picker_list.dart
Normal file
81
lib/src/widgets/standard_picker_list.dart
Normal file
|
@ -0,0 +1,81 @@
|
|||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StandardPickerList<T> extends StatefulWidget {
|
||||
StandardPickerList({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.items,
|
||||
required this.displayItem,
|
||||
required this.onSliderChanged,
|
||||
required this.onItemSelected,
|
||||
required this.selectedIdx,
|
||||
required this.customItemIndex,
|
||||
required this.customValue,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final List<T> items;
|
||||
final int customItemIndex;
|
||||
final String Function(T item, double sliderValue) displayItem;
|
||||
final Function(double) onSliderChanged;
|
||||
final Function(T) onItemSelected;
|
||||
final String value;
|
||||
final int selectedIdx;
|
||||
final double customValue;
|
||||
|
||||
@override
|
||||
_StandardPickerListState<T> createState() => _StandardPickerListState<T>();
|
||||
}
|
||||
|
||||
class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
|
||||
late String value;
|
||||
late int selectedIdx;
|
||||
late double customValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
value = widget.value;
|
||||
selectedIdx = widget.selectedIdx;
|
||||
customValue = widget.customValue;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String adaptedDisplayItem(T item) => widget.displayItem(item, customValue);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
ListRow(title: '${widget.title}:', value: value),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24, top: 0, bottom: 24),
|
||||
child: Picker(
|
||||
items: widget.items,
|
||||
displayItem: adaptedDisplayItem,
|
||||
selectedAtIndex: selectedIdx,
|
||||
customItemIndex: widget.customItemIndex,
|
||||
headerEnabled: false,
|
||||
closeOnItemSelected: false,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
sliderValue: customValue,
|
||||
isWrapped: false,
|
||||
borderColor: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
onSliderChanged: (newValue) {
|
||||
setState(() => customValue = newValue);
|
||||
value = widget.onSliderChanged(newValue).toString();
|
||||
},
|
||||
onItemSelected: (T item) {
|
||||
setState(() => selectedIdx = widget.items.indexOf(item));
|
||||
value = widget.onItemSelected(item).toString();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -79,6 +79,7 @@ abstract class SettingsStoreBase with Store {
|
|||
required Map<WalletType, Node> nodes,
|
||||
required Map<WalletType, Node> powNodes,
|
||||
required this.shouldShowYatPopup,
|
||||
required this.shouldShowRepWarning,
|
||||
required this.isBitcoinBuyEnabled,
|
||||
required this.actionlistDisplayMode,
|
||||
required this.pinTimeOutDuration,
|
||||
|
@ -105,6 +106,7 @@ abstract class SettingsStoreBase with Store {
|
|||
required this.lookupsUnstoppableDomains,
|
||||
required this.lookupsOpenAlias,
|
||||
required this.lookupsENS,
|
||||
required this.customBitcoinFeeRate,
|
||||
TransactionPriority? initialBitcoinTransactionPriority,
|
||||
TransactionPriority? initialMoneroTransactionPriority,
|
||||
TransactionPriority? initialHavenTransactionPriority,
|
||||
|
@ -224,6 +226,9 @@ abstract class SettingsStoreBase with Store {
|
|||
(bool shouldShowYatPopup) =>
|
||||
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
|
||||
|
||||
reaction((_) => shouldShowRepWarning,
|
||||
(bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
|
||||
|
||||
defaultBuyProviders.observe((change) {
|
||||
final String key = 'buyProvider_${change.key.toString()}';
|
||||
if (change.newValue != null) {
|
||||
|
@ -504,6 +509,11 @@ abstract class SettingsStoreBase with Store {
|
|||
(PinCodeRequiredDuration pinCodeInterval) => secureStorage.write(
|
||||
key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString()));
|
||||
|
||||
reaction(
|
||||
(_) => customBitcoinFeeRate,
|
||||
(int customBitcoinFeeRate) =>
|
||||
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate));
|
||||
|
||||
this.nodes.observe((change) {
|
||||
if (change.newValue != null && change.key != null) {
|
||||
_saveCurrentNode(change.newValue!, change.key!);
|
||||
|
@ -531,6 +541,9 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
bool shouldShowYatPopup;
|
||||
|
||||
@observable
|
||||
bool shouldShowRepWarning;
|
||||
|
||||
@observable
|
||||
bool shouldShowMarketPlaceInDashboard;
|
||||
|
||||
|
@ -691,6 +704,9 @@ abstract class SettingsStoreBase with Store {
|
|||
|
||||
String deviceName;
|
||||
|
||||
@observable
|
||||
int customBitcoinFeeRate;
|
||||
|
||||
final SecureStorage _secureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final BackgroundTasks _backgroundTasks;
|
||||
|
@ -835,6 +851,7 @@ abstract class SettingsStoreBase with Store {
|
|||
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
|
||||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||
|
||||
// If no value
|
||||
if (pinLength == null || pinLength == 0) {
|
||||
|
@ -869,6 +886,8 @@ abstract class SettingsStoreBase with Store {
|
|||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final deviceName = await _getDeviceName() ?? '';
|
||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||
final shouldShowRepWarning =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
|
||||
|
||||
final generateSubaddresses =
|
||||
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
|
||||
|
@ -1025,74 +1044,77 @@ abstract class SettingsStoreBase with Store {
|
|||
'';
|
||||
|
||||
return SettingsStore(
|
||||
secureStorage: secureStorage,
|
||||
sharedPreferences: sharedPreferences,
|
||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
appVersion: packageInfo.version,
|
||||
deviceName: deviceName,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
initialFiatCurrency: currentFiatCurrency,
|
||||
initialBalanceDisplayMode: currentBalanceDisplayMode,
|
||||
initialSaveRecipientAddress: shouldSaveRecipientAddress,
|
||||
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
|
||||
initialMoneroSeedType: moneroSeedType,
|
||||
initialAppSecure: isAppSecure,
|
||||
initialDisableBuy: disableBuy,
|
||||
initialDisableSell: disableSell,
|
||||
initialDisableBulletin: disableBulletin,
|
||||
initialWalletListOrder: walletListOrder,
|
||||
initialWalletListAscending: walletListAscending,
|
||||
initialFiatMode: currentFiatApiMode,
|
||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||
initialUseTOTP2FA: useTOTP2FA,
|
||||
initialTotpSecretKey: totpSecretKey,
|
||||
initialFailedTokenTrial: tokenTrialNumber,
|
||||
initialExchangeStatus: exchangeStatus,
|
||||
initialTheme: savedTheme,
|
||||
actionlistDisplayMode: actionListDisplayMode,
|
||||
initialPinLength: pinLength,
|
||||
pinTimeOutDuration: pinCodeTimeOutDuration,
|
||||
seedPhraseLength: seedPhraseWordCount,
|
||||
initialLanguageCode: savedLanguageCode,
|
||||
sortBalanceBy: sortBalanceBy,
|
||||
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
||||
useEtherscan: useEtherscan,
|
||||
usePolygonScan: usePolygonScan,
|
||||
defaultNanoRep: defaultNanoRep,
|
||||
defaultBananoRep: defaultBananoRep,
|
||||
lookupsTwitter: lookupsTwitter,
|
||||
lookupsMastodon: lookupsMastodon,
|
||||
lookupsYatService: lookupsYatService,
|
||||
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
|
||||
lookupsOpenAlias: lookupsOpenAlias,
|
||||
lookupsENS: lookupsENS,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
initialLitecoinTransactionPriority: litecoinTransactionPriority,
|
||||
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
|
||||
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
|
||||
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
|
||||
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
|
||||
initialShouldRequireTOTP2FAForSendsToInternalWallets:
|
||||
shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
|
||||
shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
|
||||
shouldRequireTOTP2FAForExchangesToExternalWallets,
|
||||
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
|
||||
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
||||
initialPolygonTransactionPriority: polygonTransactionPriority,
|
||||
backgroundTasks: backgroundTasks,
|
||||
initialSyncMode: savedSyncMode,
|
||||
initialSyncAll: savedSyncAll,
|
||||
shouldShowYatPopup: shouldShowYatPopup);
|
||||
secureStorage: secureStorage,
|
||||
sharedPreferences: sharedPreferences,
|
||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
appVersion: packageInfo.version,
|
||||
deviceName: deviceName,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
initialFiatCurrency: currentFiatCurrency,
|
||||
initialBalanceDisplayMode: currentBalanceDisplayMode,
|
||||
initialSaveRecipientAddress: shouldSaveRecipientAddress,
|
||||
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
|
||||
initialMoneroSeedType: moneroSeedType,
|
||||
initialAppSecure: isAppSecure,
|
||||
initialDisableBuy: disableBuy,
|
||||
initialDisableSell: disableSell,
|
||||
initialDisableBulletin: disableBulletin,
|
||||
initialWalletListOrder: walletListOrder,
|
||||
initialWalletListAscending: walletListAscending,
|
||||
initialFiatMode: currentFiatApiMode,
|
||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||
initialUseTOTP2FA: useTOTP2FA,
|
||||
initialTotpSecretKey: totpSecretKey,
|
||||
initialFailedTokenTrial: tokenTrialNumber,
|
||||
initialExchangeStatus: exchangeStatus,
|
||||
initialTheme: savedTheme,
|
||||
actionlistDisplayMode: actionListDisplayMode,
|
||||
initialPinLength: pinLength,
|
||||
pinTimeOutDuration: pinCodeTimeOutDuration,
|
||||
seedPhraseLength: seedPhraseWordCount,
|
||||
initialLanguageCode: savedLanguageCode,
|
||||
sortBalanceBy: sortBalanceBy,
|
||||
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
||||
useEtherscan: useEtherscan,
|
||||
usePolygonScan: usePolygonScan,
|
||||
defaultNanoRep: defaultNanoRep,
|
||||
defaultBananoRep: defaultBananoRep,
|
||||
lookupsTwitter: lookupsTwitter,
|
||||
lookupsMastodon: lookupsMastodon,
|
||||
lookupsYatService: lookupsYatService,
|
||||
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
|
||||
lookupsOpenAlias: lookupsOpenAlias,
|
||||
lookupsENS: lookupsENS,
|
||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
initialLitecoinTransactionPriority: litecoinTransactionPriority,
|
||||
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
|
||||
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
|
||||
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
|
||||
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
|
||||
initialShouldRequireTOTP2FAForSendsToInternalWallets:
|
||||
shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
|
||||
shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
|
||||
shouldRequireTOTP2FAForExchangesToExternalWallets,
|
||||
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
|
||||
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
||||
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
||||
initialPolygonTransactionPriority: polygonTransactionPriority,
|
||||
backgroundTasks: backgroundTasks,
|
||||
initialSyncMode: savedSyncMode,
|
||||
initialSyncAll: savedSyncAll,
|
||||
shouldShowYatPopup: shouldShowYatPopup,
|
||||
shouldShowRepWarning: shouldShowRepWarning,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reload({required Box<Node> nodeSource}) async {
|
||||
|
@ -1160,7 +1182,8 @@ abstract class SettingsStoreBase with Store {
|
|||
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
||||
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
|
||||
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
|
||||
disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
|
||||
disableBulletin =
|
||||
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
|
||||
walletListOrder =
|
||||
WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
|
||||
walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
|
||||
|
@ -1187,6 +1210,8 @@ abstract class SettingsStoreBase with Store {
|
|||
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
|
||||
shouldShowYatPopup =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
|
||||
shouldShowRepWarning =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
|
||||
sortBalanceBy = SortBalanceBy
|
||||
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
|
||||
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||
|
@ -1201,7 +1226,7 @@ abstract class SettingsStoreBase with Store {
|
|||
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
|
||||
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
|
||||
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final bitcoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
|
|
@ -46,6 +46,10 @@ abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
|
|||
(orientation == Orientation.portrait && screenWidth < screenHeight) ||
|
||||
(orientation == Orientation.landscape && screenWidth < screenHeight);
|
||||
}
|
||||
|
||||
bool get shouldRenderTabletUI {
|
||||
return screenWidth > _kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
|
||||
}
|
||||
}
|
||||
|
||||
_ResponsiveLayoutUtil _singletonResponsiveLayoutUtil = _ResponsiveLayoutUtil();
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:cake_wallet/entities/service_status.dart';
|
|||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
|
@ -370,6 +371,18 @@ abstract class DashboardViewModelBase with Store {
|
|||
@computed
|
||||
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
|
||||
|
||||
bool get showRepWarning {
|
||||
if (wallet.type != WalletType.nano) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!settingsStore.shouldShowRepWarning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !nano!.isRepOk(wallet);
|
||||
}
|
||||
|
||||
Future<void> reconnect() async {
|
||||
final node = appStore.settingsStore.getCurrentNode(wallet.type);
|
||||
await wallet.connectToNode(node: node);
|
||||
|
@ -534,4 +547,8 @@ abstract class DashboardViewModelBase with Store {
|
|||
return ServicesResponse([], false, '');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshDashboard() async {
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
symbol: token.title,
|
||||
decimal: token.decimals,
|
||||
contractAddress: contractAddress,
|
||||
iconPath: token.iconPath,
|
||||
);
|
||||
|
||||
await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
|
||||
|
@ -65,6 +66,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
symbol: token.title,
|
||||
decimal: token.decimals,
|
||||
contractAddress: contractAddress,
|
||||
iconPath: token.iconPath,
|
||||
);
|
||||
await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
|
||||
}
|
||||
|
|
|
@ -12,16 +12,15 @@ import 'package:permission_handler/permission_handler.dart';
|
|||
|
||||
part 'node_create_or_edit_view_model.g.dart';
|
||||
|
||||
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
|
||||
with _$NodeCreateOrEditViewModel;
|
||||
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
|
||||
|
||||
abstract class NodeCreateOrEditViewModelBase with Store {
|
||||
NodeCreateOrEditViewModelBase(
|
||||
this._nodeSource, this._walletType, this._settingsStore)
|
||||
NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
|
||||
: state = InitialExecutionState(),
|
||||
connectionState = InitialExecutionState(),
|
||||
useSSL = false,
|
||||
address = '',
|
||||
path = '',
|
||||
port = '',
|
||||
login = '',
|
||||
password = '',
|
||||
|
@ -35,6 +34,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
String path;
|
||||
|
||||
@observable
|
||||
String port;
|
||||
|
||||
|
@ -84,6 +86,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
@action
|
||||
void reset() {
|
||||
address = '';
|
||||
path = '';
|
||||
port = '';
|
||||
login = '';
|
||||
password = '';
|
||||
|
@ -99,6 +102,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
@action
|
||||
void setAddress(String val) => address = val;
|
||||
|
||||
@action
|
||||
void setPath(String val) => path = val;
|
||||
|
||||
@action
|
||||
void setLogin(String val) => login = val;
|
||||
|
||||
|
@ -121,6 +127,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
|
||||
final node = Node(
|
||||
uri: uri,
|
||||
path: path,
|
||||
type: _walletType,
|
||||
login: login,
|
||||
password: password,
|
||||
|
@ -151,6 +158,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
Future<void> connect() async {
|
||||
final node = Node(
|
||||
uri: uri,
|
||||
path: path,
|
||||
type: _walletType,
|
||||
login: login,
|
||||
password: password,
|
||||
|
@ -183,7 +191,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
Future<void> scanQRCodeForNewNode(BuildContext context) async {
|
||||
try {
|
||||
bool isCameraPermissionGranted =
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
if (!isCameraPermissionGranted) return;
|
||||
String code = await presentQRScanner();
|
||||
|
||||
|
@ -198,7 +206,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
}
|
||||
|
||||
final userInfo = uri.userInfo.split(':');
|
||||
|
||||
|
||||
if (userInfo.length < 2) {
|
||||
throw Exception('Unexpected scan QR code value: Value is invalid');
|
||||
}
|
||||
|
@ -207,8 +215,11 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
final rpcPassword = userInfo[1];
|
||||
final ipAddress = uri.host;
|
||||
final port = uri.port.toString();
|
||||
final path = uri.path;
|
||||
|
||||
|
||||
setAddress(ipAddress);
|
||||
setPath(path);
|
||||
setPassword(rpcPassword);
|
||||
setLogin(rpcUser);
|
||||
setPort(port);
|
||||
|
|
|
@ -121,11 +121,19 @@ abstract class OutputBase with Store {
|
|||
return solana!.getEstimateFees(_wallet) ?? 0.0;
|
||||
}
|
||||
|
||||
final fee = _wallet.calculateEstimatedFee(
|
||||
int? fee = _wallet.calculateEstimatedFee(
|
||||
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) {
|
||||
fee = bitcoin!.getFeeAmountWithFeeRate(
|
||||
_settingsStore.customBitcoinFeeRate, formattedCryptoAmount, 1, 1);
|
||||
}
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash) {
|
||||
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import 'package:cake_wallet/entities/parsed_address.dart';
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
part 'send_view_model.g.dart';
|
||||
|
||||
|
@ -68,9 +69,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
_settingsStore = appStore.settingsStore,
|
||||
fiatFromSettings = appStore.settingsStore.fiatCurrency,
|
||||
super(appStore: appStore) {
|
||||
if (wallet.type == WalletType.bitcoin &&
|
||||
_settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) {
|
||||
setTransactionPriority(bitcoinTransactionPriorityMedium);
|
||||
}
|
||||
final priority = _settingsStore.priority[wallet.type];
|
||||
final priorities = priorityForWalletType(wallet.type);
|
||||
|
||||
if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
|
||||
_settingsStore.priority[wallet.type] = priorities.first;
|
||||
}
|
||||
|
@ -152,6 +156,21 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
return priority;
|
||||
}
|
||||
|
||||
int? getCustomPriorityIndex(List<TransactionPriority> priorities) {
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
final customItem = priorities
|
||||
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
|
||||
|
||||
return customItem != null ? priorities.indexOf(customItem) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@computed
|
||||
int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate;
|
||||
|
||||
void set customBitcoinFeeRate(int value) => _settingsStore.customBitcoinFeeRate = value;
|
||||
|
||||
CryptoCurrency get currency => wallet.currency;
|
||||
|
||||
Validator<String> get amountValidator =>
|
||||
|
@ -308,13 +327,42 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||
if (provider is ThorChainExchangeProvider) {
|
||||
final outputCount = pendingTransaction?.outputCount ?? 0;
|
||||
if (outputCount > 10) throw Exception("ThorChain does not support more than 10 outputs");
|
||||
if (outputCount > 10) {
|
||||
throw Exception("ThorChain does not support more than 10 outputs");
|
||||
}
|
||||
if (_hasTaprootInput(pendingTransaction)) {
|
||||
throw Exception("ThorChain does not support Taproot addresses");
|
||||
}
|
||||
}
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> replaceByFee(String txId, String newFee) async {
|
||||
state = IsExecutingState();
|
||||
|
||||
final isSufficient = await bitcoin!.isChangeSufficientForFee(wallet, txId, newFee);
|
||||
|
||||
if (!isSufficient) {
|
||||
state = AwaitingConfirmationState(
|
||||
title: S.current.confirm_fee_deduction,
|
||||
message: S.current.confirm_fee_deduction_content,
|
||||
onConfirm: () async {
|
||||
pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee);
|
||||
state = ExecutedSuccessfullyState();
|
||||
},
|
||||
onCancel: () {
|
||||
state = FailureState('Insufficient change for fee');
|
||||
});
|
||||
} else {
|
||||
pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee);
|
||||
state = ExecutedSuccessfullyState();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -374,7 +422,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority!);
|
||||
return bitcoin!.createBitcoinTransactionCredentials(outputs,
|
||||
priority: priority!, feeRate: customBitcoinFeeRate);
|
||||
|
||||
case WalletType.monero:
|
||||
return monero!
|
||||
|
@ -400,9 +449,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
}
|
||||
}
|
||||
|
||||
String displayFeeRate(dynamic priority) {
|
||||
String displayFeeRate(dynamic priority, int? customValue) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (walletType == WalletType.bitcoin) {
|
||||
final rate = bitcoin!.getFeeRate(wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
|
||||
}
|
||||
|
||||
if (isElectrumWallet) {
|
||||
final rate = bitcoin!.getFeeRate(wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
|
||||
|
@ -414,6 +468,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
bool _isEqualCurrency(String currency) =>
|
||||
wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
|
||||
|
||||
TransactionPriority get bitcoinTransactionPriorityCustom =>
|
||||
bitcoin!.getBitcoinTransactionPriorityCustom();
|
||||
|
||||
TransactionPriority get bitcoinTransactionPriorityMedium =>
|
||||
bitcoin!.getBitcoinTransactionPriorityMedium();
|
||||
|
||||
@action
|
||||
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
||||
|
||||
|
@ -443,7 +503,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
address = output.address;
|
||||
}
|
||||
|
||||
if (address.isNotEmpty && !contactAddresses.contains(address)) {
|
||||
if (address.isNotEmpty &&
|
||||
!contactAddresses.contains(address) &&
|
||||
selectedCryptoCurrency.raw != -1) {
|
||||
return ContactRecord(
|
||||
contactListViewModel.contactSource,
|
||||
Contact(
|
||||
|
@ -512,4 +574,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
bool _hasTaprootInput(PendingTransaction? pendingTransaction) {
|
||||
if (walletType == WalletType.bitcoin && pendingTransaction != null) {
|
||||
return bitcoin!.hasTaprootInput(pendingTransaction);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:cw_core/wallet_base.dart';
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
part 'other_settings_view_model.g.dart';
|
||||
|
||||
|
@ -77,6 +78,8 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
ProviderType get sellProviderType =>
|
||||
_settingsStore.defaultSellProviders[walletType] ?? ProviderType.askEachTime;
|
||||
|
||||
|
||||
|
||||
String getDisplayPriority(dynamic priority) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
|
@ -90,6 +93,19 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
return priority.toString();
|
||||
}
|
||||
|
||||
String getDisplayBitcoinPriority(dynamic priority, int customValue) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash) {
|
||||
final rate = bitcoin!.getFeeRate(_wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
|
||||
}
|
||||
|
||||
return priority.toString();
|
||||
}
|
||||
|
||||
String getBuyProviderType(dynamic buyProviderType) {
|
||||
final _buyProviderType = buyProviderType as ProviderType;
|
||||
return _buyProviderType == ProviderType.askEachTime
|
||||
|
@ -105,7 +121,24 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
}
|
||||
|
||||
void onDisplayPrioritySelected(TransactionPriority priority) =>
|
||||
_settingsStore.priority[_wallet.type] = priority;
|
||||
_settingsStore.priority[walletType] = priority;
|
||||
|
||||
void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) {
|
||||
if (_wallet.type == WalletType.bitcoin) {
|
||||
_settingsStore.customBitcoinFeeRate = customValue.round();
|
||||
}
|
||||
_settingsStore.priority[_wallet.type] = priority;
|
||||
}
|
||||
|
||||
@computed
|
||||
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
|
||||
|
||||
int? get customPriorityItemIndex {
|
||||
final priorities = priorityForWalletType(walletType);
|
||||
final customItem = priorities
|
||||
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
|
||||
return customItem != null ? priorities.indexOf(customItem) : null;
|
||||
}
|
||||
|
||||
@action
|
||||
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>
|
||||
|
|
|
@ -44,6 +44,8 @@ abstract class PrivacySettingsViewModelBase with Store {
|
|||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
bool get isMoneroWallet => _wallet.type == WalletType.monero;
|
||||
|
||||
@computed
|
||||
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
|
||||
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/src/intl/date_format.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
|
||||
part 'transaction_details_view_model.g.dart';
|
||||
|
||||
|
@ -26,8 +33,11 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
{required this.transactionInfo,
|
||||
required this.transactionDescriptionBox,
|
||||
required this.wallet,
|
||||
required this.settingsStore})
|
||||
required this.settingsStore,
|
||||
required this.sendViewModel})
|
||||
: items = [],
|
||||
RBFListItems = [],
|
||||
newFee = 0,
|
||||
isRecipientAddressShown = false,
|
||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||
final dateFormat = DateFormatter.withCurrentLocal();
|
||||
|
@ -38,6 +48,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
_addMoneroListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
_addElectrumListItems(tx, dateFormat);
|
||||
_addBumpFeesListItems(tx);
|
||||
_checkForRBF();
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
_addElectrumListItems(tx, dateFormat);
|
||||
|
@ -109,10 +123,20 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
final SettingsStore settingsStore;
|
||||
final WalletBase wallet;
|
||||
final SendViewModel sendViewModel;
|
||||
|
||||
final List<TransactionDetailsListItem> items;
|
||||
final List<TransactionDetailsListItem> RBFListItems;
|
||||
bool showRecipientAddress;
|
||||
bool isRecipientAddressShown;
|
||||
int newFee;
|
||||
TransactionPriority? transactionPriority;
|
||||
|
||||
@observable
|
||||
bool _canReplaceByFee = false;
|
||||
|
||||
@computed
|
||||
bool get canReplaceByFee => _canReplaceByFee /*&& transactionInfo.confirmations <= 0*/;
|
||||
|
||||
String _explorerUrl(WalletType type, String txId) {
|
||||
switch (type) {
|
||||
|
@ -305,4 +329,88 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
void _addBumpFeesListItems(TransactionInfo tx) {
|
||||
transactionPriority = bitcoin!.getBitcoinTransactionPriorityMedium();
|
||||
|
||||
newFee = bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
bitcoin!.getBitcoinTransactionPriorityMedium(),
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
|
||||
RBFListItems.add(StandartListItem(
|
||||
title: S.current.old_fee,
|
||||
value: tx.feeFormatted() ?? '0.0'));
|
||||
|
||||
final priorities = priorityForWalletType(wallet.type);
|
||||
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
|
||||
final customItem = priorities.firstWhereOrNull(
|
||||
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
|
||||
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
|
||||
|
||||
RBFListItems.add(StandardPickerListItem(
|
||||
title: S.current.estimated_new_fee,
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
|
||||
items: priorityForWalletType(wallet.type),
|
||||
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
|
||||
selectedIdx: selectedItem,
|
||||
customItemIndex: customItemIndex ?? 0,
|
||||
displayItem: (dynamic priority, double sliderValue) =>
|
||||
sendViewModel.displayFeeRate(priority, sliderValue.round()),
|
||||
onSliderChanged: (double newValue) =>
|
||||
setNewFee(value: newValue, priority: transactionPriority!),
|
||||
onItemSelected: (dynamic item) {
|
||||
transactionPriority = item as TransactionPriority;
|
||||
return setNewFee(priority: transactionPriority!);
|
||||
}));
|
||||
|
||||
if (transactionInfo.inputAddresses != null) {
|
||||
RBFListItems.add(StandardExpandableListItem(
|
||||
title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!));
|
||||
}
|
||||
|
||||
if (transactionInfo.outputAddresses != null) {
|
||||
RBFListItems.add(StandardExpandableListItem(
|
||||
title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _checkForRBF() async {
|
||||
if (wallet.type == WalletType.bitcoin &&
|
||||
transactionInfo.direction == TransactionDirection.outgoing) {
|
||||
if (await bitcoin!.canReplaceByFee(wallet, transactionInfo.id)) {
|
||||
_canReplaceByFee = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String setNewFee({double? value, required TransactionPriority priority}) {
|
||||
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
|
||||
? bitcoin!.getFeeAmountWithFeeRate(
|
||||
wallet,
|
||||
value.round(),
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1)
|
||||
: bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
|
||||
}
|
||||
|
||||
void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo.id, newFee);
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
}
|
||||
|
|
|
@ -213,10 +213,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
return S.current.addresses;
|
||||
}
|
||||
|
||||
if (isAutoGenerateSubaddressEnabled) {
|
||||
return hasAccounts ? S.current.accounts : S.current.account;
|
||||
}
|
||||
|
||||
return hasAccounts ? S.current.accounts_subaddresses : S.current.addresses;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
@ -103,7 +104,15 @@ abstract class WalletKeysViewModelBase with Store {
|
|||
if (_appStore.wallet!.type == WalletType.bitcoin ||
|
||||
_appStore.wallet!.type == WalletType.litecoin ||
|
||||
_appStore.wallet!.type == WalletType.bitcoinCash) {
|
||||
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
|
||||
|
||||
items.addAll([
|
||||
// if (keys['wif'] != null)
|
||||
// StandartListItem(title: "WIF", value: keys['wif']!),
|
||||
// if (keys['privateKey'] != null)
|
||||
// StandartListItem(title: S.current.private_key, value: keys['privateKey']!),
|
||||
// if (keys['publicKey'] != null)
|
||||
// StandartListItem(title: S.current.public_key, value: keys['publicKey']!),
|
||||
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ dependencies:
|
|||
# ref: main
|
||||
socks5_proxy: ^1.0.4
|
||||
flutter_svg: ^2.0.9
|
||||
polyseed: ^0.0.2
|
||||
polyseed: ^0.0.4
|
||||
nostr_tools: ^1.0.9
|
||||
solana: ^0.30.1
|
||||
bitcoin_base:
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"auth_store_incorrect_password": "PIN خطأ",
|
||||
"authenticated": "تم المصادقة",
|
||||
"authentication": "المصادقة",
|
||||
"auto_generate_addresses": "تلقائي توليد العناوين",
|
||||
"auto_generate_subaddresses": "تلقائي توليد subddresses",
|
||||
"automatic": "تلقائي",
|
||||
"available_balance": "الرصيد المتوفر",
|
||||
|
@ -79,6 +80,7 @@
|
|||
"bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
|
||||
"Blocks_remaining": "بلوك متبقي ${status}",
|
||||
"bright_theme": "مشرق",
|
||||
"bump_fee": "رسوم عثرة",
|
||||
"buy": "اشتري",
|
||||
"buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .",
|
||||
"buy_bitcoin": "شراء Bitcoin",
|
||||
|
@ -133,6 +135,8 @@
|
|||
"confirm": "تأكيد",
|
||||
"confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟",
|
||||
"confirm_delete_wallet": "سيؤدي هذا الإجراء إلى حذف هذه المحفظة. هل ترغب في الاستمرار؟",
|
||||
"confirm_fee_deduction": "تأكيد خصم الرسوم",
|
||||
"confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟",
|
||||
"confirm_sending": "تأكيد الإرسال",
|
||||
"confirmations": "التأكيدات",
|
||||
"confirmed": "رصيد مؤكد",
|
||||
|
@ -172,6 +176,7 @@
|
|||
"debit_card": "بطاقة ائتمان",
|
||||
"debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.",
|
||||
"decimal_places_error": "عدد كبير جدًا من المنازل العشرية",
|
||||
"decimals_cannot_be_zero": "الرمز العشري لا يمكن أن يكون الصفر.",
|
||||
"default_buy_provider": "مزود شراء الافتراضي",
|
||||
"default_sell_provider": "ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
|
||||
"delete": "حذف",
|
||||
|
@ -212,6 +217,7 @@
|
|||
"edit_token": "تحرير الرمز المميز",
|
||||
"electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل",
|
||||
"email_address": "عنوان البريد الالكترونى",
|
||||
"enable_replace_by_fee": "تمكين الاستبدال",
|
||||
"enabled": "ممكنة",
|
||||
"enter_amount": "أدخل المبلغ",
|
||||
"enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا",
|
||||
|
@ -249,6 +255,7 @@
|
|||
"errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ",
|
||||
"errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ",
|
||||
"estimated": "مُقدَّر",
|
||||
"estimated_new_fee": "رسوم جديدة مقدرة",
|
||||
"etherscan_history": "Etherscan تاريخ",
|
||||
"event": "ﺙﺪﺣ",
|
||||
"events": "ﺙﺍﺪﺣﻷﺍ",
|
||||
|
@ -315,6 +322,7 @@
|
|||
"in_store": "في المتجر",
|
||||
"incoming": "الواردة",
|
||||
"incorrect_seed": "النص الذي تم إدخاله غير صالح.",
|
||||
"inputs": "المدخلات",
|
||||
"introducing_cake_pay": "نقدم لكم Cake Pay!",
|
||||
"invalid_input": "مدخل غير صالح",
|
||||
"invalid_password": "رمز مرور خاطئ",
|
||||
|
@ -351,6 +359,8 @@
|
|||
"moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "المزيد من الخيارات",
|
||||
"name": "ﻢﺳﺍ",
|
||||
"nano_current_rep": "الممثل الحالي",
|
||||
"nano_pick_new_rep": "اختر ممثلًا جديدًا",
|
||||
"narrow": "ضيق",
|
||||
"new_first_wallet_text": "حافظ بسهولة على أمان العملة المشفرة",
|
||||
"new_node_testing": "تجربة العقدة الجديدة",
|
||||
|
@ -383,6 +393,7 @@
|
|||
"offer_expires_in": "ينتهي العرض في:",
|
||||
"offline": "غير متصل على الانترنت",
|
||||
"ok": "حسناً",
|
||||
"old_fee": "الرسوم القديمة",
|
||||
"onion_link": "رابط البصل",
|
||||
"online": "متصل",
|
||||
"onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.",
|
||||
|
@ -399,6 +410,7 @@
|
|||
"outdated_electrum_wallet_description": "محافظ Bitcoin الجديدة التي تم إنشاؤها في Cake الآن سييد مكونة من 24 كلمة. من الضروري أن تقوم بإنشاء محفظة Bitcoin جديدة وتحويل جميع أموالك إلى المحفظة الجديدة المكونة من 24 كلمة ، والتوقف عن استخدام محافظ سييد مكونة من 12 كلمة. يرجى القيام بذلك على الفور لتأمين أموالك.",
|
||||
"outdated_electrum_wallet_receive_warning": "إذا كانت هذه المحفظة تحتوي على سييد مكونة من 12 كلمة وتم إنشاؤها في Cake ، فلا تقم بإيداع Bitcoin في هذه المحفظة. قد يتم فقد أي BTC تم تحويله إلى هذه المحفظة. قم بإنشاء محفظة جديدة مكونة من 24 كلمة (انقر فوق القائمة في الجزء العلوي الأيمن ، وحدد محافظ ، واختر إنشاء محفظة جديدة ، ثم حدد Bitcoin) وقم على الفور بنقل BTC الخاص بك هناك. محافظ BTC الجديدة (24 كلمة) من Cake آمنة",
|
||||
"outgoing": "الصادره",
|
||||
"outputs": "المخرجات",
|
||||
"overwrite_amount": "تغير المبلغ",
|
||||
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
|
||||
"password": "كلمة المرور",
|
||||
|
@ -457,6 +469,8 @@
|
|||
"remove_node": "إزالة العقدة",
|
||||
"remove_node_message": "هل أنت متأكد أنك تريد إزالة العقدة المحددة؟",
|
||||
"rename": "إعادة تسمية",
|
||||
"rep_warning": "تحذير تمثيلي",
|
||||
"rep_warning_sub": "لا يبدو أن ممثلك في وضع جيد. اضغط هنا لاختيار واحدة جديدة",
|
||||
"repeat_wallet_password": "كرر كلمة مرور المحفظة",
|
||||
"repeated_password_is_incorrect": "كلمة المرور المتكررة غير صحيحة. يرجى تكرار كلمة مرور المحفظة مرة أخرى.",
|
||||
"require_for_adding_contacts": "تتطلب إضافة جهات اتصال",
|
||||
|
@ -573,6 +587,8 @@
|
|||
"send_your_wallet": "محفظتك",
|
||||
"sending": "يتم الإرسال",
|
||||
"sent": "تم الأرسال",
|
||||
"service_health_disabled": "تم تعطيل نشرة صحة الخدمة",
|
||||
"service_health_disabled_message": "هذه هي صفحة نشرة صحة الخدمة ، يمكنك تمكين هذه الصفحة ضمن الإعدادات -> الخصوصية",
|
||||
"settings": "إعدادات",
|
||||
"settings_all": "الكل",
|
||||
"settings_allow_biometrical_authentication": "السماح بالمصادقة البيومترية",
|
||||
|
@ -737,6 +753,7 @@
|
|||
"unspent_coins_details_title": "تفاصيل العملات الغير المنفقة",
|
||||
"unspent_coins_title": "العملات الغير المنفقة",
|
||||
"unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ",
|
||||
"uptime": "مدة التشغيل",
|
||||
"upto": "حتى ${value}",
|
||||
"use": "التبديل إلى",
|
||||
"use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
|
||||
|
@ -753,6 +770,7 @@
|
|||
"view_key_private": "مفتاح العرض (خاص)",
|
||||
"view_key_public": "مفتاح العرض (عام)",
|
||||
"view_transaction_on": "عرض العملية على",
|
||||
"voting_weight": "وزن التصويت",
|
||||
"waitFewSecondForTxUpdate": "ﺕﻼﻣﺎﻌﻤﻟﺍ ﻞﺠﺳ ﻲﻓ ﺔﻠﻣﺎﻌﻤﻟﺍ ﺲﻜﻌﻨﺗ ﻰﺘﺣ ﻥﺍﻮﺛ ﻊﻀﺒﻟ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ",
|
||||
"wallet_keys": "سييد المحفظة / المفاتيح",
|
||||
"wallet_list_create_new_wallet": "إنشاء محفظة جديدة",
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
"auth_store_incorrect_password": "Грешен PIN",
|
||||
"authenticated": "Удостоверено",
|
||||
"authentication": "Удостоверяване",
|
||||
"auto_generate_addresses": "Автоматично генериране на адреси",
|
||||
"auto_generate_subaddresses": "Автоматично генериране на подадреси",
|
||||
"automatic": "Автоматично",
|
||||
"available_balance": "Наличен баланс",
|
||||
|
@ -79,6 +80,7 @@
|
|||
"bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
|
||||
"Blocks_remaining": "${status} оставащи блока",
|
||||
"bright_theme": "Ярко",
|
||||
"bump_fee": "Такса за бум",
|
||||
"buy": "Купуване",
|
||||
"buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.",
|
||||
"buy_bitcoin": "Купуване на Bitcoin",
|
||||
|
@ -133,6 +135,8 @@
|
|||
"confirm": "Потвърждаване",
|
||||
"confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?",
|
||||
"confirm_delete_wallet": "Този портфейл ще бъде изтрит. Искате ли да продължите?",
|
||||
"confirm_fee_deduction": "Потвърдете приспадането на таксите",
|
||||
"confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?",
|
||||
"confirm_sending": "Потвърждаване на изпращането",
|
||||
"confirmations": "потвърждения",
|
||||
"confirmed": "Потвърден баланс",
|
||||
|
@ -172,6 +176,7 @@
|
|||
"debit_card": "Дебитна карта",
|
||||
"debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.",
|
||||
"decimal_places_error": "Твърде много знаци след десетичната запетая",
|
||||
"decimals_cannot_be_zero": "Десетичната точка не може да бъде нула.",
|
||||
"default_buy_provider": "Доставчик по подразбиране купува",
|
||||
"default_sell_provider": "Доставчик за продажба по подразбиране",
|
||||
"delete": "Изтрий",
|
||||
|
@ -212,6 +217,7 @@
|
|||
"edit_token": "Редактиране на токена",
|
||||
"electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят",
|
||||
"email_address": "Имейл адрес",
|
||||
"enable_replace_by_fee": "Активиране на замяна по забрана",
|
||||
"enabled": "Активирано",
|
||||
"enter_amount": "Въведете сума",
|
||||
"enter_backup_password": "Въведете парола за възстановяване",
|
||||
|
@ -249,6 +255,7 @@
|
|||
"errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни",
|
||||
"errorSigningTransaction": "Възникна грешка при подписване на транзакция",
|
||||
"estimated": "Изчислено",
|
||||
"estimated_new_fee": "Прогнозна нова такса",
|
||||
"etherscan_history": "История на Etherscan",
|
||||
"event": "Събитие",
|
||||
"events": "събития",
|
||||
|
@ -315,6 +322,7 @@
|
|||
"in_store": "In Store",
|
||||
"incoming": "Входящи",
|
||||
"incorrect_seed": "Въведеният текст е невалиден.",
|
||||
"inputs": "Входове",
|
||||
"introducing_cake_pay": "Запознайте се с Cake Pay!",
|
||||
"invalid_input": "Невалиден вход",
|
||||
"invalid_password": "Невалидна парола",
|
||||
|
@ -351,6 +359,8 @@
|
|||
"moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Още настройки",
|
||||
"name": "Име",
|
||||
"nano_current_rep": "Настоящ представител",
|
||||
"nano_pick_new_rep": "Изберете нов представител",
|
||||
"narrow": "Тесен",
|
||||
"new_first_wallet_text": "Лесно пазете криптовалутата си в безопасност",
|
||||
"new_node_testing": "Тестване на нов node",
|
||||
|
@ -383,6 +393,7 @@
|
|||
"offer_expires_in": "Предложението изтича след: ",
|
||||
"offline": "Офлайн",
|
||||
"ok": "Ок",
|
||||
"old_fee": "Стара такса",
|
||||
"onion_link": "Лукова връзка",
|
||||
"online": "Онлайн",
|
||||
"onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.",
|
||||
|
@ -399,6 +410,7 @@
|
|||
"outdated_electrum_wallet_description": "Нови Bitcoin портфейли, създадени в Cake, сега имат seed от 24 думи. Трябва да създадете нов Bitcoin адрес и да прехвърлите всичките си средства в него и веднага да спрете използването на стари портфейли. Моля, напревете това незабавно, за да подсигурите средствата си.",
|
||||
"outdated_electrum_wallet_receive_warning": "Ако този адрес има seed от 12 думи и е създаден чрез Cake, НЕ добавяйте Bitcoin в него. Всякакъв Bitcoin, изпратен на този адрес, може да бъде загубен завинаги. Създайте нов портфейл от 24 думи (натиснете менюто горе, вдясно, изберете Портфейли, изберете Създаване на нов портфейл, след това изберете Bitcoin) и НЕЗАБАВНО преместете своя Bitcoin там. Нови (такива с 24 думи) Bitcoin портфейли от Cake са надеждни",
|
||||
"outgoing": "Изходящи",
|
||||
"outputs": "Изходи",
|
||||
"overwrite_amount": "Промени сума",
|
||||
"pairingInvalidEvent": "Невалидно събитие при сдвояване",
|
||||
"password": "Парола",
|
||||
|
@ -457,6 +469,8 @@
|
|||
"remove_node": "Премахни node",
|
||||
"remove_node_message": "Сигурни ли сте, че искате да премахнете избрания node?",
|
||||
"rename": "Промяна на името",
|
||||
"rep_warning": "Представително предупреждение",
|
||||
"rep_warning_sub": "Вашият представител изглежда не е в добро състояние. Докоснете тук, за да изберете нов",
|
||||
"repeat_wallet_password": "Повторете паролата на портфейла",
|
||||
"repeated_password_is_incorrect": "Многократната парола е неправилна. Моля, повторете отново паролата за портфейла.",
|
||||
"require_for_adding_contacts": "Изисква се за добавяне на контакти",
|
||||
|
@ -573,6 +587,8 @@
|
|||
"send_your_wallet": "Вашият портфейл",
|
||||
"sending": "Изпращане",
|
||||
"sent": "Изпратени",
|
||||
"service_health_disabled": "Service Health Bulletin е деактивиран",
|
||||
"service_health_disabled_message": "Това е страницата на Bulletin на Service Health, можете да активирате тази страница в Настройки -> Поверителност",
|
||||
"settings": "Настройки",
|
||||
"settings_all": "Всичко",
|
||||
"settings_allow_biometrical_authentication": "Позволяване на биометрично удостоверяване.",
|
||||
|
@ -737,6 +753,7 @@
|
|||
"unspent_coins_details_title": "Подробности за неизползваните монети",
|
||||
"unspent_coins_title": "Неизползвани монети",
|
||||
"unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.",
|
||||
"uptime": "Време за работа",
|
||||
"upto": "до ${value}",
|
||||
"use": "Смяна на ",
|
||||
"use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
|
||||
|
@ -753,6 +770,7 @@
|
|||
"view_key_private": "View key (таен)",
|
||||
"view_key_public": "View key (публичен)",
|
||||
"view_transaction_on": "Вижте транзакция на ",
|
||||
"voting_weight": "Тегло на гласуване",
|
||||
"waitFewSecondForTxUpdate": "Моля, изчакайте няколко секунди, докато транзакцията се отрази в историята на транзакциите",
|
||||
"wallet_keys": "Seed/keys на портфейла",
|
||||
"wallet_list_create_new_wallet": "Създаване на нов портфейл",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue