From bea906496a3deb49915fac6f0ca96010c1206233 Mon Sep 17 00:00:00 2001 From: Nostr Buddha Date: Thu, 19 Dec 2024 06:03:24 +0530 Subject: [PATCH] UI: Various improvements and fixes (#128) * Offer card layout issue (https://github.com/bisq-network/bisq-mobile/issues/111#) - Handles lengthy names - Handles range prices for currencies with many digits - Star rating composable * - Settings :: Payment methods 1/2 * - Settings :: Payment methods 2/2 * - Improvements: Amount Selector; BtcSats composable; * - Market list - Search; Moved Gap, Dividers to atoms/layout * - Market list - Filter bottom sheet and filter functionality; CurrencyCard - Handle lengthy currency names (#121); DropDown component * - button.isLoading (only for text buttons) * - strings accessible in all presenters that derive from BasePresenter --- .../ui/helpers/NumberFormatter.android.kt | 2 +- .../drawable/icon_arrow_down.png | Bin 0 -> 410 bytes .../drawable/icon_language_green.png | Bin 0 -> 1428 bytes .../drawable/icon_language_grey.png | Bin 0 -> 1430 bytes .../drawable/icon_language_white.png | Bin 0 -> 1423 bytes .../drawable/icon_search_dimmed.png | Bin 0 -> 2338 bytes .../drawable/icon_search_green.png | Bin 0 -> 1908 bytes .../drawable/icon_search_white.png | Bin 0 -> 2001 bytes .../drawable/icon_star_green.png | Bin 0 -> 545 bytes .../drawable/icon_star_green_hollow.png | Bin 0 -> 734 bytes .../drawable/icon_star_grey.png | Bin 0 -> 545 bytes .../drawable/icon_star_grey_hollow.png | Bin 0 -> 734 bytes .../drawable/icon_star_half_green.png | Bin 0 -> 562 bytes .../drawable/icon_star_half_hollow_green.png | Bin 0 -> 668 bytes .../drawable/icon_star_half_hollow_white.png | Bin 0 -> 663 bytes .../drawable/icon_star_half_white.png | Bin 0 -> 562 bytes .../drawable/icon_star_white.png | Bin 0 -> 545 bytes .../drawable/icon_star_white_hollow.png | Bin 0 -> 728 bytes .../drawable/icon_star_yellow.png | Bin 0 -> 558 bytes .../drawable/icon_star_yellow_hollow.png | Bin 0 -> 733 bytes .../network/bisq/mobile/i18n/CommonStrings.kt | 3 + .../bisq/mobile/i18n/CommonStringsEn.kt | 5 +- .../bisq/mobile/i18n/CommonStringsFr.kt | 8 +- .../network/bisq/mobile/i18n/Strings.kt | 1 + .../network/bisq/mobile/i18n/StringsEn.kt | 1 + .../network/bisq/mobile/i18n/StringsFr.kt | 1 + .../network/bisq/mobile/i18n/UserStrings.kt | 105 +++++++++++ .../network/bisq/mobile/i18n/UserStringsEn.kt | 107 ++++++++++++ .../network/bisq/mobile/i18n/UserStringsFr.kt | 109 ++++++++++++ .../bisq/mobile/presentation/BasePresenter.kt | 10 ++ .../presentation/di/PresentationModule.kt | 8 +- .../bisq/mobile/presentation/ui/App.kt | 5 + ...CurrencyProfileCard.kt => CurrencyCard.kt} | 23 ++- .../ui/components/atoms/BtcSatsText.kt | 66 +++++++ .../ui/components/atoms/Button.kt | 71 ++++++-- .../ui/components/atoms/DropDown.kt | 66 +++++++ .../ui/components/atoms/EditableDropDown.kt | 68 ++++++++ .../ui/components/atoms/SearchField.kt | 60 +++++++ .../ui/components/atoms/SegmentButton.kt | 30 ++++ .../ui/components/atoms/Slider.kt | 6 +- .../ui/components/atoms/StarRating.kt | 29 +++ .../presentation/ui/components/atoms/Text.kt | 93 +++++----- .../ui/components/atoms/TextField.kt | 128 +++++++++----- .../ui/components/atoms/icons/Icons.kt | 24 ++- .../ui/components/atoms/icons/Logo.kt | 8 + .../atoms/{ => layout}/BisqHDivider.kt | 5 +- .../atoms/{ => layout}/BisqVDivider.kt | 8 +- .../ui/components/atoms/{ => layout}/Gap.kt | 27 ++- .../ui/components/molecules/AmountSelector.kt | 130 +++----------- .../ui/components/molecules/BottomSheet.kt | 47 +++++ .../molecules/ConfirmationDialog.kt | 16 +- .../molecules/{BisqDialog.kt => Dialog.kt} | 3 +- .../components/molecules/DirectionToggle.kt | 4 +- .../ui/components/molecules/FiatInputField.kt | 111 ++++++++++++ .../ui/components/molecules/OfferCard.kt | 58 +++--- .../{atoms => molecules}/PaymentMethods.kt | 3 +- .../molecules/RangeAmountSelector.kt | 16 +- .../{RageSlider.kt => RangeSlider.kt} | 6 +- .../UserProfile.kt} | 25 ++- .../organisms/market/MarketFilters.kt | 87 +++++++++ .../settings/AddPaymentAccountCard.kt | 80 +++++++++ .../trades/TradeFlowAccountDetails.kt | 1 + .../organisms/trades/TradeFlowBtcPayment.kt | 2 +- .../organisms/trades/TradeFlowCompleted.kt | 2 +- .../organisms/trades/TradeFlowFiatPayment.kt | 1 + .../organisms/trades/TradeHeader.kt | 15 +- .../presentation/ui/composeModels/model.kt | 2 + .../presentation/ui/helpers/DoubleHelper.kt | 9 + .../ui/helpers/NumberFormatter.kt | 1 + .../presentation/ui/helpers/StringHelper.kt | 5 +- .../presentation/ui/theme/UIConstants.kt | 3 + .../ui/uicases/offers/MarketListPresenter.kt | 85 ++++++++- .../ui/uicases/offers/MarketListScreen.kt | 45 ++++- .../ui/uicases/offers/OffersListScreen.kt | 6 +- .../createOffer/AmountSelectorScreen.kt | 6 +- .../offers/createOffer/BuySellScreen.kt | 1 + .../CreateOfferCurrencySelectorScreen.kt | 6 +- .../PaymentMethodSelectorScreen.kt | 2 +- .../offers/createOffer/ReviewOfferScreen.kt | 4 +- .../createOffer/TradePriceSelectorScreen.kt | 2 +- .../offers/takeOffer/PaymentMethodScreen.kt | 2 +- .../offers/takeOffer/ReviewTradeScreen.kt | 5 +- .../offers/takeOffer/TradeAmountScreen.kt | 18 +- .../settings/PaymentAccountPresenter.kt | 59 +++++++ .../settings/PaymentAccountSettingsScreen.kt | 165 ++++++++++++++++-- .../ui/uicases/trades/TradeFlowScreen.kt | 1 + 86 files changed, 1742 insertions(+), 369 deletions(-) create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_arrow_down.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_language_green.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_language_grey.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_language_white.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_search_dimmed.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_search_green.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_search_white.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_green.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_green_hollow.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey_hollow.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_green.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_hollow_green.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_hollow_white.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_white.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_white.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_white_hollow.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow.png create mode 100644 shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow_hollow.png create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStrings.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsEn.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsFr.kt rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/{CurrencyProfileCard.kt => CurrencyCard.kt} (86%) create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BtcSatsText.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/DropDown.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/EditableDropDown.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SearchField.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SegmentButton.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/StarRating.kt rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/{ => layout}/BisqHDivider.kt (66%) rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/{ => layout}/BisqVDivider.kt (65%) rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/{ => layout}/Gap.kt (56%) create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BottomSheet.kt rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/{BisqDialog.kt => Dialog.kt} (90%) create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/FiatInputField.kt rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/{atoms => molecules}/PaymentMethods.kt (93%) rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/{RageSlider.kt => RangeSlider.kt} (94%) rename shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/{atoms/ProfileRating.kt => molecules/UserProfile.kt} (66%) create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/market/MarketFilters.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/settings/AddPaymentAccountCard.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/DoubleHelper.kt create mode 100644 shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountPresenter.kt diff --git a/shared/presentation/src/androidMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.android.kt b/shared/presentation/src/androidMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.android.kt index ff6fc095..9d58ed1b 100644 --- a/shared/presentation/src/androidMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.android.kt +++ b/shared/presentation/src/androidMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.android.kt @@ -3,6 +3,6 @@ package network.bisq.mobile.presentation.ui.helpers import java.text.DecimalFormat actual val numberFormatter: NumberFormatter = object : NumberFormatter { - private val formatter = DecimalFormat("#.########") + private val formatter = DecimalFormat("0.00000000") override fun satsFormat(value: Double): String = formatter.format(value) } diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_arrow_down.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..769cfd97fe771e161f2d6bdb9b3492926bfef84f GIT binary patch literal 410 zcmV;L0cHM)P)^lA*G~Fc3vA2v$`Qg+qZ#3`O8l6$PJ^<&&yF5tR=J zs#UnER#j2cr`qX*GL+23WX_)4xwEsjZ2ma6h&-|fe->s z(;!XL(f;40omdFLr_%|G#R6qn!g~*642TF-RpD~EpswrDdPb!~agrohE|>lLF!`qq z`6wOhFwb+`Zny92*0$}}E2Xr?m{v+@r|^@cop>ty2cZN!oc733aR2}S07*qoM6N<$ Ef;up-+W-In literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_language_green.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_language_green.png new file mode 100644 index 0000000000000000000000000000000000000000..116b02cfcc78d4dd193fc66cb80230822bcad396 GIT binary patch literal 1428 zcmaJ>eQXnD7(e#K7>-O)kPr2^$=J-U_tk5Ax0PjGS1Ga$O0!Ln*z4Wf^|IYvx!%^U z1_kC=z!-2L1{aK?%tb&L83Vxy6Bj-bU>YHWO~4p~Mje}cj2MOZc3UTZ5H7iUznLUG$e?%z(D<=Mpo_E@K+}= zRF>>m6HqjMa*qgcU7D3!^-Apa4Taqe`6`=A(9O%r6h;@ofS_ z$4rb`J2phM*{d#*Sg|I>J+b=cb& z0)z*IwR&BIR}#ohfT4Vk4-_|qZ1@UYhW8@WIW@6f0jlA4+A(-yl4OaetODx~kQC1N zX$wvU%oOftCavkkX|c`0IZ8Rw!Mf-K z)~$vOK@~wFwhUuWV6BhE@+CSD3{CfHT3wcX1CU(0rl4aY^YSAIm~DQGMX&&zr9^;BHZzS2WI({J94Fc=X2uo}1#AK@JtW?E zox&ZML@2?9hiZ``1mY6S*l>n2GdM^41GoT11&kzFBuc~xNRf!w%|msR*n-6wpH4Og zTuinWP+>pmuyI_2*#{70%4cq8u{Sz!uJWqVL}ulzNb{vwa<8oKx%=MnJM4uNH7EB2 z^nA*O)HW5Fw_WW`ulug;_xyGIhu^4Kd)jBF4c@!jAE~MAAKvl``en@lihk!K^L1^r!3eRnPPGANLA-(#_e~u4Q@O*P_TQ^j*r||rbjPN zYdq0f+`Tt&8hPc`#Z_;L*VcaW;`X#B)`dE_tRW>RrvKftZSZW{&ZIw6Dq^&>=eMJ$ zi@&~jY4ySzqa$ltl3v1+rtW<;{a*f&9h>@Qclvw_FTS>{HZSj7Z^5Zj*#26s(c;n=kF$f@MrHRR3CC$}8?>(HvMoCh~h-;95454uOP z8?wK2T>o}p`1ON__5h1mmE=l(VKFBc78Ikgo@MwZ@}y^aO{u<`$-yR_W7 I-{IT%AN*eZR{#J2 literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_language_grey.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_language_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe459092438e43ac1e489d4e070bc5b8bc58e7f GIT binary patch literal 1430 zcmaJ>eM}o=7{Bs01(}-p2To<@!RB;a@2l7L4mv0lq_RSRhCwnS*Soi+gZ9eZVG9~F z5%!52Y-4e3i_^qJ&1N=1CjtT*GiTHoHnzEtEene}GI3-uT}Y;Hf#UukyX5ZudVbII zdw%cp+%9MF%gKqW6A=VSwinvU;P`CheIx;XmE>y&;qbJUU#XSL9xWs&0I^E48=!W- z;00wskeV9Kfh+`xZS}b-wMxe(UX=ZMA!4Hs`-2dTAX(Yrpdi)(4Rr&rFJQs`>L0>T zpJc%{vkuY`%mcN)!e#}OHy69a<~os+u2RWXvI0;}PvIg-(x{2kdl=RLJfexrKxvX@2#O*|4yS0IWO2UixXR?~t!L4-mfeTdP^ikF}`jw48#plKXh;A&Gq6T)~vO^bT40aa9d zLCq%z(1=IDEjMZw43b^Q!XI=vmK+DvMXjJ}L|6zCl%6E~{zz)`)~Z$p{-?35wd!gL z0-_A4a-$-`EAga7!BD=32Z|U%HhhWVgZCoT+hnoP4+5IqX2IZzUh+viWfEAohoo@E zO&f92W1w(1Loqm@8INFelcLc;M*S>@x7v((1zfJp$g&2C$}iw-6m4c%(#Yi%Sj`L< z#o7a^CImzfjqQW67qNK{#qxOy5HwkF$#Q+P5S+ELCabk_5T#i?i>}}73rKQEeKB%J z3n|-x;(HB91&ZuP=SAjy%MdV_-A1Ee1UO5H0GG@L8W%{9fSWi@G#d?!*&_L|Jei!(A^ zY7DqoYAq0e{iMLgnLBiR06}yg*=<%=_{u=V9{=0q6OOcmnmFEiEQ9O&NbD<8EYN08OMsfq)xcmI&yb#ZXolHyZzS_+!t@nxi=i{lJMapU5TG{ z?>Ms7;;MLaMS7Zd``yFN;cs(~ri`t+URCTG-(mi4pl7sovSC|%x|R}i`Mpfs+TGu^ zqV!f$bzfZ3>-%pY{ioRc%U3#rKgXOn*&DZ|^YapVD6x-i9~*hza^PP44;y!ePImnL z%wwOt!Dhx)4wrlH9cwzfYd@N7m^qO7)04fS_usL9Kh!q9_u`F|nhoL6k&e?J{GM7e z`Qg`BU!u;WU7K~~#ACrzyW4ILD@Er!T4PoxRCILjIejQsC>WTiF50Jir9GqL%%Jm+ z?ms7HKi-b0y**nHHTBl*`zy;z+p^Q!@rIn~-yBIM4=4?9y->BL=g#OaQ$JR$kG+%@ z(wTOylo}`Xx~c1_NwZI_C0$>!R|}h)C-!zaFEkz>>t?5k$93vA`_A{Y4GE?eV&=K3 zxqpk^jjhBEdM{T0^QHUMHGS*4b+x(oM{|ZR^$pJ4oPJ>#IlE&k*)ZEV75P)z^NVet I=GL_Q2P78(9RL6T literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_language_white.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_language_white.png new file mode 100644 index 0000000000000000000000000000000000000000..12c7348431a60c701e0e6923ee5d773badbd9d4b GIT binary patch literal 1423 zcmaJ>eNYr-7~c!DM6uE|Kc{WQhSdGOJ#Kfn?BEW)h698PE~$pS-FIb^yS;Yzy5nYY z9Hq%DGU*dR3QBM$6Db?0rpb^@Mu%W3KhTC|a7fHjD<`L6viE=^|6tsi-S_MHJhqg^Z@wUDK|)N}5V2qwq7fu}Z7eKGn}LZ2z$P{1z^;7p6^5#^ z11saalsB9UD%JII9hAfid{TU~#LL*)N6_q;038H@DWb7pRmc!x4s6`70OyHq5<|yL z%*_sLf|TD|gyw2GKzV}3C5mFuES^wU&I%MMi&}#+6vL7MFb!vC5@Mkst_}9+v}yCxooZ0-Yst0fnM*Ho(|$ zO0m*-fTdX+Fsvfl0+eL4Qb|A4;d8Qb+_ar@+BnWi)9yScW2c>*+f6xLypwfuNvtPi zm|{o*$=E84J&EOSixqNpAex%))3mB&Arw_=re;)XVU*zr4$Ul6L$Ve%9!}iRM9MCp ztIq*BPuGIzxX6M!4FRh?V6%xffOE72aM^BUaFJ3(Jd5WgyUohl6-mS<@$xO=P1Gse zfk}juTm+~VB|#uAGprqFX)BBKOhCa!C@NrO*(TExMnZ~YqHb=fqvRGW&ct-8G2mjV zwLl2=lMWka!N%<05X92#aXEdl!IPx}!R^$2HTzSjJL=yvQ$DxsMf*}S=hqbUE8C8w zj?V0z*}U|S^TtRUH}Hh;IB^`vU&t*FRhy%Q)kCzb18L*5i$Zd+;-h)s(ui6D`LpymKw33m@WajPI|yYx_Sr z{A<;xd}r$~qy9&2bGPq(p=Rms@`BupY46NB)c6c>#9uQ*3cS7Iz?!no1`TPqkf2$qzTKTu2@LdB)A5id9cvZIBP_*m`NzSOe8ldVIJ}Dqr0(uy)RB`oRbM znGrK06PDio^Tz$(x(^=SaPFgP`JxsdjJ4=*`pQm9vBGND6Y#@A2WI9cR(RFVf>Ka2?F4*!CaPPz6^2 literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_search_dimmed.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_search_dimmed.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1bd40cb8860352c5fab16816a5037068bfc4ae GIT binary patch literal 2338 zcmbVOYitx%6kbplC{!pE1T0|~3PELd9{ZeWx6Qsv7g%UZ6Dl>r>^#~{cXy^U)9&^W zFa@*^7urIg5J8{`AP}AwO8`-zJd9`rqZkZtEdCJMCL$6E-q~)~LI4Syot-=Pe&;*i zIp?0aTbDCuMqRM%S(Bc|@aYhN5jctyD2^exks>hgg<>BV_>)+k%5!GLyn!bx zT&yS}g`%NQNEg!U1gQwcNs>e{0wo9pAP~7CsL){~C{ONVaB?yu`9#Gh1R<4?_6lW+ z6$V;+g9wOm+Mpau6i68wrbQIjVQNrOAj`yYVwvQRI%gS_^K$_%sK@||$FX9upa^oY z@C@qs@?#D_ay_27kEd!01mZ4a#Z?Z%h$ZByXgR+^6mAaq7(nulh5K{bfC`k8P}TH&lPs2hRc1VZ5Xn1RA@%0Rq>nJEnG zg?a?m$5+ImdITd8oXp1w%4njD#>b#w+px4k|1X$jC|-~PGzi%jpo=(E3>LvqJdu<` z@Cy=f4AklS&$}It97*7Pey||t&9Fmmm&1gUCKE#F@TgpmhjIsHg$^>D+i8VCdUQS? zOOY7Mm>2_wa14zj27;v#(qg6&1BS6AW;XF=p6|1F3QU>03Ht0=U~i-?dYk}x0+$gn z&@6$F2Cosp8QyDP8N!PZOrQN+$p_ky_P;a_XqNv{(Z^x~5y_y+a;%l?bWv>N_hqPI zsxk?Sm++by5;5Q$55mN7#DeSf2x~SQc%I=&-hic}YTp2(-j?iVZL~W{!ss&Tjd~M~ zy9kRV%VEPXgUz00aoR{rKg%dbuLSh@TBChHW&2d^6)~UlxYD?HD7y72no(QY3Nz|n z=2$p3?R!csU#u3rw4q`SkUnRJ_rhd>S3T3Xu2bpHBY=uTD9opb4b-aE3P>T>6yM~}$P ziYxiLwfc=MudbWDHC#=-PNQp*ADWhJvwW0x>ttbJp=TQY<>-~ARQ;n0=vgqjU^>hncdXbw)#F*^N=jI*0`J(u1dcD3wgQsKcL$?e*@(nj;`h9i4tE_F3! zrjAQrd1QA&H?^edQR>;N!J9licYaN4OWpg|K3HQobA8;_>j``$w;EY7xJ1mWUE8cZ zVS${sR&nN=SxHwSb0#&(O*1x$ZyY^oyLNj|(t~wLBiqJYShjduY2weh3pZW(@I;5U zWA}!OsW;CPpVZEuxY)Vs_`Rbi{t172yraNV^;tsC?Ni!Gg_T=G>u*`zi_=G(|9r^s zhKexSlheZ-Do#w zG8Cf|#z0L#qSJ_j1%)7JScHUy!3qN-7$6A4DMUarGKLUP{`{`%8Zh|B<-Ys=p6_{o zzW05v!BI3TF=1SSMx#lzSuG`?AFH06DW=$cqT<);Dw?O4CG0yn<}x)j7kGDBkWZa zkwVd6FsKXab%NwUagroaj6ewj0SH8{2`F?33CL-K3>HpiBwkc_Apoh2v`eT~j4;r3 zzzDw>qYcPWM*)|iAzDOn9i|!;0kTXCCss?oh;WucIUnce0*VZs1dG>#vFP33tlMgYMIi}c6`lL^Rc6oh@4g3i%0ic-`#WQjhwXR zHP5wlFKy1&;kIk+;?W72JZ9Ce$WA?Z`iI7y7mtvQORn6z{l(6ceY>;ll9+T+Mid~S(QGMsk=ZY z>JHqy()Q-O4sFKIDO;+B6;wTW_EH$Fv!tHct`&~eA8s9=fW>XbOs8&++;eb4nbXsI zb4bDV;FR|H86$?Zje2eJkx|w0o}q8yQ%4*f$1JWekyJx*Yj{g~`;M`F>-dpD!b`WN5+!XL-r-7By|_GuyOLEO>tQdf`CQamx(*mqjOj z+jXSq$DVlp^y&{ztxlP~KmPdemd}#I=T^y8zNC}+O)v5@%GN$zw9>I|Ov~xoe&e`J zyZXvLES!H^H2glNH&vlq91FhL*zYWCy0Ao?+q%i#mgT*Z+?%{~DBsw*%DZso^0p1l z&W}@cuF@@A>ld{-60n88h~``0Prlso{@Rz4?p92g*bsNnx;5cO;>#y!)-#Zch~k<+dJ-V>&9>+ zGr(Zc5l|zT0AW!VM3k2aOu^@1kw*e5F^bGFP+|rMVn}%CcYTQjQE7U;@4owe|KI=j ze|2SL#q8{{kBo&NDBE4`tOEBu^%`?O_#KQ{uY%hHV)=721U)=my)xWYk6nbItX|$b zPnqYLLoq@`OS6KX(>6y$fQF!wvSyKH8aM^^b3s08MK1L1M_`_{A~gmN?hzeah%a9( zan*|}yv*VT#=;_HrEp0z1q?(ug@&6W;iybCTamb33ar&_41wbirNN5WR6=;3rxJDu z5(k^K1j^tz4x21mvxPP4Xq4Nz6iSxGkVWJN-X zB2pL@i^a4tomP;77-6wkFr36l5(NlUZiy;%Ga8i(QWl(?%t*Yb@In+;Ez*9WQL!Sx z*Mx|Om^K@glYs&u!Plh?GO(Xho2` zf)Gxnsxsv=Op;my{Dg;Q_^2wP@RlQ-lU6t@0`f+p1c{P_7uQobLFvgN+(hAc0_qW1 zKG2ee>QLN*5*9B(Ql#;AC@342R_K2PvkVmwqzDZp^AS48VPZ6h!0A9z4k0W^fEakE zyLI2~a8yb{fDeNga@A}*>~=YfgvDq?Ni7j~*W;nwQCXp*4Ci)Q5fC0N&$AS1_LF`S zV?p%<7eMtSPM~H&r$bqjNgoI>0ZTxikPFT`1*TCgg4B8Tf1aN!@t_Uq@IBiB&GJ_$ zQdVq0kqojdCtJx*2a~HXU#iMf-89&p7SiA{>I^y~LAXe>d6vV5<9eHYmf2~um`gCV zXTSkCu}T=h?#>pkWU8E3*;|sF)!d>qI~*@wie}WNv?7dJjU0<4xA}X{M#5|?!~vss zsnG;X76M9)mN;7wWbQw70hS6uLJ4-e$atV`88#_6m|*HMJv_lfdi-)xFmoj^T+f`y zy9q%VN8L`Fw|wR9Z>$Xyo}SSE>v&h>H6Jq0{^Ycdj@sT3eXMiYryV&(#Zz2G#Ut5c znv89k+r5`EHp98LDdTP9Gz)TGx|*9+uxdtG*GH#c-dWtY*0W?#9Q>j6NPFP?o}Z3D zL+uNnAGqps)^~R9>8yPJf`-V~94OLM?&^8{>tng|hYHW$c&9G!-0IWsO&mSrVsREW zNH+B>zmnIl`Tn47gRgGl$E8c$w&Qstzq6siNbMgvZ|u!54z<;mbfMNYLF4j`h3A); zW)`5epKn+-pzC|Ze~RvXP&AY zQb?mYx@Kx&=GJH59Dy@GoZHsAacTa*er;w+dE3_R;VYpRuC4pv+g;xskhs0=%broD zf3feAvpdL>tBXV3hC2JJ!z(V1`_g=JWK&ktkJTNAe8KMF$9Jzkm3!pMeWNNu`8TrW zEHw5_JG{K+09}7#+nI{hJ8oX;%_-@w*<(8N+vd!l!`Mt;&Lm>R)McX0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwCNmP;r^Q5eUK@rn=&WkZsZ zB2qFTlo$;WM#>9)87RD)X3=B79-ZAaV^ z=YR=mKWJtiGvYo_A?;U!9yi1x#aP5?aRC@tjE{jlC&fdcL@{0t{6-T+#4H%$T^Dhz zn8&@6tWaaFUGUEBXYd2Q!3)RUz$e%PYe{jGlP^(ZCi01hGR1Lm6Z2qQe$@%E3 zZG(2z9c#oLui3G>j<2{h^oDrCYdZ=^Bd9VU7Ok@04spQ5Hx4){PBAy))ITARu|)c z^rpu78$2gjZU^}{YFO-JYZM)&8{Fzh#2e5B7MLF`>H4uMYFL3T4K5|o{uaRrs7rOd jmUzozgSa5d_!VFP4eCojpH<+|00000NkvXXu0mjfxpMAf literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_green_hollow.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_green_hollow.png new file mode 100644 index 0000000000000000000000000000000000000000..06bc33bc552d5f5f1ffe2ee3a7c4639eb439a24f GIT binary patch literal 734 zcmV<40wMj0P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!fJsC_RCwCNmrY1iQ546WEX$&@7D7a& zgb-4h2%%*3L6%~oM&t*z2p4UlaNDX{)FKEH6g4o4kST}?Qi+IGil9^!xr!j8pj;I8 zfqgms5Bvr$I=-3rW*QIt=Dl~{z4w3LdFP(_ZdNa{{-^Lku|kaCW^?y*v07C6&RnvSqLx)Jqis3& zGZ8ae$}#NB5hjb>7C&g46o17Z@s+mkWV$<|Gb-H9jcVB#CzSV@yhf&6VrKBXOCiwY_^q zJQQmkcPA}u$)JoVw^H~*>?I-8h&tR`!Mz;bak*HTjBq8HDvMNS6W`;#+tb|_Tg3ww z*-d_5M5YQXT4MADXZKPX7cL0Eu*k6C%mb Qng9R*07*qoM6N<$g1o>;!2kdN literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..1afd3272624929a03782b6de97876afb5ce4c3ba GIT binary patch literal 545 zcmV++0^a?JP)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwCNmP;r^Q5eUK@rn=&WkZsZ zB2qFTlo$3y5VIid`vXa%5ENo;a3$kE#Ugg#JpZZUoICJN| zef6t*&%O8j?&CY(cYT_sW&KBF+qkwFH2jA+0-|1veIN{`ycTzWYETQ>yc9>I?WkAc z955m6Pg-F&1@OTmZ%u<6|JtP4N&YQH+;^pxHzbF$YF?*F_vF z=5fCyE7X~57rb%%34DVu@XWDS@B#L~T1p(_Jx;prVBC^Sd8`1JDExf1Q)dN@ZCrzcNwmXV?N0il?7tBQW&3^~8Ru)y4TA zy{U8l2G2>A+d=+~nidDx8bwFx1~�@fvgijrq}%sUNGNh85_-cb%*6W&&00000NkvXXu0mjf>pt*; literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey_hollow.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_grey_hollow.png new file mode 100644 index 0000000000000000000000000000000000000000..c471eac02299283cc7282ca0ef8fc181113d78c2 GIT binary patch literal 734 zcmV<40wMj0P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!fJsC_RCwCNmrY1iQ546WEX|_Q7D0rS zgb!2|75aa7Z+Yrfdsu6RX4;u_c?r&CYpCwuCJp4>{L|gUk~|c#l{{G`|tc zMP)XG8#&$-y+PgWS9rcS$ayc~W(^|me+usti^U*rHg`W4OGSuReM7sOsMM%xzxX*I4i3Ru{PGvPbtDRBCg5OCx485Px{4XuCP@nf-B42VK;O;m}` zFyY~e38!DJxQZA1ne$1q;QGM6>?De>h#t<9nZ>|$aS69y6uZR7RHJ&5fICXS91~A* z;Y?Oa9~<+Hw0c_n46>bWh^?%+TpaP}Yl0iad&Wf5-N!H48fN!A`o-_r6TZqs0x1(K ziR59YeZ5%cTw7B4M5$bx3E?`9_t^?I<_tnZ;uIeSw!a@t86_5w0Oq<&o-a;@iA;d%8PfgSg8g zyUFkK$W(!a>zt1}OZLYpfn!de_|u=D5QI{3x0i6PQ|kJuKs_G)yXiB)b*-}qU&0lX zMyn`0#oH7D(Q5P#aSqp)C#%Patb`2o%;(2~mA?pEt8}LEq1pQH^jClZ0H8F56TSh| QkpKVy07*qoM6N<$g5GdZ4gdfE literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_green.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_half_green.png new file mode 100644 index 0000000000000000000000000000000000000000..acd793056620906f0a085c65f194a0e04be05cc6 GIT binary patch literal 562 zcmV-20?qx2P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)Ja4^RCwBA{Qv(y!$1HQ5D~uKlN`MU zT9+(-0qJX`YOJrXXJDeh(UCwLMXqLYEoKA-6c9(y+hS`VmIq>GAU30?#gRztD0*28 zN{}H){4jFNA;)4DAQnU7O9HV2JuD8#A{Iq!i#dQe2#a_i5VO(JVjm#p!y+yO#2zG< zTtJWm#QsoMb3?^=q4LZ~mT}=RYZVZGgR(yX@n0bR1H_-9Vqbw6RMM>l;u$cDr$P;H zf?7llV25H55X2%6azqsn&qNMtP)*SW#O^@+ng(^kOCWXw;%=y27;(H8h>d~x1Qjhk z3N>>-p~UqVnlGnP!O}@U>RCZ)KE4di8pHyS53f?A0MP|vIT9=eRh1glu-Kc7IQFKc#s6t(F{li-#8Dvl0&xHk zzrrGK1H}B4SnPqN2;BgU_pLy@5s3AGcng{uR%l5@w#CRn1}f~6p;h2(EWS^NRs=)^ zH$ID*k*YdSv1b6p^YEyj1yutpZatx`QIajTgjS898pR%n?-7v@?*Oqa5H~>6hasW* z7#mQ8mO$r7%u1l@uMvp11F0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!K1oDDRCwCNmrqC(Q5?s2-TYUm=|Kp= zkXVof(+FgjlnSy|D%1$KUGUVQi`2c#S_eINN?6f76hy=-hy{ssh)yel5wfBu2}(ND z9)c~!N*ljF_zDl2-Pw7w?*8Dj^JeC~_kHu5-~0V$-G*VfN@2(3Cp6!yGq%H*^8dkc z!|uRnH0mmIu;?&|=nmEuc8jCHoni}L0H?tj&{<63u+okcOIQN(wsJnGGmnn&70}4` zCO8Ey6+<|rv|sAF+pcgGxTT!W@HhY}j#Bsrs8!A-d8~p45VW1SV8ft~t3JVNHN5Xp z#EzTHk&r9~X2A}xWxzhz13!6f8%U;`15<~>F;4!NLUv4q$3P_!t2gN7{(Hyoz32Qlz_1^FIeK3UrehHz+av1^aA+JR%~J-*bcsMOv33t zUgOrVvIQRH3D*meK-xfamU}*>y#l_oRJImE_*nk_-YS*Cip`1pRbx4@iNNe8*j&&WUi)B!#}>dzYts4bFl$Tx6X2y_}g!TUd3O zB>TH8foYSczsEx0&d1%kggr{BOR@qz&Cx&3K8NYHS6PHD;Q&jcRFsS0QyzgRpF-VW zgswklRgcZAg&F9h&3rB8?-93_H`{o3ss1}12rvM5-)$4ujw5{l00000000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!IY~r8RCwCNmrF<+Q51%a?+0zQ3qb@W zh){&uiXe)L3cf-WtB5fHS1#NX-MfjpNEclxDC(jG5wQh9K@k_FD~nJmMX4(hR9sjW zh4?^CHU0;F;eun5xsxUbzRaDOd+&ee%(>@GY=J-^E*6$3KYqVoXY6aQ*L#iQj@f}8 zk0&nP!lGTktvgs(*an=SJKDmPARiQfvSvWDy#sArbq23J;r#?fEZty^ zdC6kn3%KAlAGiVkz$vet1Ics?VDesgoRg1G$c%~bdyqoJvJCpkA_l>f>T0PJUeE+i zObYLFo;12#uruI%>~jnn!3SzRc*kEsDcI16_Dk@WIKU@*ah-F13l&_4+?V4T;t~Th zV1jNR19jji+^D`{!1X8r6Uqv~caAw@S{;z8tFxVELAlY24NL@Ifdh`2x4MtFxiv!B z3Xk%Hzax=AUV^+w?iDHRAK(v5<%>uNzX@`06ZG@L;1;ZgF~+iRfO~fotbts`-Ij$V z8B`ctmxRy?-qO7dU<+jO9an*8R)m`kQs^SzyFA?zCZ7&I0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)Ja4^RCwBA{Qv(y!$1HQ5b^u>Z*ue+ zw6(SU0@Bw=)mUF&&%i{1qa%Shid@a)TFeN<;XoWgZ;P#gSRRO#f!K_m7Dpnnqv&NZ zC_#oG@x#b5ha8JtfLIKPFA2mB^sqP_i&zw`E#?5?AS~j6K+Hx_yR3f^Fg&PIoW9m5L;k#F&^*; zs%HhI`S>z4YY+=SKDEvG_h6S`iQx z-1sbJMyl#S#hw8W&%>jB7E}$axb=j#MoG5V5?VEaY7~1QzDGntyaU9xK->ULABKeL zV{AYXS^}LTF)M+pzeXV54#cXs^fL}r-w7bV00AgfTR*m(CIA2c07*qoM6N<$f`--M A>Hq)$ literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_white.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_white.png new file mode 100644 index 0000000000000000000000000000000000000000..eefaf4ecd8c8938a382582325e7aa4be349bfca3 GIT binary patch literal 545 zcmV++0^a?JP)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwCNmdz_fQ5eRJ8Ac;yiik)o zq==ME2$3d3SZO9~B%ApQtmIEnQ>-npP!w5dR?5mocA`ic%EDKQb#(+cuog%-wkg0_4JM?p+=utzb}9&iJ`L5rIn_kl*x40_xY zj_ST+ZiU(h?m;zwKY)7h>$JzCI#FS@V91S7&q~^2PKUY#q(FIE`!hY)obq@=A}TBh z!YUre!4xP4rR*!?LGtRv0>E!D*C+Vk_Y3%8p1-o~oe|js8$LEBIQbfdRLrkiRHdX} zEMf(0%Jz5!Y=bJB?}QX{KWVhbC!iag*(iF-Ig{BA*B$dRVT0&87-qrADV^rHCxILV z-@p>sv69E_;26}I6ukxG;L>UyUobVy+E+GFv%&RoQBXa75uyMVu7=b5dB; z;E+K2EZy8+@<32JPX0OVx1Px j-X^eX=Y!s|{R=PvVi9S<*IS?d00000NkvXXu0mjfJR0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!dPzht{cOLcyo)Ea-%%NgHM|nV>2~r#C{AZ{yIU z_J}xy7w`)vS2E&xPz#e_j?V|GQpjAirbSc0jv-uf}fkl;& zdV`J^;k>WhNu?eL9WV^0K!11w9~60KZ%MsUPVY%(llviLVR_=pg>8q`ieQND4b}5% zIWrrqW6t?TPF3s@eAm6t@LPHQMfbi*kt=YdD76)YU9eY<;>>36czC1hS{0M9Ogrnu zZrF#^QRx^jg$pnOK0yoIRRm@~ARjaSgLFO|>Mb&<)uG&P>uQW|!ECq#HE;?V;G@XL zM3i^lTLCBK^tQg)RxX`hDK8Hxieqp>=jF`iz-c%P1K=PmR>4V>PMhGUKG+WHG@k!6 zORvi?6TZUMl1Z2-ozB8s^|($Q>B^er^rhP5(pIj=N)5BtnD>g$3Mwk8OXduip!8{X z?oWql&b1YlcUmf^_Jrd`9j`&8Haq3$KI~8qTYg_JY11Q)TU9u2!hX$K%i9BR1I9XX zr*@oGA^54K@DVIgL0AQAByWSB)o6_8tB827PPW{glf=c6conw6dtHx$<61xh=iT*6 zFRyfJbT)TZ?6<&L$7$e1-=c*e?2g=V5m!sbtjgYIcwVNXw&OgbJhrx@r9h&H>$No6 zin6d9P6Msc3t*2rT;~&UvkJij&z-1kdnam(^iRZLU;TIbE5HCNV8GR@c_t?S0000< KMNUMnLSTYNzE*Gm literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..0b30c15f23769d52a8e30946a67236884131a277 GIT binary patch literal 558 zcmV+}0@3}6P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz&`Cr=RCwBA{Qv(y!$1HQ5OJ|ZfgHUC zK>Q1cuaT-zzvcl069tZr1mY-iHIr*GBM^rJaRj|BwgzH(AXWxqGkRJaiNubgm&Kq2 z8G^(QBgY(aEOr56F(keu5IfMr;&3ctQM9(01BipLhz9~O8!avN0b)KZ;zB^|L2}6j z1UW$L4|O#+RE!rY&x~Xl7ap@#0r58|`x6lV1>!$I{240t6zYni>E>jZ-QDx z4q%635D>&74{}5m5YI#oYEVtl2E^__{F(-J!b>1_1LAI|T^Mn^7l@64_yiR#Jqk5* zKcU3+7@9AqQo+(mKl>BT#N@ig6dg8 zX+FLT%^Ji4kPoj?qX5wbVmT5l233_B)Ueo_j5zkDrN#egX)&k_w!~2&_yTbN5Wm7A zZv({qlvwP6r3l>sjrXlUyb*}?fOrd<8dhjYMYhGrK?W-9lc81MYb?G`hgJkc1vfs6 znUSixqtJHrJUr@WLDj&DTTf_flw^x7p;aTOMzIItdqgC}J3wp;#0}8&VMwSx#s(Cj wCD1t%vl6KKYXsu$K&*;OKjT34od5z10Ir}?TS=?bd;kCd07*qoM6N<$f*$zWr~m)} literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow_hollow.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_star_yellow_hollow.png new file mode 100644 index 0000000000000000000000000000000000000000..c51b7802b73d1825626f40b81d9c17e2f2b85b10 GIT binary patch literal 733 zcmV<30wVp1P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!e@R3^RCwCNmrY1iQ544=Ez_d37D7a& zgb-1g1ff`1k)>Ez5t&kpaN#Brx6vXcauEaxhA9|D$P@$xeGn0?48iD=Tt!q;P%aGn zz&@w{f#1Nz#y9icjN^gdy!Y&vEPT`$mff&Ng=I*Crk*M~axnxH~9jl&4+kEb4 zA!at0W7wG^OcuK;zSA}?{)pe=3vJ)Xbk{_8RJe=D+Y$1zzcGY6#2heN7-O6*;+Qz^ zy4otmDX~M0()O7^D#VpWE(`nMO!$_0a-Duf1l%}%K8o$)G+O_?*9qNoyM zFyY~e38!C;xPTW2nDarr;2L3Hb`izBqL1@e| zd&uvz$y9-bYn_ifOZH8az)`19^y!-^1ffLS?IoP$l)3>bP@hNt9{LP&UE?gmmvA|y z(JD%%c%48XT8-W$PT=~|c=b4z6_J4+`}|n2^4G@JDxPY5Xr}%<{S#mSniPc-Uj{=+ P00000NkvXXu0mjfb}3$S literal 0 HcmV?d00001 diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStrings.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStrings.kt index 243530d9..acb6d4c9 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStrings.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStrings.kt @@ -3,6 +3,7 @@ package network.bisq.mobile.i18n data class CommonStrings( val buttons_back: String, val buttons_next: String, + val buttons_save: String, val buttons_submit: String, val buttons_cancel: String, @@ -14,4 +15,6 @@ data class CommonStrings( val take_offer: String, val currency: String, + + val delete_account: String, ) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsEn.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsEn.kt index 5eb065f0..05357792 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsEn.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsEn.kt @@ -1,11 +1,9 @@ package network.bisq.mobile.i18n -import cafe.adriel.lyricist.LyricistStrings - -// @LyricistStrings(languageTag = Locales.EN, default = true) val EnCommonStrings = CommonStrings( buttons_back = "Back", buttons_next = "Next", + buttons_save = "Save", buttons_submit = "Submit", buttons_cancel = "Cancel", @@ -18,4 +16,5 @@ val EnCommonStrings = CommonStrings( take_offer = "Take offer", currency = "Currency", + delete_account = "Delete account", ) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsFr.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsFr.kt index f101e221..47f891b2 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsFr.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/CommonStringsFr.kt @@ -1,12 +1,10 @@ package network.bisq.mobile.i18n -import cafe.adriel.lyricist.LyricistStrings - -//@LyricistStrings(languageTag = Locales.FR) val FrCommonStrings = CommonStrings( buttons_back = "[FR] Back", buttons_next = "[FR] Next", + buttons_save = "[FR] Save", buttons_submit = "[FR] Submit", buttons_cancel = "[FR] Cancel", @@ -17,5 +15,7 @@ val FrCommonStrings = CommonStrings( offers_list_sell_to = "[FR] Sell to", take_offer = "[FR] Take offer", - currency = "[FR] Currency" + currency = "[FR] Currency", + + delete_account = "[FR] Delete account", ) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt index 17693bd9..7e253138 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt @@ -6,5 +6,6 @@ data class AppStrings( val bisqEasy: BisqEasyStrings, val bisqEasyTradeState: BisqEasyTradeStateStrings, val bisqEasyTradeWizard: BisqEasyTradeWizardStrings, + val user: UserStrings, val common: CommonStrings ) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsEn.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsEn.kt index 62ba50db..5237f106 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsEn.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsEn.kt @@ -9,5 +9,6 @@ val EnAppStrings = AppStrings( bisqEasy = EnBisqEasyStrings, bisqEasyTradeWizard = EnBisqEasyTradeWizardStrings, bisqEasyTradeState = EnBisqEasyTradeStateStrings, + user = EnUserStrings, common = EnCommonStrings ) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsFr.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsFr.kt index 17180ef0..9bdebf12 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsFr.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/StringsFr.kt @@ -9,5 +9,6 @@ val FrAppStrings = AppStrings( bisqEasy = FrBisqEasyStrings, bisqEasyTradeWizard = FrBisqEasyTradeWizardStrings, bisqEasyTradeState = FrBisqEasyTradeStateStrings, + user = FrUserStrings, common = FrCommonStrings ) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStrings.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStrings.kt new file mode 100644 index 00000000..9f07d49c --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStrings.kt @@ -0,0 +1,105 @@ +package network.bisq.mobile.i18n + +data class UserStrings( + val user_userProfile_tooltip: String, + val user_userProfile_tooltip_banned: String, + val user_userProfile_userName_banned: String, + val user_userProfile_livenessState: String, + val user_userProfile_livenessState_ageDisplay: String, + val user_userProfile_version: String, + val user_userProfile_addressByTransport_CLEAR: String, + val user_userProfile_addressByTransport_TOR: String, + val user_userProfile_addressByTransport_I2P: String, + val user_userProfile: String, + val user_password: String, + val user_paymentAccounts: String, + val user_bondedRoles_userProfile_select: String, + val user_bondedRoles_userProfile_select_invalid: String, + val user_userProfile_comboBox_description: String, + val user_userProfile_nymId: String, + val user_userProfile_nymId_tooltip: String, + val user_userProfile_profileId: String, + val user_userProfile_profileId_tooltip: String, + val user_userProfile_profileAge: String, + val user_userProfile_profileAge_tooltip: String, + val user_userProfile_livenessState_description: String, + val user_userProfile_livenessState_tooltip: String, + val user_userProfile_reputation: String, + val user_userProfile_statement: String, + val user_userProfile_statement_prompt: String, + val user_userProfile_statement_tooLong: String, + val user_userProfile_terms: String, + val user_userProfile_terms_prompt: String, + val user_userProfile_terms_tooLong: String, + val user_userProfile_createNewProfile: String, + val user_userProfile_learnMore: String, + val user_userProfile_deleteProfile: String, + val user_userProfile_deleteProfile_popup_warning: String, + val user_userProfile_deleteProfile_popup_warning_yes: String, + val user_userProfile_deleteProfile_cannotDelete: String, + val user_userProfile_popup_noSelectedProfile: String, + val user_userProfile_save_popup_noChangesToBeSaved: String, + val user_userProfile_new_step2_headline: String, + val user_userProfile_new_step2_subTitle: String, + val user_userProfile_new_statement: String, + val user_userProfile_new_statement_prompt: String, + val user_userProfile_new_terms: String, + val user_userProfile_new_terms_prompt: String, + val user_password_headline_setPassword: String, + val user_password_headline_removePassword: String, + val user_password_button_savePassword: String, + val user_password_button_removePassword: String, + val user_password_enterPassword: String, + val user_password_confirmPassword: String, + val user_password_savePassword_success: String, + val user_password_removePassword_success: String, + val user_password_removePassword_failed: String, + val user_paymentAccounts_headline: String, + val user_paymentAccounts_noAccounts_headline: String, + val user_paymentAccounts_noAccounts_info: String, + val user_paymentAccounts_noAccounts_whySetup: String, + val user_paymentAccounts_noAccounts_whySetup_info: String, + val user_paymentAccounts_noAccounts_whySetup_note: String, + val user_paymentAccounts_accountData: String, + val user_paymentAccounts_selectAccount: String, + val user_paymentAccounts_createAccount: String, + val user_paymentAccounts_deleteAccount: String, + val user_paymentAccounts_createAccount_headline: String, + val user_paymentAccounts_createAccount_subtitle: String, + val user_paymentAccounts_createAccount_accountName: String, + val user_paymentAccounts_createAccount_accountName_prompt: String, + val user_paymentAccounts_createAccount_accountData_prompt: String, + val user_paymentAccounts_createAccount_sameName: String, + val user_profileCard_userNickname_banned: String, + val user_profileCard_reputation_totalReputationScore: String, + val user_profileCard_reputation_ranking: String, + val user_profileCard_userActions_sendPrivateMessage: String, + val user_profileCard_userActions_ignore: String, + val user_profileCard_userActions_undoIgnore: String, + val user_profileCard_userActions_report: String, + val user_profileCard_tab_overview: String, + val user_profileCard_tab_details: String, + val user_profileCard_tab_offers: String, + val user_profileCard_tab_reputation: String, + val user_profileCard_overview_statement: String, + val user_profileCard_overview_tradeTerms: String, + val user_profileCard_details_botId: String, + val user_profileCard_details_userId: String, + val user_profileCard_details_transportAddress: String, + val user_profileCard_details_totalReputationScore: String, + val user_profileCard_details_profileAge: String, + val user_profileCard_details_lastUserActivity: String, + val user_profileCard_details_version: String, + val user_profileCard_offers_table_columns_market: String, + val user_profileCard_offers_table_columns_offer: String, + val user_profileCard_offers_table_columns_amount: String, + val user_profileCard_offers_table_columns_price: String, + val user_profileCard_offers_table_columns_paymentMethods: String, + val user_profileCard_offers_table_columns_offerAge: String, + val user_profileCard_offers_table_columns_offerAge_tooltip: String, + val user_profileCard_offers_table_columns_goToOffer_button: String, + val user_profileCard_offers_table_placeholderText: String, + + // Mobile app specific + val user_userProfile_payment_account: String, +) \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsEn.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsEn.kt new file mode 100644 index 00000000..464e48c7 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsEn.kt @@ -0,0 +1,107 @@ +package network.bisq.mobile.i18n + +import cafe.adriel.lyricist.LyricistStrings + +val EnUserStrings = UserStrings( + user_userProfile_tooltip = "Nickname: {0}\nBot ID: {1}\nProfile ID: {2}\n{3}", + user_userProfile_tooltip_banned = "This profile is banned!", + user_userProfile_userName_banned = "[Banned] {0}", + user_userProfile_livenessState = "Last user activity: {0} ago", + user_userProfile_livenessState_ageDisplay = "{0} ago", + user_userProfile_version = "Version: {0}", + user_userProfile_addressByTransport_CLEAR = "Clear net address: {0}", + user_userProfile_addressByTransport_TOR = "Tor address: {0}", + user_userProfile_addressByTransport_I2P = "I2P address: {0}", + user_userProfile = "User profile", + user_password = "Password", + user_paymentAccounts = "Payment accounts", + user_bondedRoles_userProfile_select = "Select user profile", + user_bondedRoles_userProfile_select_invalid = "Please pick a user profile from the list", + user_userProfile_comboBox_description = "User profile", + user_userProfile_nymId = "Bot ID", + user_userProfile_nymId_tooltip = " The 'Bot ID' is generated from the hash of the public key of that\n user profiles identity.\n It is appended to the nickname in case there are multiple user profiles in\n the network with the same nickname to distinct clearly between those profiles.", + user_userProfile_profileId = "Profile ID", + user_userProfile_profileId_tooltip = " The 'Profile ID' is the hash of the public key of that user profiles identity\n encoded as hexadecimal string.", + user_userProfile_profileAge = "Profile age", + user_userProfile_profileAge_tooltip = "The 'Profile age' is the age in days of that user profile.", + user_userProfile_livenessState_description = "Last user activity", + user_userProfile_livenessState_tooltip = "The time passed since the user profile has been republished to the network triggered by user activity like mouse movements.", + user_userProfile_reputation = "Reputation", + user_userProfile_statement = "Statement", + user_userProfile_statement_prompt = "Enter optional statement", + user_userProfile_statement_tooLong = "Statement must not be longer than {0} characters", + user_userProfile_terms = "Trade terms", + user_userProfile_terms_prompt = "Enter optional trade terms", + user_userProfile_terms_tooLong = "Trade terms must not be longer than {0} characters", + user_userProfile_createNewProfile = "Create new profile", + user_userProfile_learnMore = "Why create a new profile?", + user_userProfile_deleteProfile = "Delete profile", + user_userProfile_deleteProfile_popup_warning = "Do you really want to delete {0}? You cannot un-do this operation.", + user_userProfile_deleteProfile_popup_warning_yes = "Yes, delete profile", + user_userProfile_deleteProfile_cannotDelete = "Deleting user profile is not permitted\n\n To delete this profile, first:\n - Delete all messages posted with this profile\n - Close all private channels for this profile\n - Make sure to have at least one more profile", + user_userProfile_popup_noSelectedProfile = "Please pick a user profile from the list", + user_userProfile_save_popup_noChangesToBeSaved = "There are no new changes to be saved", + user_userProfile_new_step2_headline = "Complete your profile", + user_userProfile_new_step2_subTitle = "You can optionally add a personalized statement to your profile and set your trade terms.", + user_userProfile_new_statement = "Statement", + user_userProfile_new_statement_prompt = "Optional add statement", + user_userProfile_new_terms = "Your trade terms", + user_userProfile_new_terms_prompt = "Optional set trade terms", + user_password_headline_setPassword = "Set password protection", + user_password_headline_removePassword = "Remove password protection", + user_password_button_savePassword = "Save password", + user_password_button_removePassword = "Remove password", + user_password_enterPassword = "Enter password (min. 8 characters)", + user_password_confirmPassword = "Confirm password", + user_password_savePassword_success = "Password protection enabled.", + user_password_removePassword_success = "Password protection removed.", + user_password_removePassword_failed = "Invalid password.", + user_paymentAccounts_headline = "Your payment accounts", + user_paymentAccounts_noAccounts_headline = "Your payment accounts", + user_paymentAccounts_noAccounts_info = "You haven't set up any accounts yet.", + user_paymentAccounts_noAccounts_whySetup = "Why is setting up an account useful?", + user_paymentAccounts_noAccounts_whySetup_info = "When you're selling Bitcoin, you need to provide your payment account details to the buyer for receiving the fiat payment. Setting up accounts in advance allows for quick and convenient access to this information during the trade.", + user_paymentAccounts_noAccounts_whySetup_note = "Background information:\n Your account data is exclusively stored locally on your computer and is shared with your trade partner only when you decide to share it.", + user_paymentAccounts_accountData = "Payment account info", + user_paymentAccounts_selectAccount = "Select payment account", + user_paymentAccounts_createAccount = "Create new payment account", + user_paymentAccounts_deleteAccount = "Delete payment account", + user_paymentAccounts_createAccount_headline = "Add new payment account", + user_paymentAccounts_createAccount_subtitle = "The payment account is stored only locally on your computer and only sent to your trade peer if you decide to do so.", + user_paymentAccounts_createAccount_accountName = "Payment account name", + user_paymentAccounts_createAccount_accountName_prompt = "Set a unique name for your payment account", + user_paymentAccounts_createAccount_accountData_prompt = "Enter the payment account info (e.g. bank account data) you want to share with a potential Bitcoin buyer so that they can transfer you the national currency amount.", + user_paymentAccounts_createAccount_sameName = "This account name is already used. Please use a different name.", + user_profileCard_userNickname_banned = "[Banned] {0}", + user_profileCard_reputation_totalReputationScore = "Total Reputation Score", + user_profileCard_reputation_ranking = "Ranking", + user_profileCard_userActions_sendPrivateMessage = "Send private message", + user_profileCard_userActions_ignore = "Ignore", + user_profileCard_userActions_undoIgnore = "Undo ignore", + user_profileCard_userActions_report = "Report to moderator", + user_profileCard_tab_overview = "Overview", + user_profileCard_tab_details = "Details", + user_profileCard_tab_offers = "Offers ({0})", + user_profileCard_tab_reputation = "Reputation", + user_profileCard_overview_statement = "Statement", + user_profileCard_overview_tradeTerms = "Trade terms", + user_profileCard_details_botId = "Bot ID", + user_profileCard_details_userId = "User ID", + user_profileCard_details_transportAddress = "Transport address", + user_profileCard_details_totalReputationScore = "Total reputation score", + user_profileCard_details_profileAge = "Profile age", + user_profileCard_details_lastUserActivity = "Last user activity", + user_profileCard_details_version = "Software version", + user_profileCard_offers_table_columns_market = "Market", + user_profileCard_offers_table_columns_offer = "Offer", + user_profileCard_offers_table_columns_amount = "Amount", + user_profileCard_offers_table_columns_price = "Price", + user_profileCard_offers_table_columns_paymentMethods = "Payment methods", + user_profileCard_offers_table_columns_offerAge = "Age", + user_profileCard_offers_table_columns_offerAge_tooltip = "Creation date:\n{0}", + user_profileCard_offers_table_columns_goToOffer_button = "Go to offer", + user_profileCard_offers_table_placeholderText = "No offers", + + user_userProfile_payment_account = "Payment account", +) + diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsFr.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsFr.kt new file mode 100644 index 00000000..a0204f1d --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/UserStringsFr.kt @@ -0,0 +1,109 @@ +package network.bisq.mobile.i18n + +import cafe.adriel.lyricist.LyricistStrings + +val FrUserStrings = UserStrings( + user_userProfile_tooltip = "[FR] Nickname: {0}\nBot ID: {1}\nProfile ID: {2}\n{3}", +user_userProfile_tooltip_banned = "[FR] This profile is banned!", +user_userProfile_userName_banned = "[FR] [Banned] {0}", +user_userProfile_livenessState = "[FR] Last user activity: {0} ago", +user_userProfile_livenessState_ageDisplay = "[FR] {0} ago", +user_userProfile_version = "[FR] Version: {0}", +user_userProfile_addressByTransport_CLEAR = "[FR] Clear net address: {0}", +user_userProfile_addressByTransport_TOR = "[FR] Tor address: {0}", +user_userProfile_addressByTransport_I2P = "[FR] I2P address: {0}", +user_userProfile = "[FR] User profile", +user_password = "[FR] Password", +user_paymentAccounts = "[FR] Payment accounts", +user_bondedRoles_userProfile_select = "[FR] Select user profile", +user_bondedRoles_userProfile_select_invalid = "[FR] Please pick a user profile from the list", +user_userProfile_comboBox_description = "[FR] User profile", +user_userProfile_nymId = "[FR] Bot ID", +user_userProfile_nymId_tooltip = "[FR] The 'Bot ID' is generated from the hash of the public key of that\n user profiles identity.\n It is appended to the nickname in case there are multiple user profiles in\n the network with the same nickname to distinct clearly between those profiles.", +user_userProfile_profileId = "[FR] Profile ID", +user_userProfile_profileId_tooltip = "[FR] The 'Profile ID' is the hash of the public key of that user profiles identity\n encoded as hexadecimal string.", +user_userProfile_profileAge = "[FR] Profile age", +user_userProfile_profileAge_tooltip = "[FR] The 'Profile age' is the age in days of that user profile.", +user_userProfile_livenessState_description = "[FR] Last user activity", +user_userProfile_livenessState_tooltip = "[FR] The time passed since the user profile has been republished to the network triggered by user activity like mouse movements.", +user_userProfile_reputation = "[FR] Reputation", +user_userProfile_statement = "[FR] Statement", +user_userProfile_statement_prompt = "[FR] Enter optional statement", +user_userProfile_statement_tooLong = "[FR] Statement must not be longer than {0} characters", +user_userProfile_terms = "[FR] Trade terms", +user_userProfile_terms_prompt = "[FR] Enter optional trade terms", +user_userProfile_terms_tooLong = "[FR] Trade terms must not be longer than {0} characters", +user_userProfile_createNewProfile = "[FR] Create new profile", +user_userProfile_learnMore = "[FR] Why create a new profile?", +user_userProfile_deleteProfile = "[FR] Delete profile", +user_userProfile_deleteProfile_popup_warning = "[FR] Do you really want to delete {0}? You cannot un-do this operation.", +user_userProfile_deleteProfile_popup_warning_yes = "[FR] Yes, delete profile", +user_userProfile_deleteProfile_cannotDelete = "[FR] Deleting user profile is not permitted\n\n To delete this profile, first:\n - Delete all messages posted with this profile\n - Close all private channels for this profile\n - Make sure to have at least one more profile", +user_userProfile_popup_noSelectedProfile = "[FR] Please pick a user profile from the list", +user_userProfile_save_popup_noChangesToBeSaved = "[FR] There are no new changes to be saved", +user_userProfile_new_step2_headline = "[FR] Complete your profile", +user_userProfile_new_step2_subTitle = "[FR] You can optionally add a personalized statement to your profile and set your trade terms.", +user_userProfile_new_statement = "[FR] Statement", +user_userProfile_new_statement_prompt = "[FR] Optional add statement", +user_userProfile_new_terms = "[FR] Your trade terms", +user_userProfile_new_terms_prompt = "[FR] Optional set trade terms", +user_password_headline_setPassword = "[FR] Set password protection", +user_password_headline_removePassword = "[FR] Remove password protection", +user_password_button_savePassword = "[FR] Save password", +user_password_button_removePassword = "[FR] Remove password", +user_password_enterPassword = "[FR] Enter password (min. 8 characters)", +user_password_confirmPassword = "[FR] Confirm password", +user_password_savePassword_success = "[FR] Password protection enabled.", +user_password_removePassword_success = "[FR] Password protection removed.", +user_password_removePassword_failed = "[FR] Invalid password.", +user_paymentAccounts_headline = "[FR] Your payment accounts", +user_paymentAccounts_noAccounts_headline = "[FR] Your payment accounts", +user_paymentAccounts_noAccounts_info = "[FR] You haven't set up any accounts yet.", +user_paymentAccounts_noAccounts_whySetup = "[FR] Why is setting up an account useful?", +user_paymentAccounts_noAccounts_whySetup_info = "[FR] When you're selling Bitcoin, you need to provide your payment account details to the buyer for receiving the fiat payment. Setting up accounts in advance allows for quick and convenient access to this information during the trade.", +user_paymentAccounts_noAccounts_whySetup_note = "[FR] Background information:\n Your account data is exclusively stored locally on your computer and is shared with your trade partner only when you decide to share it.", +user_paymentAccounts_accountData = "[FR] Payment account info", +user_paymentAccounts_selectAccount = "[FR] Select payment account", +user_paymentAccounts_createAccount = "[FR] Create new payment account", +user_paymentAccounts_deleteAccount = "[FR] Delete payment account", +user_paymentAccounts_createAccount_headline = "[FR] Add new payment account", +user_paymentAccounts_createAccount_subtitle = "[FR] The payment account is stored only locally on your computer and only sent to your trade peer if you decide to do so.", +user_paymentAccounts_createAccount_accountName = "[FR] Payment account name", +user_paymentAccounts_createAccount_accountName_prompt = "[FR] Set a unique name for your payment account", +user_paymentAccounts_createAccount_accountData_prompt = "[FR] Enter the payment account info (e.g. bank account data) you want to share with a potential Bitcoin buyer so that they can transfer you the national currency amount.", +user_paymentAccounts_createAccount_sameName = "[FR] This account name is already used. Please use a different name.", +user_profileCard_userNickname_banned = "[FR] [Banned] {0}", +user_profileCard_reputation_totalReputationScore = "[FR] Total Reputation Score", +user_profileCard_reputation_ranking = "[FR] Ranking", +user_profileCard_userActions_sendPrivateMessage = "[FR] Send private message", +user_profileCard_userActions_ignore = "[FR] Ignore", +user_profileCard_userActions_undoIgnore = "[FR] Undo ignore", +user_profileCard_userActions_report = "[FR] Report to moderator", +user_profileCard_tab_overview = "[FR] Overview", +user_profileCard_tab_details = "[FR] Details", +user_profileCard_tab_offers = "[FR] Offers ({0})", +user_profileCard_tab_reputation = "[FR] Reputation", +user_profileCard_overview_statement = "[FR] Statement", +user_profileCard_overview_tradeTerms = "[FR] Trade terms", +user_profileCard_details_botId = "[FR] Bot ID", +user_profileCard_details_userId = "[FR] User ID", +user_profileCard_details_transportAddress = "[FR] Transport address", +user_profileCard_details_totalReputationScore = "[FR] Total reputation score", +user_profileCard_details_profileAge = "[FR] Profile age", +user_profileCard_details_lastUserActivity = "[FR] Last user activity", +user_profileCard_details_version = "[FR] Software version", +user_profileCard_offers_table_columns_market = "[FR] Market", +user_profileCard_offers_table_columns_offer = "[FR] Offer", +user_profileCard_offers_table_columns_amount = "[FR] Amount", +user_profileCard_offers_table_columns_price = "[FR] Price", +user_profileCard_offers_table_columns_paymentMethods = "[FR] Payment methods", +user_profileCard_offers_table_columns_offerAge = "[FR] Age", +user_profileCard_offers_table_columns_offerAge_tooltip = "[FR] Creation date:\n{0}", +user_profileCard_offers_table_columns_goToOffer_button = "[FR] Go to offer", +user_profileCard_offers_table_placeholderText = "[FR] No offers", + + user_userProfile_payment_account = "[FR] Payment account", + + +) + diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt index 8b3fab1c..68d7852f 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import network.bisq.mobile.domain.data.BackgroundDispatcher import network.bisq.mobile.domain.data.model.BaseModel +import network.bisq.mobile.i18n.AppStrings import network.bisq.mobile.utils.Logging @@ -225,4 +226,13 @@ abstract class BasePresenter(private val rootPresenter: MainPresenter?): ViewPre } private fun isRoot() = rootPresenter == null + + companion object { + lateinit var strings: AppStrings + } + + fun setStrings(localStrings: AppStrings) { + BasePresenter.strings = localStrings + } + } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt index 41bf1dc7..f8b54a99 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt @@ -1,7 +1,5 @@ package network.bisq.mobile.presentation.di -import androidx.navigation.NavController -import androidx.navigation.NavHostController import network.bisq.mobile.client.ClientMainPresenter import network.bisq.mobile.presentation.MainPresenter import network.bisq.mobile.presentation.ui.AppPresenter @@ -19,9 +17,10 @@ import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.ReviewTradeP import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.TradeAmountPresenter import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.CreateOfferPresenter import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.ICreateOfferPresenter -import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.* +import network.bisq.mobile.presentation.ui.uicases.settings.IPaymentAccountSettingsPresenter import network.bisq.mobile.presentation.ui.uicases.settings.ISettingsPresenter import network.bisq.mobile.presentation.ui.uicases.settings.IUserProfileSettingsPresenter +import network.bisq.mobile.presentation.ui.uicases.settings.PaymentAccountPresenter import network.bisq.mobile.presentation.ui.uicases.settings.SettingsPresenter import network.bisq.mobile.presentation.ui.uicases.settings.UserProfileSettingsPresenter import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfilePresenter @@ -34,7 +33,6 @@ import network.bisq.mobile.presentation.ui.uicases.trades.IMyTrades import network.bisq.mobile.presentation.ui.uicases.trades.ITradeFlowPresenter import network.bisq.mobile.presentation.ui.uicases.trades.MyTradesPresenter import network.bisq.mobile.presentation.ui.uicases.trades.TradeFlowPresenter -import org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.module @@ -102,4 +100,6 @@ val presentationModule = module { single{ TradeFlowPresenter(get(), get()) } bind ITradeFlowPresenter::class single{ CreateOfferPresenter(get(), get()) } bind ICreateOfferPresenter::class + + single{ PaymentAccountPresenter(get(), get()) } bind IPaymentAccountSettingsPresenter::class } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt index 4313c1b8..3d7cf4f3 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt @@ -3,11 +3,13 @@ package network.bisq.mobile.presentation.ui import androidx.compose.runtime.* import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import cafe.adriel.lyricist.LocalStrings import cafe.adriel.lyricist.ProvideStrings import cafe.adriel.lyricist.rememberStrings import org.jetbrains.compose.ui.tooling.preview.Preview import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.i18n.AppStrings import network.bisq.mobile.presentation.ViewPresenter import network.bisq.mobile.presentation.ui.components.SwipeBackIOSNavigationHandler import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle @@ -28,6 +30,8 @@ interface AppPresenter : ViewPresenter { fun toggleContentVisibility() fun isIOS(): Boolean + + fun setStrings(strings: AppStrings) } /** @@ -52,6 +56,7 @@ fun App() { BisqTheme(darkTheme = true) { ProvideStrings(lyricist) { + presenter.setStrings(LocalStrings.current) if (isNavControllerSet) { SwipeBackIOSNavigationHandler(rootNavController) { RootNavGraph(rootNavController) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyCard.kt similarity index 86% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyCard.kt index b54460e4..794119e6 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyCard.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember @@ -21,14 +20,16 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign import network.bisq.mobile.domain.data.model.MarketListItem import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.DynamicImage +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants @Composable -fun CurrencyProfileCard( +fun CurrencyCard( item: MarketListItem, isSelected: Boolean = false, onClick: () -> Unit @@ -63,18 +64,14 @@ fun CurrencyProfileCard( Modifier.background(Color.Transparent) } ) - .padding(horizontal = 4.dp, vertical = (16/LocalDensity.current.density).dp) - // .padding(vertical = 4.dp) + .padding(vertical = BisqUIConstants.ScreenPadding) .clickable( interactionSource = interactionSource, indication = null, onClick = onClick ) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(3.0f)) { // If the image is not available we get an exception here and we cannot use try/catch // Is DynamicImage needed? If so we can pass it as DynamicImage( @@ -83,12 +80,12 @@ fun CurrencyProfileCard( .replace("-", "_")}.png", modifier = Modifier.size(36.dp) ) - - Spacer(modifier = Modifier.width(8.dp)) + BisqGap.HHalf() Column { BisqText.baseRegular( text = item.market.quoteCurrencyName, color = BisqTheme.colors.light1, + singleLine = true ) Spacer(modifier = Modifier.height(0.dp)) BisqText.baseRegular( @@ -97,9 +94,11 @@ fun CurrencyProfileCard( ) } } - BisqText.smallRegular( + BisqText.baseRegular( text = "$numOffers offers", color = BisqTheme.colors.primary, + textAlign = TextAlign.End, + modifier = Modifier.weight(1.0f), ) } } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BtcSatsText.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BtcSatsText.kt new file mode 100644 index 00000000..d6dbde3a --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BtcSatsText.kt @@ -0,0 +1,66 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import network.bisq.mobile.presentation.ui.components.atoms.icons.BtcLogo +import network.bisq.mobile.presentation.ui.helpers.numberFormatter +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun BtcSatsText(sats: Long) { + + val btcValue = sats.toDouble() / 100_000_000 + val formattedValue = formatSatsToDisplay(btcValue, sats) + + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + BtcLogo() + Text( + text = formattedValue, + fontSize = 20.sp, + color = BisqTheme.colors.light1, + ) + } +} + +@Composable +private fun formatSatsToDisplay(btcValue: Double, sats: Long): AnnotatedString { + val btcString = numberFormatter.satsFormat(btcValue) + + return buildAnnotatedString { + val parts = btcString.split(".") + val integerPart = parts[0] + val fractionalPart = parts[1] + + val formattedFractional = fractionalPart.chunked(3).joinToString(" ") + val leadingZeros = formattedFractional.takeWhile { it == '0' || it == ' ' } + val significantDigits = formattedFractional.dropWhile { it == '0' || it == ' ' } + + val prefixColor = if(integerPart.toInt() > 0) BisqTheme.colors.light1 else BisqTheme.colors.grey2 + + withStyle(style = SpanStyle(color = prefixColor)) { + append(integerPart) + append(".") + } + + withStyle(style = SpanStyle(color = prefixColor)) { + append(leadingZeros) + } + + withStyle(style = SpanStyle(color = BisqTheme.colors.light1)) { + append(significantDigits) + append(" sats") + } + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt index 66548de0..5a650444 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt @@ -1,22 +1,32 @@ package network.bisq.mobile.presentation.ui.components.atoms import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonColors +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.theme.BisqTheme +enum class BisqButtonType { + Default, + Outline, + Clear +} + /** * Either pass - * - text for regular button (or) - * - iconOnly for Icon only button. - * If both are given, iconOnly takes precedence + * - iconOnly for Icon only button (or) + * - textComponent for button with custom styled text (or) + * - text for regular button */ @Composable fun BisqButton( @@ -32,20 +42,39 @@ fun BisqButton( modifier: Modifier = Modifier, cornerRadius: Dp = 8.dp, disabled: Boolean = false, - border: BorderStroke? = null + isLoading: Boolean = false, + border: BorderStroke? = null, + type: BisqButtonType = BisqButtonType.Default ) { + val enabled = !disabled && !isLoading + + val finalBackgroundColor = when (type) { + BisqButtonType.Default -> backgroundColor + BisqButtonType.Outline -> Color.Transparent + BisqButtonType.Clear -> Color.Transparent + } + + val finalBorder = when (type) { + BisqButtonType.Default -> border + BisqButtonType.Outline -> BorderStroke(1.dp, BisqTheme.colors.primary) + BisqButtonType.Clear -> null + } + + val finalContentColor = color + Button( onClick = { onClick() }, - contentPadding = if(iconOnly != null) PaddingValues(horizontal = 0.dp, vertical = 0.dp) else padding, + contentPadding = if (iconOnly != null) PaddingValues(horizontal = 0.dp, vertical = 0.dp) else padding, colors = ButtonColors( - containerColor = backgroundColor, - disabledContainerColor = backgroundColor, - contentColor = color, - disabledContentColor = color), + containerColor = finalBackgroundColor, + disabledContainerColor = finalBackgroundColor.copy(alpha = 0.5f), + contentColor = finalContentColor, + disabledContentColor = finalContentColor.copy(alpha = 0.5f), + ), shape = RoundedCornerShape(cornerRadius), - enabled = !disabled, - border = border, + enabled = enabled, + border = finalBorder, modifier = modifier ) { if (iconOnly == null && text == null && textComponent == null) { @@ -55,19 +84,27 @@ fun BisqButton( if (iconOnly != null) { iconOnly() } else if (text != null) { - Row { - if(leftIcon != null) leftIcon() - if(leftIcon != null) Spacer(modifier = Modifier.width(10.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + color = BisqTheme.colors.light1, + strokeWidth = 2.dp + ) + BisqGap.HHalf() + } + if (leftIcon != null) leftIcon() + if (leftIcon != null) Spacer(modifier = Modifier.width(10.dp)) if (textComponent != null) { textComponent() } else { BisqText.baseMedium( text = text, color = BisqTheme.colors.light1, - ) + ) } - if(rightIcon != null) Spacer(modifier = Modifier.width(10.dp)) - if(rightIcon != null) rightIcon() + if (rightIcon != null) Spacer(modifier = Modifier.width(10.dp)) + if (rightIcon != null) rightIcon() } } } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/DropDown.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/DropDown.kt new file mode 100644 index 00000000..703d33a0 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/DropDown.kt @@ -0,0 +1,66 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.text.style.TextAlign +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun BisqDropDown( + label: String = "", + items: List, + value: String, + onValueChanged: (String) -> Unit, + modifier: Modifier = Modifier, + placeholder: String = "Select an item" +) { + var expanded by remember { mutableStateOf(false) } + + Column { + if (label.isNotEmpty()) { + BisqText.baseRegular( + text = label, + color = BisqTheme.colors.light2, + ) + } + + BisqButton( + onClick = { expanded = true }, + modifier = Modifier.fillMaxWidth(), + backgroundColor = BisqTheme.colors.grey5, + textComponent = { + BisqText.baseRegular( + text = value, + color = BisqTheme.colors.light1, + textAlign = TextAlign.Start + ) + }, + ) + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier.wrapContentSize().background(color = BisqTheme.colors.secondary) + ) { + items.forEach { item -> + DropdownMenuItem( + text = { BisqText.baseRegular(text = item) }, + onClick = { + onValueChanged.invoke(item) + expanded = false + }, + ) + } + } + } +} diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/EditableDropDown.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/EditableDropDown.kt new file mode 100644 index 00000000..fd6107b6 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/EditableDropDown.kt @@ -0,0 +1,68 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.icons.ArrowDownIcon +import network.bisq.mobile.presentation.ui.components.molecules.BisqDialog +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun BisqEditableDropDown( + value: String, + onValueChanged: (String) -> Unit, + items: List, + label: String +) { + var showDialog by remember { mutableStateOf(false) } + + BisqTextField( + value = value, + onValueChanged = onValueChanged, + label = label, + rightSuffix = { + Box( + modifier = Modifier + .padding(vertical = 8.dp, horizontal = 16.dp) + .clickable( + onClick = { showDialog = true }, + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) + ) { + ArrowDownIcon() + } + }, + ) + + if (showDialog) { + BisqDialog(onDismissRequest = { showDialog = false }) { + LazyColumn( + modifier = Modifier.padding(16.dp), + ) { + items(items) { item -> + Row( + modifier = Modifier.fillMaxWidth() + .padding(vertical = 8.dp) + .clickable { + onValueChanged(item) + showDialog = false + } + ) { + BisqText.baseBold(text = item) + } + } + } + } + } +} diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SearchField.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SearchField.kt new file mode 100644 index 00000000..d471cc5d --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SearchField.kt @@ -0,0 +1,60 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import network.bisq.mobile.presentation.ui.components.atoms.icons.SearchIcon +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun BisqSearchField( + label: String = "", + value: String, + onValueChanged: (String) -> Unit = {}, + placeholder: String = "", + rightSuffix: (@Composable () -> Unit)? = null, + disabled: Boolean = false, + modifier: Modifier = Modifier) { + + BisqTextField( + label = label, + value = value, + onValueChanged = onValueChanged, + placeholder = placeholder, + leftSuffix = { SearchIcon() }, + rightSuffix = rightSuffix, + isSearch= true, + disabled = disabled, + modifier = modifier, + ) + +} diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SegmentButton.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SegmentButton.kt new file mode 100644 index 00000000..b9dbd3ea --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/SegmentButton.kt @@ -0,0 +1,30 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember + +// TODO: SingleChoiceSegmentedButtonRow: Not available in KMP? +@Composable +fun BisqSegmentButton() { + var selectedIndex by remember { mutableIntStateOf(0) } + val options = listOf("Day", "Month", "Week") + + /* + SingleChoiceSegmentedButtonRow { + options.forEachIndexed { index, label -> + SegmentedButton( + shape = SegmentedButtonDefaults.itemShape( + index = index, + count = options.size + ), + onClick = { selectedIndex = index }, + selected = index == selectedIndex, + label = { Text(label) } + ) + } + } + */ +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Slider.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Slider.kt index 22d731ce..1692a339 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Slider.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Slider.kt @@ -18,10 +18,10 @@ import network.bisq.mobile.presentation.ui.theme.BisqTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun BisqSlider( + value: Float, + onValueChange: (Float) -> Unit, minAmount: Float, maxAmount: Float, - tradeValue: Float, - onValueChange: (Float) -> Unit ) { val colors = SliderColors( @@ -39,7 +39,7 @@ fun BisqSlider( Slider( modifier = Modifier.fillMaxWidth(), - value = tradeValue, + value = value, onValueChange = { onValueChange(it) }, valueRange = minAmount..maxAmount, thumb = { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/StarRating.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/StarRating.kt new file mode 100644 index 00000000..6a31c313 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/StarRating.kt @@ -0,0 +1,29 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.icons.StarEmptyIcon +import network.bisq.mobile.presentation.ui.components.atoms.icons.StarFillIcon +import network.bisq.mobile.presentation.ui.components.atoms.icons.StarHalfFilledIcon + +@Composable +fun StarRating(rating: Double) { + + val fullStars = rating.toInt() + val hasHalfStar = rating - fullStars >= 0.5 + val emptyStars = 5 - fullStars - if (hasHalfStar) 1 else 0 + + Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) { + repeat(fullStars) { + StarFillIcon() + } + if(hasHalfStar) { + StarHalfFilledIcon() + } + repeat(emptyStars) { + StarEmptyIcon() + } + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt index b5ca9957..c81ab083 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.unit.sp import org.jetbrains.compose.resources.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import bisqapps.shared.presentation.generated.resources.Res @@ -46,10 +47,12 @@ object BisqText { fontWeight: FontWeight = FontWeight.REGULAR, textAlign: TextAlign = TextAlign.Start, lineHeight: TextUnit = TextUnit.Unspecified, + maxLines: Int = Int.MAX_VALUE, + overflow: TextOverflow = TextOverflow.Clip, modifier: Modifier = Modifier, ) { - val fontFamily = when(fontWeight) { + val fontFamily = when (fontWeight) { FontWeight.LIGHT -> FontFamily(Font(Res.font.ibm_plex_sans_light)) FontWeight.REGULAR -> FontFamily(Font(Res.font.ibm_plex_sans_regular)) FontWeight.MEDIUM -> FontFamily(Font(Res.font.ibm_plex_sans_medium)) @@ -63,6 +66,8 @@ object BisqText { fontFamily = fontFamily, textAlign = textAlign, lineHeight = lineHeight, + maxLines = maxLines, + overflow = overflow, modifier = modifier, ) } @@ -78,7 +83,7 @@ object BisqText { text = text, fontSize = FontSize.XSMALL, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -95,7 +100,7 @@ object BisqText { text = text, fontSize = FontSize.XSMALL, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -112,7 +117,7 @@ object BisqText { text = text, fontSize = FontSize.XSMALL, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -129,7 +134,7 @@ object BisqText { text = text, fontSize = FontSize.XSMALL, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -147,7 +152,7 @@ object BisqText { text = text, fontSize = FontSize.SMALL, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -164,7 +169,7 @@ object BisqText { text = text, fontSize = FontSize.SMALL, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -181,7 +186,7 @@ object BisqText { text = text, fontSize = FontSize.SMALL, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -198,7 +203,7 @@ object BisqText { text = text, fontSize = FontSize.SMALL, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -216,7 +221,7 @@ object BisqText { text = text, fontSize = FontSize.BASE, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, lineHeight = TextUnit(16.0f, TextUnitType.Sp), modifier = modifier, @@ -228,15 +233,18 @@ object BisqText { text: String, color: Color = BisqTheme.colors.light1, textAlign: TextAlign = TextAlign.Start, + singleLine: Boolean = false, modifier: Modifier = Modifier, ) { styledText( text = text, fontSize = FontSize.BASE, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, lineHeight = TextUnit(16.0f, TextUnitType.Sp), + maxLines = if (singleLine) 1 else Int.MAX_VALUE, + overflow = if (singleLine) TextOverflow.Ellipsis else TextOverflow.Clip, modifier = modifier, ) } @@ -252,7 +260,7 @@ object BisqText { text = text, fontSize = FontSize.BASE, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, lineHeight = TextUnit(16.0f, TextUnitType.Sp), modifier = modifier, @@ -270,7 +278,7 @@ object BisqText { text = text, fontSize = FontSize.BASE, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, lineHeight = TextUnit(16.0f, TextUnitType.Sp), modifier = modifier, @@ -289,7 +297,7 @@ object BisqText { text = text, fontSize = FontSize.LARGE, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -300,14 +308,17 @@ object BisqText { text: String, color: Color = BisqTheme.colors.light1, textAlign: TextAlign = TextAlign.Start, + singleLine: Boolean = false, modifier: Modifier = Modifier, ) { styledText( text = text, fontSize = FontSize.LARGE, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, + maxLines = if (singleLine) 1 else Int.MAX_VALUE, + overflow = if (singleLine) TextOverflow.Ellipsis else TextOverflow.Clip, modifier = modifier, ) } @@ -323,7 +334,7 @@ object BisqText { text = text, fontSize = FontSize.LARGE, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -340,7 +351,7 @@ object BisqText { text = text, fontSize = FontSize.LARGE, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -358,7 +369,7 @@ object BisqText { text = text, fontSize = FontSize.H6, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -375,7 +386,7 @@ object BisqText { text = text, fontSize = FontSize.H6, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -392,7 +403,7 @@ object BisqText { text = text, fontSize = FontSize.H6, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -409,7 +420,7 @@ object BisqText { text = text, fontSize = FontSize.H6, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -427,7 +438,7 @@ object BisqText { text = text, fontSize = FontSize.H5, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -444,7 +455,7 @@ object BisqText { text = text, fontSize = FontSize.H5, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -461,7 +472,7 @@ object BisqText { text = text, fontSize = FontSize.H5, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -478,7 +489,7 @@ object BisqText { text = text, fontSize = FontSize.H5, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -496,7 +507,7 @@ object BisqText { text = text, fontSize = FontSize.H4, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -513,7 +524,7 @@ object BisqText { text = text, fontSize = FontSize.H4, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -530,7 +541,7 @@ object BisqText { text = text, fontSize = FontSize.H4, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -547,7 +558,7 @@ object BisqText { text = text, fontSize = FontSize.H4, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -565,7 +576,7 @@ object BisqText { text = text, fontSize = FontSize.H3, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -582,7 +593,7 @@ object BisqText { text = text, fontSize = FontSize.H3, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -599,7 +610,7 @@ object BisqText { text = text, fontSize = FontSize.H3, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -616,7 +627,7 @@ object BisqText { text = text, fontSize = FontSize.H3, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -634,7 +645,7 @@ object BisqText { text = text, fontSize = FontSize.H2, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -651,7 +662,7 @@ object BisqText { text = text, fontSize = FontSize.H2, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -668,7 +679,7 @@ object BisqText { text = text, fontSize = FontSize.H2, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -685,7 +696,7 @@ object BisqText { text = text, fontSize = FontSize.H2, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -703,7 +714,7 @@ object BisqText { text = text, fontSize = FontSize.H1, fontWeight = FontWeight.LIGHT, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -720,7 +731,7 @@ object BisqText { text = text, fontSize = FontSize.H1, fontWeight = FontWeight.REGULAR, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -737,7 +748,7 @@ object BisqText { text = text, fontSize = FontSize.H1, fontWeight = FontWeight.MEDIUM, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) @@ -754,7 +765,7 @@ object BisqText { text = text, fontSize = FontSize.H1, fontWeight = FontWeight.BOLD, - color=color, + color = color, textAlign = textAlign, modifier = modifier, ) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt index df60f130..9895cfc1 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt @@ -1,42 +1,67 @@ package network.bisq.mobile.presentation.ui.components.atoms import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import network.bisq.mobile.presentation.ui.components.atoms.icons.SearchIcon import network.bisq.mobile.presentation.ui.theme.BisqTheme @Composable fun BisqTextField( label: String, value: String, - onValueChanged: (String) -> Unit, - placeholder: String? = null, + onValueChanged: (String) -> Unit = {}, + placeholder: String = "", labelRightSuffix: (@Composable () -> Unit)? = null, - prefix: (@Composable () -> Unit)? = null, - suffix: (@Composable () -> Unit)? = null, - disabled: Boolean = false, + leftSuffix: (@Composable () -> Unit)? = null, + rightSuffix: (@Composable () -> Unit)? = null, + isSearch: Boolean = false, + helperText: String = "", indicatorColor: Color = BisqTheme.colors.primary, + isTextArea: Boolean = false, + paddingValues: PaddingValues = PaddingValues(all = 12.dp), + disabled: Boolean = false, modifier: Modifier = Modifier, ) { var isFocused by remember { mutableStateOf(false) } + + val focusManager = LocalFocusManager.current + + val imeAction = when { + isSearch -> ImeAction.Search + isTextArea -> ImeAction.Next + else -> ImeAction.Done + } + Column(modifier = modifier) { if (label.isNotEmpty()) { Row( @@ -59,7 +84,7 @@ fun BisqTextField( .clip(shape = RoundedCornerShape(6.dp)) .background(color = BisqTheme.colors.secondary) .drawBehind { - if (isFocused || value.isNotEmpty()) { + if (!isSearch && isFocused) { drawLine( color = indicatorColor, start = Offset(0f, size.height), @@ -69,45 +94,60 @@ fun BisqTextField( } } ) { - TextField( + BasicTextField( value = value, - singleLine = true, - modifier = Modifier.fillMaxWidth().clickable { isFocused = true } + onValueChange = onValueChanged, + modifier = Modifier + .padding(paddingValues) + .fillMaxWidth() .onFocusChanged { focusState -> isFocused = focusState.isFocused }, - textStyle = TextStyle(fontSize = 22.sp), - onValueChange = onValueChanged, - prefix = prefix, - suffix = suffix, - colors = TextFieldDefaults.colors( - focusedTextColor = BisqTheme.colors.light3, - unfocusedTextColor = BisqTheme.colors.secondaryHover, - unfocusedIndicatorColor = BisqTheme.colors.secondary, - focusedIndicatorColor = Color.Transparent, - focusedContainerColor = BisqTheme.colors.secondary, - cursorColor = Color.Blue, - unfocusedContainerColor = BisqTheme.colors.secondary + singleLine = !isTextArea, + maxLines = if (isTextArea) 4 else 1, + textStyle = TextStyle( + color = Color.White, + fontSize = 18.sp, + textDecoration = TextDecoration.None ), - placeholder = { - if (placeholder != null) { - BisqText.h5Regular( - text = placeholder, - color = BisqTheme.colors.secondaryHover, - ) - } - }, + keyboardOptions = KeyboardOptions(imeAction = imeAction), + cursorBrush = SolidColor(BisqTheme.colors.primary), enabled = !disabled, + decorationBox = { innerTextField -> + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (leftSuffix != null) { + leftSuffix() + Spacer(modifier = Modifier.width(10.dp)) + } + + Box(modifier = Modifier.weight(1f)) { + if (value.isEmpty()) { + BisqText.largeRegular( + text = placeholder, + color = BisqTheme.colors.secondaryHover + ) + } + innerTextField() + } + + if (rightSuffix != null) { + Box(modifier = Modifier.width(50.dp)) { + rightSuffix() + } + } + } + } + ) + } + if (helperText.isNotEmpty()) { + BisqText.smallRegular( + text = helperText, + color = BisqTheme.colors.grey1 ) - if (isFocused) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(2.dp) - .align(Alignment.BottomCenter) - .background(BisqTheme.colors.primary) - ) - } } } } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt index 5d4f0e6c..7a3f9af4 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt @@ -13,6 +13,11 @@ import org.jetbrains.compose.resources.painterResource expect fun rememberPlatformImagePainter(platformImage: PlatformImage): Painter +@Composable +fun ArrowDownIcon(modifier: Modifier = Modifier.size(12.dp)) { + Image(painterResource(Res.drawable.icon_arrow_down), "Down arrow icon", modifier = modifier) +} + @Composable fun BellIcon(modifier: Modifier = Modifier.size(30.dp)) { Image(painterResource(Res.drawable.icon_bell), "Bell icon", modifier = modifier) @@ -28,6 +33,11 @@ fun CopyIcon(modifier: Modifier = Modifier) { Image(painterResource(Res.drawable.icon_copy), "Copy icon", modifier = modifier) } +@Composable +fun LanguageIcon(modifier: Modifier = Modifier.size(16.dp)) { + Image(painterResource(Res.drawable.icon_language_grey), "Language icon", modifier = modifier) +} + @Composable fun InfoIcon(modifier: Modifier = Modifier.size(16.dp)) { Image(painterResource(Res.drawable.icon_info), "Info icon", modifier = modifier) @@ -53,6 +63,11 @@ fun ScanIcon(modifier: Modifier = Modifier) { Image(painterResource(Res.drawable.icon_qr), "Scan icon", modifier = modifier) } +@Composable +fun SearchIcon(modifier: Modifier = Modifier.size(16.dp)) { + Image(painterResource(Res.drawable.icon_search_dimmed), "Search icon", modifier = modifier) +} + @Composable fun SortIcon(modifier: Modifier = Modifier) { Image(painterResource(Res.drawable.icon_sort), "Sort icon", modifier = modifier) @@ -61,12 +76,17 @@ fun SortIcon(modifier: Modifier = Modifier) { @Composable fun StarEmptyIcon(modifier: Modifier = Modifier.size(16.dp)) { // TODO: Import right resource for this - Image(painterResource(Res.drawable.icon_star), "Empty star icon", modifier = modifier) + Image(painterResource(Res.drawable.icon_star_grey_hollow), "Empty star icon", modifier = modifier) +} + +@Composable +fun StarHalfFilledIcon(modifier: Modifier = Modifier.size(16.dp)) { + Image(painterResource(Res.drawable.icon_star_half_green), "Half filled star icon", modifier = modifier) } @Composable fun StarFillIcon(modifier: Modifier = Modifier.size(16.dp)) { - Image(painterResource(Res.drawable.icon_star), "Filled star icon", modifier = modifier) + Image(painterResource(Res.drawable.icon_star_green), "Filled star icon", modifier = modifier) } @Composable diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt index fcbf0f18..afff951c 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt @@ -1,11 +1,14 @@ package network.bisq.mobile.presentation.ui.components.atoms.icons import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import bisqapps.shared.presentation.generated.resources.Res import bisqapps.shared.presentation.generated.resources.bisq_logo import bisqapps.shared.presentation.generated.resources.bisq_logo_small +import network.bisq.mobile.presentation.ui.components.atoms.DynamicImage import org.jetbrains.compose.resources.painterResource @Composable @@ -16,4 +19,9 @@ fun BisqLogo(modifier: Modifier = Modifier) { @Composable fun BisqLogoSmall(modifier: Modifier = Modifier) { Image(painterResource(Res.drawable.bisq_logo_small), "Bisq Logo small", modifier = modifier) +} + +@Composable +fun BtcLogo(modifier: Modifier = Modifier.size(16.dp)) { + DynamicImage("drawable/bitcoin.png", modifier = modifier) } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqHDivider.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqHDivider.kt similarity index 66% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqHDivider.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqHDivider.kt index dea42767..406fc15c 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqHDivider.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqHDivider.kt @@ -1,14 +1,11 @@ -package network.bisq.mobile.presentation.ui.components.atoms +package network.bisq.mobile.presentation.ui.components.atoms.layout -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import network.bisq.mobile.presentation.ui.theme.BisqTheme @Composable fun BisqHDivider() { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqVDivider.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqVDivider.kt similarity index 65% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqVDivider.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqVDivider.kt index b57a5aa6..554a6ae4 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/BisqVDivider.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/BisqVDivider.kt @@ -1,18 +1,12 @@ -package network.bisq.mobile.presentation.ui.components.atoms +package network.bisq.mobile.presentation.ui.components.atoms.layout -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import network.bisq.mobile.presentation.ui.theme.BisqTheme - - @Composable fun BisqVDivider() { VerticalDivider( diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Gap.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/Gap.kt similarity index 56% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Gap.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/Gap.kt index 5580167f..ca8ae8ee 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Gap.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/layout/Gap.kt @@ -1,4 +1,4 @@ -package network.bisq.mobile.presentation.ui.components.atoms +package network.bisq.mobile.presentation.ui.components.atoms.layout import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -9,6 +9,11 @@ import network.bisq.mobile.presentation.ui.theme.BisqUIConstants object BisqGap { + @Composable() + fun VHalf() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPaddingHalf)) + } + @Composable() fun V1() { Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) @@ -29,6 +34,26 @@ object BisqGap { Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding4X)) } + @Composable() + fun V5() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding5X)) + } + + @Composable() + fun HQuarter() { + Spacer(modifier = Modifier.width(BisqUIConstants.ScreenPaddingQuarter)) + } + + @Composable() + fun HHalf() { + Spacer(modifier = Modifier.width(BisqUIConstants.ScreenPaddingHalf)) + } + + @Composable() + fun HHalfQuarter() { + Spacer(modifier = Modifier.width(BisqUIConstants.ScreenPaddingHalfQuarter)) + } + @Composable() fun H1() { Spacer(modifier = Modifier.width(BisqUIConstants.ScreenPadding)) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/AmountSelector.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/AmountSelector.kt index 698992f8..cd30a007 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/AmountSelector.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/AmountSelector.kt @@ -1,140 +1,68 @@ package network.bisq.mobile.presentation.ui.components.molecules -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch import network.bisq.mobile.presentation.ui.components.atoms.* -import network.bisq.mobile.presentation.ui.components.atoms.icons.InfoIcon -import network.bisq.mobile.presentation.ui.helpers.numberFormatter +import network.bisq.mobile.presentation.ui.helpers.* import network.bisq.mobile.presentation.ui.theme.BisqTheme -import network.bisq.mobile.presentation.ui.theme.BisqUIConstants -import kotlin.math.roundToInt -@OptIn(ExperimentalMaterial3Api::class) @Composable fun BisqAmountSelector( - minAmount: Float, - maxAmount: Float, + minAmount: Double, + maxAmount: Double, exchangeRate: Double, currency: String, - onValueChange: (Float) -> Unit + onValueChange: ((Double) -> Unit)? = null ) { - var sliderPosition by remember { mutableFloatStateOf((minAmount + maxAmount) * 0.5f) } - val roundedNumber = (sliderPosition * 100).roundToInt() / 100.0 - val price = if (roundedNumber.toString().split(".").getOrNull(1)?.length == 1) - "${roundedNumber}0" // to make 3.1 to 3.10 - else - roundedNumber.toString() // if it's 3.14, keep the same + var fiatValue by remember { mutableDoubleStateOf((minAmount + maxAmount) * 0.5) } + val sats = (100_000_000L * (fiatValue.toDouble()) / exchangeRate).toLong() - val satsValue = numberFormatter.satsFormat(price.toDouble() / exchangeRate) - - val highLightedSatsZeros = satsValue.takeWhile { it == '0' || it == '.' } - val sats = satsValue.dropWhile { it == '0' || it == '.' } - var showPopup by remember { mutableStateOf(false) } - val satoshi = sats.reversed().chunked(3).joinToString(" ").reversed() - val tooltipState = rememberTooltipState(isPersistent = true) - val scope = rememberCoroutineScope() - - LaunchedEffect(sliderPosition) { - onValueChange(sliderPosition) + LaunchedEffect(fiatValue) { + onValueChange?.invoke(fiatValue) } Column( - verticalArrangement = Arrangement.Top + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.Bottom - ) { - BisqText.h1Regular( - text = price, - ) - BisqText.h5Regular( - text = currency - ) - } - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // TODO: Create a control out of this - DynamicImage( - "drawable/bitcoin.png", - modifier = Modifier.size(16.dp) - ) - Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { - BisqText.h5Regular( - text = highLightedSatsZeros, - color = BisqTheme.colors.grey2 - ) - BisqText.h5Regular( - text = "$satoshi sats", - ) - } - BisqGap.H1() - // TODO: Needs work - TooltipBox(positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), - tooltip = { - Box( - modifier = Modifier.background(BisqTheme.colors.dark5), - contentAlignment = Alignment.Center - ){ - BisqText.h5Regular( - text = "Hi welcome" - ) - } - }, - // modifier = Modifier.offset((-20).dp, (-20).dp), - state = tooltipState - ) { - IconButton(onClick = { - scope.launch { - tooltipState.show() - } - }) { - // TODO: Make SVG Icons work! - InfoIcon() - } - } - } + FiatInputField( + text = fiatValue.toInt().toString(), + onValueChanged = { fiatValue = it.toDoubleOrNull() ?: (0.0) }, + currency = currency + ) + + if (fiatValue < minAmount || fiatValue > maxAmount) { + BisqText.baseRegular("Amount out of range", color = BisqTheme.colors.danger) } - Column(modifier = Modifier.padding(horizontal = 24.dp)) { + BtcSatsText(sats) + + Column { BisqSlider( - minAmount, - maxAmount, - sliderPosition, - onValueChange = { sliderPosition = it } + fiatValue.toFloat(), + onValueChange = { fiatValue = it.toDouble() }, + minAmount.toFloat(), + maxAmount.toFloat(), ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp) ) { - - val minString = minAmount // Do precision rounding to 2 decimals - val maxString = maxAmount // Do precision rounding to 2 decimals BisqText.smallRegular( - text = "Min $minString $currency", + text = "Min ${minAmount.toStringWith2Decimals()} $currency", color = BisqTheme.colors.grey2 ) BisqText.smallRegular( - text = "Max $maxString $currency", + text = "Max ${maxAmount.toStringWith2Decimals()} $currency", color = BisqTheme.colors.grey2 ) } } } -} \ No newline at end of file +} diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BottomSheet.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BottomSheet.kt new file mode 100644 index 00000000..e8d61d5a --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BottomSheet.kt @@ -0,0 +1,47 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BisqBottomSheet( + onDismissRequest:() -> Unit, + content: @Composable () -> Unit +) { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + shape = RoundedCornerShape(16.dp), + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true, + ), + containerColor = BisqTheme.colors.dark3, + dragHandle = { + Box( + modifier = Modifier.padding(top = 20.dp) + .clip(shape = RoundedCornerShape(4.dp)) + ) { + Box( + modifier = Modifier.height(4.dp).width(60.dp) + .background(Color(0xFF6F6F6F)) + ) + } + } + ) { + content() + } +} + diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ConfirmationDialog.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ConfirmationDialog.kt index 057e72bc..a94f63a0 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ConfirmationDialog.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ConfirmationDialog.kt @@ -10,6 +10,7 @@ import network.bisq.mobile.presentation.ui.components.atoms.BisqButton import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.molecules.BisqDialog import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants @Composable fun ConfirmationDialog( @@ -17,8 +18,9 @@ fun ConfirmationDialog( message: String = "Are you sure?", confirmButtonText: String = "Yes", cancelButtonText: String = "No", + onConfirm: () -> Unit, onDismissRequest: () -> Unit, - ) { +) { BisqDialog { Column( modifier = Modifier @@ -37,12 +39,18 @@ fun ConfirmationDialog( text = cancelButtonText, backgroundColor = BisqTheme.colors.dark5, onClick = { onDismissRequest() }, - padding = PaddingValues(horizontal = 42.dp, vertical = 4.dp) + padding = PaddingValues( + horizontal = BisqUIConstants.ScreenPadding4X, + vertical = BisqUIConstants.ScreenPaddingHalf + ) ) BisqButton( text = confirmButtonText, - onClick = { onDismissRequest() }, - padding = PaddingValues(horizontal = 32.dp, vertical = 4.dp) + onClick = { onConfirm() }, + padding = PaddingValues( + horizontal = BisqUIConstants.ScreenPadding4X, + vertical = BisqUIConstants.ScreenPaddingHalf + ) ) } } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BisqDialog.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/Dialog.kt similarity index 90% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BisqDialog.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/Dialog.kt index 49465224..5edb4211 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/BisqDialog.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/Dialog.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import network.bisq.mobile.presentation.ui.theme.BisqTheme @Composable @@ -21,7 +22,7 @@ fun BisqDialog( onDismissRequest: () -> Unit = {}, content: @Composable ColumnScope.() -> Unit = {} ) { - Dialog(onDismissRequest = { onDismissRequest() }) { + Dialog(onDismissRequest = onDismissRequest, properties = DialogProperties(dismissOnClickOutside = true)) { Box( modifier = Modifier .fillMaxSize() diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/DirectionToggle.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/DirectionToggle.kt index 21db242d..fcf409ae 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/DirectionToggle.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/DirectionToggle.kt @@ -61,7 +61,6 @@ fun DirectionToggle( .offset(x = slideOffset) .background(BisqTheme.colors.primary, RoundedCornerShape(4.dp)) ) { - BisqText.baseMedium( text = toggleText, color = BisqTheme.colors.light1, @@ -86,8 +85,9 @@ fun DirectionToggle( } ) ) { + val label = if (direction.isBuy) strings.offers_list_buy_from else strings.offers_list_sell_to BisqText.baseMedium( - text = toggleText, + text = label, color = BisqTheme.colors.light1, ) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/FiatInputField.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/FiatInputField.kt new file mode 100644 index 00000000..23c4e5f9 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/FiatInputField.kt @@ -0,0 +1,111 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_regular +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.Font + +@Composable +fun FiatInputField( + text: String, + onValueChanged: (String) -> Unit = {}, + label: String = "", + currency: String, + paddingValues: PaddingValues = PaddingValues(all = 0.dp), + indicatorColor: Color = BisqTheme.colors.primary, +) { + var isFocused by remember { mutableStateOf(false) } + + val keyboardController = LocalSoftwareKeyboardController.current + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(paddingValues) + .clip(shape = RoundedCornerShape(6.dp)) + .background(color = BisqTheme.colors.secondary) + .drawBehind { + if (isFocused) { + drawLine( + color = indicatorColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = 4.dp.toPx() + ) + } + } + ) { + BasicTextField( + value = text, + onValueChange = onValueChanged, + modifier = Modifier + .padding(12.dp) + .fillMaxWidth() + .onFocusChanged { focusState -> + isFocused = focusState.isFocused + }, + textStyle = TextStyle( + color = Color.White, + fontSize = 32.sp, + textAlign = TextAlign.End, + textDecoration = TextDecoration.None + ), + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), + cursorBrush = SolidColor(BisqTheme.colors.primary), + decorationBox = { innerTextField -> + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.Bottom + ) { + if (label.isNotEmpty()) { + BisqText.h5Regular( + text = label, + color = BisqTheme.colors.grey1, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + Box(modifier = Modifier.weight(1f)) { + innerTextField() + } + + BisqText.h5Regular(text = currency) + } + } + ) + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/OfferCard.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/OfferCard.kt index 6efac1f9..a21639c3 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/OfferCard.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/OfferCard.kt @@ -1,37 +1,32 @@ package network.bisq.mobile.presentation.ui.components.molecules -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.IconButton -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import bisqapps.shared.presentation.generated.resources.Res -import bisqapps.shared.presentation.generated.resources.icon_chat_outlined import network.bisq.mobile.domain.data.model.OfferListItem -import network.bisq.mobile.presentation.ui.components.atoms.BisqText -import network.bisq.mobile.presentation.ui.components.atoms.BisqVDivider -import network.bisq.mobile.presentation.ui.components.atoms.PaymentMethods -import network.bisq.mobile.presentation.ui.components.atoms.ProfileRating +import network.bisq.mobile.presentation.ui.components.atoms.* import network.bisq.mobile.presentation.ui.components.atoms.icons.ChatIcon +import network.bisq.mobile.presentation.ui.components.atoms.icons.LanguageIcon +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqVDivider +import network.bisq.mobile.presentation.ui.components.molecules.* import network.bisq.mobile.presentation.ui.theme.BisqTheme -import org.jetbrains.compose.resources.painterResource @Composable fun OfferCard( item: OfferListItem, onClick: () -> Unit, onChatClick: () -> Unit, - ) { +) { Row( modifier = Modifier .fillMaxWidth() @@ -51,27 +46,44 @@ fun OfferCard( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { - ProfileRating(item) + Column( + verticalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.weight(3f) + ) { + UserProfile(item) PaymentMethods(item) } Column( horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.SpaceBetween + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.weight(2f) ) { BisqText.smallMedium( - // text = "\$52,000 / BTC", text = item.formattedPrice, color = BisqTheme.colors.primary ) - BisqText.largeRegular( - text = "\$98,000 / BTC", - color = BisqTheme.colors.grey1 - ) - BisqText.baseRegular( - text = item.formattedQuoteAmount, - color = BisqTheme.colors.light1 - ) + Row(verticalAlignment = Alignment.CenterVertically) { + LanguageIcon() + BisqText.largeRegular( + text = ": ${item.supportedLanguageCodes}", + color = BisqTheme.colors.grey1 + ) + } + BisqGap.H1() + // Len: 13 - "300 - 600 USD" + // Len: 17 - "3,000 - 6,000 XYZ" + // Len: 23 - "150,640 - 1,200,312 CRC" + if (item.formattedQuoteAmount.length < 18) { + BisqText.baseRegular( + text = item.formattedQuoteAmount, + color = BisqTheme.colors.light1 + ) + } else { + BisqText.smallRegular( + text = item.formattedQuoteAmount, + color = BisqTheme.colors.light1 + ) + } } } BisqVDivider() diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/PaymentMethods.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentMethods.kt similarity index 93% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/PaymentMethods.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentMethods.kt index 30c13dd5..3867270f 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/PaymentMethods.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentMethods.kt @@ -1,4 +1,4 @@ -package network.bisq.mobile.presentation.ui.components.atoms +package network.bisq.mobile.presentation.ui.components.molecules import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -8,6 +8,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import network.bisq.mobile.domain.data.model.OfferListItem +import network.bisq.mobile.presentation.ui.components.atoms.DynamicImage // TODO: Get params and render apt @Composable diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeAmountSelector.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeAmountSelector.kt index 3438918d..23725f24 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeAmountSelector.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeAmountSelector.kt @@ -19,10 +19,10 @@ import kotlin.math.roundToInt // TODO: This has more work to do @Composable fun RangeAmountSelector( - minAmount: Float, - maxAmount: Float, + minAmount: Double, + maxAmount: Double, ) { - var sliderPosition by remember { mutableStateOf(minAmount .. maxAmount) } + var sliderPosition by remember { mutableStateOf(minAmount..maxAmount) } var tradeValue = 873f..1200f Column { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { @@ -113,12 +113,12 @@ fun RangeAmountSelector( } Column { BisqRangeSlider( - minAmount, - maxAmount, - sliderPosition, + sliderPosition.start.toFloat()..sliderPosition.endInclusive.toFloat(), onValueChange = { - sliderPosition = it - } + sliderPosition = it.start.toDouble()..it.endInclusive.toDouble() + }, + minAmount.toFloat(), + maxAmount.toFloat(), ) Row( verticalAlignment = Alignment.CenterVertically, diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RageSlider.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeSlider.kt similarity index 94% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RageSlider.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeSlider.kt index 04bf1d90..40c434bb 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RageSlider.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeSlider.kt @@ -18,10 +18,10 @@ import network.bisq.mobile.presentation.ui.theme.BisqTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun BisqRangeSlider( + value: ClosedFloatingPointRange, + onValueChange: (ClosedFloatingPointRange) -> Unit, minAmount: Float, maxAmount: Float, - tradeValue: ClosedFloatingPointRange, - onValueChange: (ClosedFloatingPointRange) -> Unit ){ val colors = SliderColors( thumbColor = BisqTheme.colors.primary, @@ -38,7 +38,7 @@ fun BisqRangeSlider( RangeSlider( modifier = Modifier.fillMaxWidth(), - value = tradeValue, + value = value, onValueChange = {onValueChange(it)}, valueRange = minAmount .. maxAmount, track = { rangeSliderState -> diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProfileRating.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/UserProfile.kt similarity index 66% rename from shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProfileRating.kt rename to shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/UserProfile.kt index e8391026..c58443f6 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProfileRating.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/UserProfile.kt @@ -1,4 +1,4 @@ -package network.bisq.mobile.presentation.ui.components.atoms +package network.bisq.mobile.presentation.ui.components.molecules import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -14,31 +14,28 @@ import bisqapps.shared.presentation.generated.resources.Res import bisqapps.shared.presentation.generated.resources.icon_star import bisqapps.shared.presentation.generated.resources.img_bot_image import network.bisq.mobile.domain.data.model.OfferListItem +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.atoms.StarRating import network.bisq.mobile.presentation.ui.components.atoms.icons.StarEmptyIcon import network.bisq.mobile.presentation.ui.components.atoms.icons.StarFillIcon import org.jetbrains.compose.resources.painterResource // TODO: Get params and render apt @Composable -fun ProfileRating(item: OfferListItem) { - val fiveSystemScore:Int = 3 // item.reputationScore.fiveSystemScore.toInt() +fun UserProfile(item: OfferListItem) { + val fiveSystemScore:Double = 3.5 // TODO: item.reputationScore.fiveSystemScore Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { Image( painterResource(Res.drawable.img_bot_image), "", - modifier = Modifier.size(48.dp) + modifier = Modifier.size(36.dp) ) Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - BisqText.largeRegular(text = item.userName) - Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) { - // TODO: Find right icons from Bisq2 and update - repeat(fiveSystemScore) { - StarFillIcon() - } - repeat(5 - fiveSystemScore) { - StarEmptyIcon() - } - } + BisqText.baseRegular( + text = item.userName, // + " abcde fghijkl mnop qrst uvwxyz", + singleLine = true, + ) + StarRating(fiveSystemScore) } } } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/market/MarketFilters.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/market/MarketFilters.kt new file mode 100644 index 00000000..74e1394e --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/market/MarketFilters.kt @@ -0,0 +1,87 @@ +package network.bisq.mobile.presentation.ui.components.organisms.market + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.BisqButton +import network.bisq.mobile.presentation.ui.components.atoms.BisqDropDown +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import network.bisq.mobile.presentation.ui.uicases.offers.MarketListPresenter +import org.koin.compose.koinInject + +enum class MarketSortBy(val displayName: String) { + MostOffers("Most Offers"), + NameAZ("Name A-Z"), + NameZA("Name Z-A") +} + +enum class MarketFilter(val displayName: String) { + WithOffers("With Offers"), + All("All") +} + + +@Composable +fun MarketFilters( + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + + val presenter: MarketListPresenter = koinInject() + + Column(modifier = Modifier.padding(all = BisqUIConstants.ScreenPadding2X)) { + + BisqDropDown( + label = "Sort by", + value = presenter.sortBy.collectAsState().value.displayName, + items = MarketSortBy.entries.map { it.displayName }, + onValueChanged = { + val newValue = when (it) { + MarketSortBy.MostOffers.displayName -> MarketSortBy.MostOffers + MarketSortBy.NameAZ.displayName -> MarketSortBy.NameAZ + else -> MarketSortBy.NameZA + } + presenter.setSortBy(newValue) + } + ) + + BisqGap.V2() + + BisqDropDown( + label = "Show markets", + value = presenter.filter.collectAsState().value.displayName, + items = MarketFilter.entries.map { it.displayName }, + onValueChanged = { + val newValue = when (it) { + MarketFilter.All.displayName -> MarketFilter.All + else -> MarketFilter.WithOffers + } + presenter.setFilter(newValue) } + ) + +// BisqGap.V1() +// +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.SpaceBetween +// ) { +// BisqButton( +// text = "Cancel", +// onClick = onCancel, +// backgroundColor = BisqTheme.colors.dark5, +// padding = PaddingValues(horizontal = 48.dp, vertical = 4.dp), +// ) +// BisqButton( +// text = "Apply", +// onClick = onConfirm, +// padding = PaddingValues(horizontal = 48.dp, vertical = 4.dp), +// ) +// } + + } + +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/settings/AddPaymentAccountCard.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/settings/AddPaymentAccountCard.kt new file mode 100644 index 00000000..4846d285 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/settings/AddPaymentAccountCard.kt @@ -0,0 +1,80 @@ +package network.bisq.mobile.presentation.ui.components.organisms.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import cafe.adriel.lyricist.LocalStrings +import network.bisq.mobile.presentation.ui.components.atoms.BisqButton +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants + +@Composable +fun AppPaymentAccountCard( + onConfirm: (String, String) -> Unit, + onCancel: () -> Unit, +) { + val strings = LocalStrings.current.user + val stringsCommon = LocalStrings.current.common + + var accountName by remember { mutableStateOf("") } + var accountDescription by remember { mutableStateOf("") } + + Column( + modifier = Modifier.padding( + horizontal = BisqUIConstants.ScreenPadding, + ), verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding) + ) { + BisqText.h5Regular( + text = strings.user_paymentAccounts_createAccount_headline, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + BisqText.smallRegular( + text = strings.user_paymentAccounts_createAccount_subtitle, + color = BisqTheme.colors.grey1, + textAlign = TextAlign.Center + ) + BisqTextField( + value = accountName, + onValueChanged = { accountName = it }, + placeholder = strings.user_paymentAccounts_createAccount_accountName_prompt, + label = strings.user_userProfile_payment_account + ) + BisqTextField( + value = accountDescription, + onValueChanged = { accountDescription = it }, + placeholder = strings.user_paymentAccounts_createAccount_accountData_prompt, + label = strings.user_paymentAccounts_accountData, + isTextArea = true + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + BisqButton( + text = stringsCommon.buttons_cancel, + backgroundColor = BisqTheme.colors.dark5, + onClick = onCancel, + padding = PaddingValues(horizontal = 24.dp, vertical = 12.dp) + ) + BisqButton( + text = stringsCommon.buttons_save, + onClick = { onConfirm(accountName, accountDescription) }, + padding = PaddingValues(horizontal = 64.dp, vertical = 12.dp) + ) + } + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowAccountDetails.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowAccountDetails.kt index 69681794..850fac32 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowAccountDetails.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowAccountDetails.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import network.bisq.mobile.presentation.ui.components.atoms.* +import network.bisq.mobile.presentation.ui.components.atoms.layout.* import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import network.bisq.mobile.presentation.ui.uicases.trades.ITradeFlowPresenter import org.koin.compose.koinInject diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowBtcPayment.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowBtcPayment.kt index 433cba5e..7270c5a8 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowBtcPayment.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowBtcPayment.kt @@ -11,7 +11,7 @@ import bisqapps.shared.presentation.generated.resources.img_bitcoin_payment_wait import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.delay import network.bisq.mobile.presentation.ui.components.atoms.BisqButton -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.CircularLoadingImage import network.bisq.mobile.presentation.ui.theme.BisqTheme diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowCompleted.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowCompleted.kt index 9816aa19..7aab33b4 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowCompleted.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowCompleted.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import network.bisq.mobile.presentation.ui.components.atoms.BisqButton -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField import network.bisq.mobile.presentation.ui.theme.BisqTheme diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowFiatPayment.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowFiatPayment.kt index bd3dff54..4db81545 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowFiatPayment.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeFlowFiatPayment.kt @@ -13,6 +13,7 @@ import bisqapps.shared.presentation.generated.resources.img_fiat_payment_waiting import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.delay import network.bisq.mobile.presentation.ui.components.atoms.* +import network.bisq.mobile.presentation.ui.components.atoms.layout.* import network.bisq.mobile.presentation.ui.theme.BisqTheme import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import network.bisq.mobile.presentation.ui.uicases.trades.ITradeFlowPresenter diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeHeader.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeHeader.kt index e907c8ad..97f1a732 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeHeader.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/trades/TradeHeader.kt @@ -19,10 +19,10 @@ import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ui.components.atoms.BisqButton -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText -import network.bisq.mobile.presentation.ui.components.atoms.ProfileRating import network.bisq.mobile.presentation.ui.components.atoms.icons.UpIcon +import network.bisq.mobile.presentation.ui.components.molecules.UserProfile import network.bisq.mobile.presentation.ui.components.molecules.info.InfoBoxStyle import network.bisq.mobile.presentation.ui.components.molecules.info.InfoRow import network.bisq.mobile.presentation.ui.theme.BisqTheme @@ -78,13 +78,16 @@ fun TradeHeader( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - ProfileRating(offer) + Column(modifier = Modifier.weight(3f)) { + UserProfile(offer) + } Column( verticalArrangement = Arrangement.spacedBy(2.dp), - horizontalAlignment = Alignment.End + horizontalAlignment = Alignment.End, + modifier = Modifier.weight(2f) ) { - BisqText.xsmallRegular(text = "10000.02 USD") - BisqText.xsmallRegular(text = "0.00173399 BTC") + BisqText.smallRegular(text = "10000.02 USD") + BisqText.smallRegular(text = "0.00173399 BTC") } } AnimatedVisibility( diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt index d064d2e9..da7523bc 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt @@ -6,3 +6,5 @@ data class BottomNavigationItem(val title: String, val route: String, val icon: data class PagerViewItem(val title: String, val image: DrawableResource, val desc: String) data class PaymentTypeData(val image: String, val title: String) + +data class PaymentAccount(val id: String, val name: String, var description: String) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/DoubleHelper.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/DoubleHelper.kt new file mode 100644 index 00000000..50ba3a74 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/DoubleHelper.kt @@ -0,0 +1,9 @@ +package network.bisq.mobile.presentation.ui.helpers + +import kotlin.math.roundToInt + +fun Double.toStringWith2Decimals(): String { + val roundedNumber = (this * 100).roundToInt() / 100.0 + val isNumberHasSingleZero = roundedNumber.toString().split(".").getOrNull(1)?.length == 1 + return if (isNumberHasSingleZero) "${roundedNumber}0" else roundedNumber.toString() +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.kt index 464098c9..bc3c5258 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.kt @@ -1,6 +1,7 @@ package network.bisq.mobile.presentation.ui.helpers interface NumberFormatter { + // TODO: Should be re-named eightDecimalFormat fun satsFormat(value: Double): String } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/StringHelper.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/StringHelper.kt index e7355016..255ab6ea 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/StringHelper.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/StringHelper.kt @@ -2,11 +2,10 @@ package network.bisq.mobile.presentation.ui.helpers import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.TextMeasurer -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.* import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.sp +import network.bisq.mobile.presentation.ui.theme.BisqTheme object StringHelper { @Composable diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/UIConstants.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/UIConstants.kt index 905604d4..778de33b 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/UIConstants.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/UIConstants.kt @@ -3,6 +3,9 @@ package network.bisq.mobile.presentation.ui.theme import androidx.compose.ui.unit.dp object BisqUIConstants { + val ScreenPaddingQuarter = 3.dp + val ScreenPaddingHalf = 6.dp + val ScreenPaddingHalfQuarter = 9.dp val ScreenPadding = 12.dp val ScreenPadding2X = 24.dp val ScreenPadding3X = 36.dp diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListPresenter.kt index 8e75defa..648407c0 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListPresenter.kt @@ -1,34 +1,109 @@ package network.bisq.mobile.presentation.ui.uicases.offers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import network.bisq.mobile.domain.data.model.MarketListItem import network.bisq.mobile.domain.service.offerbook.OfferbookServiceFacade import network.bisq.mobile.presentation.BasePresenter import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.components.organisms.market.MarketFilter +import network.bisq.mobile.presentation.ui.components.organisms.market.MarketSortBy import network.bisq.mobile.presentation.ui.navigation.Routes class MarketListPresenter( mainPresenter: MainPresenter, private val offerbookServiceFacade: OfferbookServiceFacade, - ) : BasePresenter(mainPresenter) { +) : BasePresenter(mainPresenter) { private var mainCurrencies = OfferbookServiceFacade.mainCurrencies - var marketListItemWithNumOffers: List = offerbookServiceFacade.offerbookMarketItems + private var _sortBy = MutableStateFlow(MarketSortBy.MostOffers) + var sortBy: StateFlow = _sortBy + fun setSortBy(newValue: MarketSortBy) { + _sortBy.value = newValue + } + + private var _filter = MutableStateFlow(MarketFilter.WithOffers) + var filter: StateFlow = _filter + fun setFilter(newValue: MarketFilter) { + _filter.value = newValue + } + + private var _searchText = MutableStateFlow("") + val searchText: StateFlow = _searchText + fun setSearchText(newValue: String) { + _searchText.value = newValue + } + + /* + var marketListItems: List = offerbookServiceFacade.offerbookMarketItems .sortedWith( - compareByDescending { it.numOffers.value } + compareByDescending { it.numOffers.value } .thenByDescending { mainCurrencies.contains(it.market.quoteCurrencyCode.lowercase()) } // [1] - .thenBy { item-> + .thenBy { item -> if (!mainCurrencies.contains(item.market.quoteCurrencyCode.lowercase())) item.market.quoteCurrencyName else null // Null values will naturally be sorted together } ) + */ // [1] thenBy doesn’t work as expected for boolean expressions because true and false are // sorted alphabetically (false before true), thus we use thenByDescending + val marketListItemWithNumOffers: StateFlow> = combine( + _filter, + _searchText, + _sortBy + ) { filter: MarketFilter, searchText: String, sortBy: MarketSortBy -> + computeMarketList(filter, searchText, sortBy) + }.stateIn( + CoroutineScope(Dispatchers.Main), + SharingStarted.Lazily, + emptyList() + ) + + private fun computeMarketList( + filter: MarketFilter, + searchText: String, + sortBy: MarketSortBy + ): List { + return offerbookServiceFacade.offerbookMarketItems + .filter { item -> + when (filter) { + MarketFilter.WithOffers -> item.numOffers.value > 0 + MarketFilter.All -> true + } + } + .filter { item -> + searchText.isEmpty() || + item.market.quoteCurrencyCode.contains(searchText, ignoreCase = true) || + item.market.quoteCurrencyName.contains(searchText, ignoreCase = true) + } + .sortedWith( + compareByDescending { + when (sortBy) { + MarketSortBy.MostOffers -> it.numOffers.value + else -> 0 + } + } + .thenByDescending { mainCurrencies.contains(it.market.quoteCurrencyCode.lowercase()) } + .thenBy { + when (sortBy) { + MarketSortBy.NameAZ -> it.market.quoteCurrencyName + MarketSortBy.NameZA -> it.market.quoteCurrencyName + else -> null + } + } + .let { comparator -> + if (sortBy == MarketSortBy.NameZA) comparator.reversed() else comparator + } + ) + } + fun onSelectMarket(marketListItem: MarketListItem) { offerbookServiceFacade.selectMarket(marketListItem) rootNavigator.navigate(Routes.OfferList.name) - // rootNavigator.navigate(Routes.TradeFlow.name) } override fun onViewAttached() { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListScreen.kt index a54c5483..79eebdcd 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/MarketListScreen.kt @@ -3,38 +3,65 @@ package network.bisq.mobile.presentation.ui.uicases.offers import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier +import androidx.compose.runtime.* import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings -import network.bisq.mobile.presentation.ui.components.CurrencyProfileCard -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap -import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField +import network.bisq.mobile.presentation.ui.components.CurrencyCard +import network.bisq.mobile.presentation.ui.components.atoms.BisqButton +import network.bisq.mobile.presentation.ui.components.atoms.BisqButtonType +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.BisqSearchField import network.bisq.mobile.presentation.ui.components.atoms.icons.SortIcon import network.bisq.mobile.presentation.ui.components.layout.BisqStaticLayout +import network.bisq.mobile.presentation.ui.components.molecules.BisqBottomSheet +import network.bisq.mobile.presentation.ui.components.organisms.market.MarketFilters import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle -import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import org.koin.compose.koinInject @Composable fun MarketListScreen() { val strings = LocalStrings.current.common val presenter: MarketListPresenter = koinInject() + var showFilterDialog by remember { mutableStateOf(false) } + + val marketItems = presenter.marketListItemWithNumOffers.collectAsState().value RememberPresenterLifecycle(presenter) BisqStaticLayout(padding = PaddingValues(all = 0.dp), verticalArrangement = Arrangement.Top) { - BisqTextField(label = "", placeholder = strings.common_search, value ="", onValueChanged = {}) + + BisqSearchField( + value = presenter.searchText.collectAsState().value, + onValueChanged = { presenter.setSearchText(it) }, + placeholder = strings.common_search, + rightSuffix = { + // TODO: Height to be reduced with Icon only buttons + BisqButton( + iconOnly = { SortIcon() }, + onClick = { showFilterDialog = true }, + type = BisqButtonType.Clear + ) + } + ) BisqGap.V1() LazyColumn { - items(presenter.marketListItemWithNumOffers) { item -> - CurrencyProfileCard( + items(marketItems) { item -> + CurrencyCard( item, onClick = { presenter.onSelectMarket(item) } ) } } + + if (showFilterDialog) { + BisqBottomSheet(onDismissRequest = { showFilterDialog = false }) { + MarketFilters( + onConfirm = {}, + onCancel = {} + ) + } + } } } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/OffersListScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/OffersListScreen.kt index c6f78809..6736eea0 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/OffersListScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/OffersListScreen.kt @@ -1,27 +1,23 @@ package network.bisq.mobile.presentation.ui.uicases.offers import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.client.replicated_model.offer.Direction import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ViewPresenter -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.layout.BisqStaticScaffold import network.bisq.mobile.presentation.ui.components.molecules.DirectionToggle import network.bisq.mobile.presentation.ui.components.molecules.OfferCard import network.bisq.mobile.presentation.ui.components.molecules.TopBar import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle -import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import org.koin.compose.koinInject interface IOffersListPresenter : ViewPresenter { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/AmountSelectorScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/AmountSelectorScreen.kt index c364b49b..287a0bbe 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/AmountSelectorScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/AmountSelectorScreen.kt @@ -32,8 +32,8 @@ fun CreateOfferAmountSelectorScreen() { val stringsCommon = LocalStrings.current.common val presenter: ICreateOfferPresenter = koinInject() - val offerMinFiatAmount = 800.0f - val offerMaxFiatAmount = 1500.0f + val offerMinFiatAmount = 800.0 + val offerMaxFiatAmount = 1500.0 val state by presenter.state.collectAsState() val isBuy = presenter.direction.collectAsState().value.isBuy val fixedAmount = presenter.fixedAmount.collectAsState().value @@ -96,7 +96,7 @@ fun CreateOfferAmountSelectorScreen() { maxAmount = offerMaxFiatAmount, exchangeRate = 95000.0, currency = "USD", - onValueChange = { value -> presenter.onFixedAmountChange(value) } + // onValueChanged = { value -> presenter.onFixedAmountChange(value) } ) } else { RangeAmountSelector( diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/BuySellScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/BuySellScreen.kt index fd7066e1..101a18af 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/BuySellScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/BuySellScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import network.bisq.mobile.presentation.ui.components.atoms.* +import network.bisq.mobile.presentation.ui.components.atoms.layout.* import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle import network.bisq.mobile.presentation.ui.theme.BisqTheme diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferCurrencySelectorScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferCurrencySelectorScreen.kt index 7c4d28cc..11a6a071 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferCurrencySelectorScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferCurrencySelectorScreen.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings -import network.bisq.mobile.presentation.ui.components.CurrencyProfileCard -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.CurrencyCard +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold @@ -77,7 +77,7 @@ fun CreateOfferCurrencySelectorScreen() { verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(presenter.marketListItemWithNumOffers) { item -> - CurrencyProfileCard( + CurrencyCard( item, isSelected = state.selectedOfferbookMarket.market.quoteCurrencyCode == item.market.quoteCurrencyCode, onClick = { presenter.onSelectMarket(item) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/PaymentMethodSelectorScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/PaymentMethodSelectorScreen.kt index bcabf84f..ce070a04 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/PaymentMethodSelectorScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/PaymentMethodSelectorScreen.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold import network.bisq.mobile.presentation.ui.components.organisms.PaymentMethodCard diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/ReviewOfferScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/ReviewOfferScreen.kt index 77ece910..45704a19 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/ReviewOfferScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/ReviewOfferScreen.kt @@ -13,8 +13,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap -import network.bisq.mobile.presentation.ui.components.atoms.BisqHDivider +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqHDivider import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold import network.bisq.mobile.presentation.ui.components.molecules.info.InfoBox diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/TradePriceSelectorScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/TradePriceSelectorScreen.kt index 0e82a6f8..7afe1681 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/TradePriceSelectorScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/TradePriceSelectorScreen.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.unit.sp import bisqapps.shared.presentation.generated.resources.Res import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_medium import cafe.adriel.lyricist.LocalStrings -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField import network.bisq.mobile.presentation.ui.components.atoms.NoteText diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/PaymentMethodScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/PaymentMethodScreen.kt index 39a56f4e..1912e2c5 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/PaymentMethodScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/PaymentMethodScreen.kt @@ -13,7 +13,7 @@ import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ViewPresenter -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.atoms.DynamicImage import network.bisq.mobile.presentation.ui.theme.BisqTheme diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/ReviewTradeScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/ReviewTradeScreen.kt index 9b29c751..a17449ca 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/ReviewTradeScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/ReviewTradeScreen.kt @@ -4,14 +4,13 @@ import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ViewPresenter -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap -import network.bisq.mobile.presentation.ui.components.atoms.BisqHDivider +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqHDivider import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.theme.BisqTheme import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountScreen.kt index 1ea87d2d..1cc9bb10 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountScreen.kt @@ -1,27 +1,23 @@ package network.bisq.mobile.presentation.ui.uicases.offers.takeOffer -import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ViewPresenter -import network.bisq.mobile.presentation.ui.components.atoms.BisqGap +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold import network.bisq.mobile.presentation.ui.components.molecules.BisqAmountSelector import network.bisq.mobile.presentation.ui.theme.BisqTheme -import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import org.koin.compose.koinInject interface ITakeOfferTradeAmountPresenter : ViewPresenter { // TODO: Update later to refer to a single OfferListItem val offerListItems: StateFlow> fun amountConfirmed() - + fun onFixedAmountChange(amount: Float) } @@ -33,8 +29,8 @@ fun TakeOfferTradeAmountScreen() { val offer = presenter.offerListItems.collectAsState().value.first() // TODO: Should be from OfferListItem - val offerMinFiatAmount = 800.0f - val offerMaxFiatAmount = 1500.0f + val offerMinFiatAmount = 800.0 + val offerMaxFiatAmount = 1500.0 MultiScreenWizardScaffold( strings.bisqEasy_takeOffer_progress_amount, @@ -56,14 +52,14 @@ fun TakeOfferTradeAmountScreen() { color = BisqTheme.colors.grey2 ) - Spacer(modifier = Modifier.height(128.dp)) + BisqGap.V5() BisqAmountSelector( minAmount = offerMinFiatAmount, maxAmount = offerMaxFiatAmount, - exchangeRate = 95000.0, + exchangeRate = 100000.0, currency = "USD", - onValueChange = {value -> } + onValueChange = { println(it) } ) } } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountPresenter.kt new file mode 100644 index 00000000..385b36c9 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountPresenter.kt @@ -0,0 +1,59 @@ +package network.bisq.mobile.presentation.ui.uicases.settings + +import kotlinx.coroutines.flow.MutableStateFlow +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.domain.data.repository.SettingsRepository +import network.bisq.mobile.presentation.BasePresenter +import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.composeModels.PaymentAccount + +open class PaymentAccountPresenter( + private val settingsRepository: SettingsRepository, + mainPresenter: MainPresenter +) : BasePresenter(mainPresenter), IPaymentAccountSettingsPresenter { + + private val _accounts = MutableStateFlow(listOf()) + override val accounts: StateFlow> = _accounts + + private val _selectedAccount = MutableStateFlow(_accounts.value.firstOrNull() ?: PaymentAccount(id= "0", name= "", description = "")) + override val selectedAccount: StateFlow = _selectedAccount + + override fun selectAccount(account: PaymentAccount) { + _selectedAccount.value = _accounts.value.firstOrNull { it.id == account.id } + ?: account + } + + override fun addAccount(newName: String, newDescription: String) { + val newAccount = PaymentAccount( + id = _accounts.value.count().toString(), + name = newName, + description = newDescription + ) + + val updatedAccounts = _accounts.value.toMutableList().apply { + add(newAccount) + } + _accounts.value = updatedAccounts + _selectedAccount.value = newAccount + } + + override fun saveAccount(newName: String, newDescription: String) { + val updatedAccounts = _accounts.value.map { + if (it.id == _selectedAccount.value.id) { + it.copy(name = newName, description = newDescription) + } else it + } + _accounts.value = updatedAccounts + _selectedAccount.value = updatedAccounts.first { it.id == _selectedAccount.value.id } + } + + override fun deleteCurrentAccount() { + val updatedAccounts = _accounts.value.toMutableList() + updatedAccounts.remove(_selectedAccount.value) + _accounts.value = updatedAccounts + _selectedAccount.value = updatedAccounts.firstOrNull() ?: PaymentAccount("0", "", "") + } + +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountSettingsScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountSettingsScreen.kt index e9dcd2e4..4837197b 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountSettingsScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/PaymentAccountSettingsScreen.kt @@ -1,24 +1,161 @@ package network.bisq.mobile.presentation.ui.uicases.settings -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.sp -import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle +import androidx.compose.ui.unit.dp +import cafe.adriel.lyricist.LocalStrings +import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.presentation.ViewPresenter +import network.bisq.mobile.presentation.ui.components.atoms.BisqButton +import network.bisq.mobile.presentation.ui.components.atoms.BisqEditableDropDown +import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField +import network.bisq.mobile.presentation.ui.components.molecules.BisqBottomSheet +import network.bisq.mobile.presentation.ui.components.molecules.ConfirmationDialog +import network.bisq.mobile.presentation.ui.components.organisms.settings.AppPaymentAccountCard +import network.bisq.mobile.presentation.ui.composeModels.PaymentAccount import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject +interface IPaymentAccountSettingsPresenter : ViewPresenter { + val accounts: StateFlow> + val selectedAccount: StateFlow + + fun selectAccount(account: PaymentAccount) + + fun addAccount(newName: String, newDescription: String) + fun saveAccount(newName: String, newDescription: String) + fun deleteCurrentAccount() +} + +// TODO: Toast messages @Composable fun PaymentAccountSettingsScreen() { -// RememberPresenterLifecycle() - Column(modifier = Modifier.fillMaxSize()) { - Text( - text = "PaymentAccountSettingsScreen", - style = MaterialTheme.typography.bodyLarge.copy(color = BisqTheme.colors.light1 , fontSize = 16.sp), - modifier = Modifier.weight(1f) + val strings = LocalStrings.current.user + val stringsCommon = LocalStrings.current.common + + val presenter: IPaymentAccountSettingsPresenter = koinInject() + val accounts by presenter.accounts.collectAsState() + val selectedAccount by presenter.selectedAccount.collectAsState() + + var accountName by remember { mutableStateOf(selectedAccount.name) } + var accountDescription by remember { mutableStateOf(selectedAccount.description) } + + var showConfirmationDialog by remember { mutableStateOf(false) } + var showBottomSheet by remember { mutableStateOf(false) } + + LaunchedEffect(selectedAccount) { + accountName = selectedAccount.name + accountDescription = selectedAccount.description + } + + if (showBottomSheet) { + BisqBottomSheet( + onDismissRequest = { showBottomSheet = !showBottomSheet } + ) { + AppPaymentAccountCard( + onCancel = { showBottomSheet = false }, + onConfirm = { name, description -> + presenter.addAccount(name, description) + showBottomSheet = false + }, + ) + } + } + + if (accounts.isEmpty()) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + BisqButton( + text = strings.user_paymentAccounts_createAccount, + onClick = { showBottomSheet = !showBottomSheet }, + modifier = Modifier.padding(all = 8.dp) + ) + } + return + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding) + ) { + BisqButton( + text = strings.user_paymentAccounts_createAccount, + onClick = { showBottomSheet = !showBottomSheet }, + padding = PaddingValues(horizontal = 18.dp, vertical = 6.dp), + modifier = Modifier.align(Alignment.End) + ) + + BisqEditableDropDown( + value = accountName, + onValueChanged = { name -> + var account = accounts.firstOrNull { it.name == name } + + if (account == null) { + account = accounts.firstOrNull { it.description == accountDescription } + + if (account != null) { + account = PaymentAccount(id = account.id, name = name, description = account.description) + } + } + + if (account == null) { + account = + PaymentAccount(id = accounts.count().toString(), name = name, description = accountDescription) + } + + presenter.selectAccount(account) + accountName = account.name + accountDescription = account.description + }, + items = accounts.map { it.name }, + label = strings.user_userProfile_payment_account + ) + + BisqTextField( + value = accountDescription, + onValueChanged = { accountDescription = it }, + label = strings.user_paymentAccounts_accountData, + isTextArea = true + ) + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + BisqButton( + text = stringsCommon.delete_account, + backgroundColor = BisqTheme.colors.dark5, + onClick = { showConfirmationDialog = true } + ) + BisqButton( + text = stringsCommon.buttons_save, + onClick = { + presenter.saveAccount(accountName, accountDescription) + } + ) + } + } + + if (showConfirmationDialog) { + ConfirmationDialog( + onConfirm = { + presenter.deleteCurrentAccount() + accountName = presenter.selectedAccount.value.name + accountDescription = presenter.selectedAccount.value.description + showConfirmationDialog = false + }, + onDismissRequest = { + showConfirmationDialog = false + } ) } -} \ No newline at end of file + +} + diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/TradeFlowScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/TradeFlowScreen.kt index 75d0ff3f..a7d067f5 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/TradeFlowScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/TradeFlowScreen.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.domain.data.model.OfferListItem import network.bisq.mobile.presentation.ViewPresenter import network.bisq.mobile.presentation.ui.components.atoms.* +import network.bisq.mobile.presentation.ui.components.atoms.layout.* import network.bisq.mobile.presentation.ui.components.layout.BisqStaticScaffold import network.bisq.mobile.presentation.ui.components.molecules.BisqDialog import network.bisq.mobile.presentation.ui.components.molecules.TopBar