diff --git a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt index ecaa720c..cbf5c815 100644 --- a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt +++ b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt @@ -43,5 +43,5 @@ val androidNodeModule = module { // and binding the same obj to 2 different abstractions single { NodeMainPresenter(get(), get(), get(), get(), get(), get()) } bind AppPresenter::class - single { OnBoardingNodePresenter(get()) } bind IOnboardingPresenter::class + single { OnBoardingNodePresenter(get(), get()) } bind IOnboardingPresenter::class } \ No newline at end of file diff --git a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/OnBoardingNodePresenter.kt b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/OnBoardingNodePresenter.kt index 3d2558a6..7c6e044d 100644 --- a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/OnBoardingNodePresenter.kt +++ b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/OnBoardingNodePresenter.kt @@ -1,11 +1,12 @@ package network.bisq.mobile.android.node.presentation +import network.bisq.mobile.domain.data.repository.SettingsRepository import network.bisq.mobile.presentation.MainPresenter import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter import network.bisq.mobile.presentation.ui.uicases.startup.OnBoardingPresenter class OnBoardingNodePresenter( - mainPresenter: MainPresenter -) : OnBoardingPresenter(mainPresenter), IOnboardingPresenter { + mainPresenter: MainPresenter, settingsRepository: SettingsRepository +) : OnBoardingPresenter(mainPresenter, settingsRepository), IOnboardingPresenter { override val indexesToShow = listOf(1, 2) } diff --git a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt index f71cc18c..21bdfd64 100644 --- a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt +++ b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt @@ -6,4 +6,6 @@ import kotlinx.serialization.Serializable open class Settings : BaseModel() { open var bisqApiUrl: String = "" open var isConnected: Boolean = false + + open var firstLaunch: Boolean = true } \ No newline at end of file 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 new file mode 100644 index 00000000..ff6fc095 --- /dev/null +++ b/shared/presentation/src/androidMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.android.kt @@ -0,0 +1,8 @@ +package network.bisq.mobile.presentation.ui.helpers + +import java.text.DecimalFormat + +actual val numberFormatter: NumberFormatter = object : NumberFormatter { + private val formatter = DecimalFormat("#.########") + override fun satsFormat(value: Double): String = formatter.format(value) +} diff --git a/shared/presentation/src/commonMain/composeResources/drawable/icon_info.png b/shared/presentation/src/commonMain/composeResources/drawable/icon_info.png new file mode 100644 index 00000000..6c304de8 Binary files /dev/null and b/shared/presentation/src/commonMain/composeResources/drawable/icon_info.png differ diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStrings.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStrings.kt index f9000db2..9d3f2e16 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStrings.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStrings.kt @@ -29,7 +29,7 @@ data class BisqEasyTradeWizardStrings( val bisqEasy_price_percentage_title: String, val bisqEasy_price_percentage_inputBoxText: String, val bisqEasy_price_tradePrice_title: String, - val bisqEasy_price_tradePrice_inputBoxText: String, + val bisqEasy_price_tradePrice_inputBoxText: (String) -> String, val bisqEasy_price_feedback_sentence: String, val bisqEasy_price_feedback_sentence_veryLow: String, val bisqEasy_price_feedback_sentence_low: String, @@ -85,8 +85,8 @@ data class BisqEasyTradeWizardStrings( val bisqEasy_tradeWizard_amount_buyer_numSellers_many: String, val bisqEasy_tradeWizard_amount_numOffers_0: String, val bisqEasy_tradeWizard_amount_numOffers_1: String, - val bisqEasy_tradeWizard_amount_numOffers_many: String, - val bisqEasy_tradeWizard_amount_buyer_limitInfo: String, + val bisqEasy_tradeWizard_amount_numOffers_many: (String) -> String, + val bisqEasy_tradeWizard_amount_buyer_limitInfo: (String, String) -> String, val bisqEasy_tradeWizard_amount_buyer_limitInfo_overlay_info: String, val bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info_leadLine: String, val bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info: String, @@ -171,4 +171,14 @@ data class BisqEasyTradeWizardStrings( val bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_sell: String, val bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_buy: String, val bisqEasy_tradeWizard_review_chatMessage_myMessageTitle: String, + + // Mobile app specific + val bisqEasy_tradeWizard_buy_description: String, + val bisqEasy_tradeWizard_sell_description: String, + val bisqEasy_tradeWizard_trade_amount: String, + val bisqEasy_tradeWizard_fixed_amount: String, + val bisqEasy_tradeWizard_range_amount: String, + val bisqEasy_tradeWizard_trade_price_percentage: String, + val bisqEasy_tradeWizard_trade_price_fixed: String, + ) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsEn.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsEn.kt index 41b0f3ac..0265e423 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsEn.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsEn.kt @@ -30,7 +30,7 @@ val EnBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_price_percentage_title = "Percentage price", bisqEasy_price_percentage_inputBoxText = "Market price percentage between -10% and 50%", bisqEasy_price_tradePrice_title = "Fixed price", - bisqEasy_price_tradePrice_inputBoxText = "Trade price in {0}", + bisqEasy_price_tradePrice_inputBoxText = { currency -> "Trade price in $currency" }, bisqEasy_price_feedback_sentence = "Your offer has {0} chances to be taken at this price.", bisqEasy_price_feedback_sentence_veryLow = "very low", bisqEasy_price_feedback_sentence_low = "low", @@ -86,8 +86,8 @@ val EnBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_tradeWizard_amount_buyer_numSellers_many = "are {0} sellers", bisqEasy_tradeWizard_amount_numOffers_0 = "is no offer", bisqEasy_tradeWizard_amount_numOffers_1 = "is one offer", - bisqEasy_tradeWizard_amount_numOffers_many = "are {0} offers", - bisqEasy_tradeWizard_amount_buyer_limitInfo = "There {0} in the network with sufficient reputation to take an offer of {1}.", + bisqEasy_tradeWizard_amount_numOffers_many = { count -> "are $count offers" }, + bisqEasy_tradeWizard_amount_buyer_limitInfo = { sellers, currency -> "There $sellers in the network with sufficient reputation to take an offer of $currency." }, bisqEasy_tradeWizard_amount_buyer_limitInfo_overlay_info = "A seller who wants to take your offer of {0}, must have a reputation score of at least {1}.\n By reducing the maximum trade amount, you make your offer accessible to more sellers.", bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info_leadLine = "There {0} matching the chosen trade amount.", bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info = "For offers up to {0}, reputation requirements are relaxed.", @@ -172,4 +172,14 @@ val EnBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_sell = "Sell Bitcoin to", bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_buy = "Buy Bitcoin from", bisqEasy_tradeWizard_review_chatMessage_myMessageTitle = "My Offer to {0} Bitcoin", + + bisqEasy_tradeWizard_buy_description = "The easiest way to get your first Bitcoin", + bisqEasy_tradeWizard_sell_description = "Experienced Bisq users with reputation can act as seller", + + bisqEasy_tradeWizard_trade_amount = "Trade amount", + bisqEasy_tradeWizard_fixed_amount = "Fixed amount", + bisqEasy_tradeWizard_range_amount = "Range amount", + + bisqEasy_tradeWizard_trade_price_percentage = "Percentage", + bisqEasy_tradeWizard_trade_price_fixed = "Fixed", ) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsFr.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsFr.kt index c3abf74f..08c8fbef 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsFr.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/BisqEasyTradeWizardStringsFr.kt @@ -27,7 +27,7 @@ val FrBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_price_percentage_title = "[FR] Percentage price", bisqEasy_price_percentage_inputBoxText = "[FR] Market price percentage between -10% and 50%", bisqEasy_price_tradePrice_title = "[FR] Fixed price", - bisqEasy_price_tradePrice_inputBoxText = "[FR] Trade price in {0}", + bisqEasy_price_tradePrice_inputBoxText = { currency -> "[FR] Trade price in $currency" }, bisqEasy_price_feedback_sentence = "[FR] Your offer has {0} chances to be taken at this price.", bisqEasy_price_feedback_sentence_veryLow = "[FR] very low", bisqEasy_price_feedback_sentence_low = "[FR] low", @@ -83,8 +83,8 @@ val FrBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_tradeWizard_amount_buyer_numSellers_many = "[FR] are {0} sellers", bisqEasy_tradeWizard_amount_numOffers_0 = "[FR] is no offer", bisqEasy_tradeWizard_amount_numOffers_1 = "[FR] is one offer", - bisqEasy_tradeWizard_amount_numOffers_many = "[FR] are {0} offers", - bisqEasy_tradeWizard_amount_buyer_limitInfo = "[FR] There {0} in the network with sufficient reputation to take an offer of {1}.", + bisqEasy_tradeWizard_amount_numOffers_many = { count -> "[FR] are $count offers" }, + bisqEasy_tradeWizard_amount_buyer_limitInfo = { sellers, currency -> "[FR] There $sellers in the network with sufficient reputation to take an offer of $currency."}, bisqEasy_tradeWizard_amount_buyer_limitInfo_overlay_info = "[FR] A seller who wants to take your offer of {0}, must have a reputation score of at least {1}.\n By reducing the maximum trade amount, you make your offer accessible to more sellers.", bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info_leadLine = "[FR] There {0} matching the chosen trade amount.", bisqEasy_tradeWizard_amount_buyer_limitInfo_wizard_info = "[FR] For offers up to {0}, reputation requirements are relaxed.", @@ -169,4 +169,14 @@ val FrBisqEasyTradeWizardStrings = BisqEasyTradeWizardStrings( bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_sell = "[FR] Sell Bitcoin to", bisqEasy_tradeWizard_review_chatMessage_peerMessageTitle_buy = "[FR] Buy Bitcoin from", bisqEasy_tradeWizard_review_chatMessage_myMessageTitle = "[FR] My Offer to {0} Bitcoin", + + bisqEasy_tradeWizard_buy_description = "[FR] The easiest way to get your first Bitcoin", + bisqEasy_tradeWizard_sell_description = "[FR] Experienced Bisq users with reputation can act as seller", + + bisqEasy_tradeWizard_trade_amount= "[FR] Trade amount", + bisqEasy_tradeWizard_fixed_amount= "[FR] Fixed amount", + bisqEasy_tradeWizard_range_amount= "[FR] Range amount", + + bisqEasy_tradeWizard_trade_price_percentage = "[FR] Percentage", + bisqEasy_tradeWizard_trade_price_fixed = "[FR] Fixed", ) 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 972e884c..243530d9 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 @@ -13,4 +13,5 @@ data class CommonStrings( val offers_list_sell_to: String, val take_offer: String, + val currency: 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 9d8c437e..5eb065f0 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 @@ -16,5 +16,6 @@ val EnCommonStrings = CommonStrings( offers_list_sell_to = "Sell to", take_offer = "Take offer", + currency = "Currency", ) \ 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 4746c273..f101e221 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 @@ -16,5 +16,6 @@ val FrCommonStrings = CommonStrings( offers_list_buy_from = "[FR] Buy from", offers_list_sell_to = "[FR] Sell to", - take_offer = "[FR] Take offer" + take_offer = "[FR] Take offer", + currency = "[FR] Currency" ) \ No newline at end of file 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 e6ffb1d5..371e463b 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 @@ -17,6 +17,9 @@ import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.ITakeOfferTr import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.PaymentMethodPresenter import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.ReviewTradePresenter 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.startup.CreateProfilePresenter import network.bisq.mobile.presentation.ui.uicases.startup.IOnboardingPresenter import network.bisq.mobile.presentation.ui.uicases.startup.ITrustedNodeSetupPresenter @@ -43,11 +46,12 @@ val presentationModule = module { SplashPresenter( get(), get(), - get() + get(), + get(), ) } - single { OnBoardingPresenter(get()) } bind IOnboardingPresenter::class + single { OnBoardingPresenter(get(), get()) } bind IOnboardingPresenter::class single { GettingStartedPresenter( @@ -91,4 +95,6 @@ val presentationModule = module { } bind IMyTrades::class single{ TradeFlowPresenter(get(), get()) } bind ITradeFlowPresenter::class + + single{ CreateOfferPresenter(get(), get()) } bind ICreateOfferPresenter::class } \ No newline at end of file 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/CurrencyProfileCard.kt index 492fca21..b54460e4 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/CurrencyProfileCard.kt @@ -1,5 +1,6 @@ package network.bisq.mobile.presentation.ui.components +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -16,7 +17,11 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +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 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 @@ -25,17 +30,41 @@ import network.bisq.mobile.presentation.ui.theme.BisqTheme @Composable fun CurrencyProfileCard( item: MarketListItem, + isSelected: Boolean = false, onClick: () -> Unit ) { val interactionSource = remember { MutableInteractionSource() } val numOffers = item.numOffers.collectAsState().value + val highLightColor = BisqTheme.colors.primary + + val backgroundColor = if (isSelected) { + BisqTheme.colors.secondary + } else { + BisqTheme.colors.backgroundColor + } Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .fillMaxWidth() - .padding(vertical = 4.dp) + .background(backgroundColor) + .then( + if (isSelected) { + Modifier.drawBehind { + drawLine( + color = highLightColor, + start = Offset(0f, size.height), + end = Offset(0f, 0f), + strokeWidth = 2.dp.toPx() + ) + } + } else { + Modifier.background(Color.Transparent) + } + ) + .padding(horizontal = 4.dp, vertical = (16/LocalDensity.current.density).dp) + // .padding(vertical = 4.dp) .clickable( interactionSource = interactionSource, indication = null, @@ -44,6 +73,7 @@ fun CurrencyProfileCard( ) { Row( verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) ) { // 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 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 f31adab6..66548de0 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 @@ -28,6 +28,7 @@ fun BisqButton( iconOnly: (@Composable () -> Unit)? = null, leftIcon: (@Composable () -> Unit)? = null, rightIcon: (@Composable () -> Unit)? = null, + textComponent: (@Composable () -> Unit)? = null, modifier: Modifier = Modifier, cornerRadius: Dp = 8.dp, disabled: Boolean = false, @@ -44,10 +45,11 @@ fun BisqButton( disabledContentColor = color), shape = RoundedCornerShape(cornerRadius), enabled = !disabled, - border = border + border = border, + modifier = modifier ) { - if (iconOnly == null && text == null) { - BisqText.baseMedium("Error: Pass either text or icon") + if (iconOnly == null && text == null && textComponent == null) { + BisqText.baseMedium("Error: Pass either text or customText or icon") } if (iconOnly != null) { @@ -56,10 +58,14 @@ fun BisqButton( Row { if(leftIcon != null) leftIcon() if(leftIcon != null) Spacer(modifier = Modifier.width(10.dp)) - BisqText.baseMedium( - text = text, - color = BisqTheme.colors.light1, - ) + 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() } 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/Gap.kt new file mode 100644 index 00000000..5580167f --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Gap.kt @@ -0,0 +1,38 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants + +object BisqGap { + + @Composable() + fun V1() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + } + + @Composable() + fun V2() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + } + + @Composable() + fun V3() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding3X)) + } + + @Composable() + fun V4() { + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding4X)) + } + + @Composable() + fun H1() { + Spacer(modifier = Modifier.width(BisqUIConstants.ScreenPadding)) + } + +} + diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/NoteText.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/NoteText.kt new file mode 100644 index 00000000..a3a12b14 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/NoteText.kt @@ -0,0 +1,53 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink +import androidx.compose.ui.text.withStyle +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.theme.BisqTheme +import org.jetbrains.compose.resources.Font + +@Composable +fun NoteText( + notes: String, + linkText: String, + uri: String = "https://bisq.network/", + textAlign: TextAlign = TextAlign.Start +) { + val uriHandler = LocalUriHandler.current + val annotatedString = buildAnnotatedString { + append(notes) + withStyle( + style = SpanStyle( + color = BisqTheme.colors.primary, + textDecoration = TextDecoration.Underline + ) + ) { + val link = + LinkAnnotation.Url( + uri, + ) { + val url = (it as LinkAnnotation.Url).url + uriHandler.openUri(url) + } + withLink(link) { append(linkText) } + } + + } + Text( + text = annotatedString, + color = BisqTheme.colors.grey3, + fontSize = 12.sp, + textAlign = textAlign, + ) +} \ No newline at end of file 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 27809acd..df60f130 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 @@ -14,9 +14,11 @@ 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.graphics.Color import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import network.bisq.mobile.presentation.ui.theme.BisqTheme @@ -28,7 +30,10 @@ fun BisqTextField( onValueChanged: (String) -> Unit, placeholder: String? = null, labelRightSuffix: (@Composable () -> Unit)? = null, + prefix: (@Composable () -> Unit)? = null, + suffix: (@Composable () -> Unit)? = null, disabled: Boolean = false, + indicatorColor: Color = BisqTheme.colors.primary, modifier: Modifier = Modifier, ) { var isFocused by remember { mutableStateOf(false) } @@ -53,6 +58,16 @@ fun BisqTextField( .fillMaxWidth() .clip(shape = RoundedCornerShape(6.dp)) .background(color = BisqTheme.colors.secondary) + .drawBehind { + if (isFocused || value.isNotEmpty()) { + drawLine( + color = indicatorColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = 4.dp.toPx() + ) + } + } ) { TextField( value = value, @@ -63,6 +78,8 @@ fun BisqTextField( }, textStyle = TextStyle(fontSize = 22.sp), onValueChange = onValueChanged, + prefix = prefix, + suffix = suffix, colors = TextFieldDefaults.colors( focusedTextColor = BisqTheme.colors.light3, unfocusedTextColor = BisqTheme.colors.secondaryHover, 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 5dfe2abc..5d4f0e6c 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 @@ -28,6 +28,11 @@ fun CopyIcon(modifier: Modifier = Modifier) { Image(painterResource(Res.drawable.icon_copy), "Copy icon", modifier = modifier) } +@Composable +fun InfoIcon(modifier: Modifier = Modifier.size(16.dp)) { + Image(painterResource(Res.drawable.icon_info), "Info icon", modifier = modifier) +} + @Composable fun SwapHArrowIcon(modifier: Modifier = Modifier.size(16.dp)) { Image(painterResource(Res.drawable.exchange_h_arrow), "Swap horizontal icon", modifier = modifier) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/MultiScreenWizardScaffold.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/MultiScreenWizardScaffold.kt index 199f50b4..ba4caa7c 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/MultiScreenWizardScaffold.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/MultiScreenWizardScaffold.kt @@ -5,6 +5,7 @@ import androidx.compose.material3.BottomAppBar import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings import network.bisq.mobile.presentation.ui.components.atoms.* @@ -21,14 +22,45 @@ fun MultiScreenWizardScaffold( nextButtonText: String = LocalStrings.current.common.buttons_next, prevOnClick: (() -> Unit)? = null, nextOnClick: (() -> Unit)? = null, + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + useStaticScaffold: Boolean = false, content: @Composable ColumnScope.() -> Unit ) { - BisqScrollScaffold( - padding = PaddingValues(all = 0.dp), - topBar = { + + val scaffold: @Composable ( + padding: PaddingValues, + topBar: @Composable (() -> Unit)?, + bottomBar: @Composable (() -> Unit)?, + hAlignment: Alignment.Horizontal, + vArrangement: Arrangement.Vertical, + content: @Composable ColumnScope.() -> Unit + ) -> Unit = + if (useStaticScaffold) { padding, topBar, bottomBar, hAlignment, verticalArrangement, innerContent -> + BisqStaticScaffold( + padding = padding, + topBar = topBar, + bottomBar = bottomBar, + horizontalAlignment = hAlignment, + verticalArrangement = verticalArrangement, + content = innerContent + ) + } else { padding, topBar, bottomBar, hAlignment, verticalArrangement, innerContent -> + BisqScrollScaffold( + padding = padding, + topBar = topBar, + bottomBar = bottomBar, + horizontalAlignment = hAlignment, + verticalArrangement = verticalArrangement, + content = innerContent + ) + } + + scaffold( + PaddingValues(all = 0.dp), + { TopBar(title, isFlowScreen = true, stepText = "$stepIndex/$stepsLength") }, - bottomBar = { + { // TODO: This takes up too much height BottomAppBar( containerColor = BisqTheme.colors.backgroundColor, @@ -63,26 +95,26 @@ fun MultiScreenWizardScaffold( } } - } + }, + horizontalAlignment, + Arrangement.Top, ) { - // TODO: Get correct full width - val screenSize = remember { mutableStateOf(320) } BisqProgressBar( - stepIndex.toFloat() * screenSize.value / stepsLength.toFloat(), + stepIndex.toFloat() / stepsLength.toFloat(), modifier = Modifier.fillMaxWidth().padding(top = 16.dp) ) // TODO: Should pass these values to the column deep inside BisqScrollLayout // as BissScrollScaffold's params, rather than creating a column here? - // 2X screen padding for Flow screens + Column( modifier = Modifier.fillMaxHeight().padding( - horizontal = BisqUIConstants.ScreenPadding2X, - vertical = BisqUIConstants.ScreenPadding2X + horizontal = BisqUIConstants.ScreenPadding, + vertical = BisqUIConstants.ScreenPadding ), + horizontalAlignment = horizontalAlignment, verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally ) { content() } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollScaffold.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollScaffold.kt index 18d9d22e..64a64ac4 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollScaffold.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollScaffold.kt @@ -3,6 +3,8 @@ package network.bisq.mobile.presentation.ui.components.layout import androidx.compose.foundation.layout.* import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import network.bisq.mobile.presentation.ui.theme.BisqTheme import network.bisq.mobile.presentation.ui.theme.BisqUIConstants @@ -17,6 +19,8 @@ fun BisqScrollScaffold( ), topBar: @Composable (() -> Unit)? = null, bottomBar: @Composable (() -> Unit)? = null, + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + verticalArrangement: Arrangement.Vertical = Arrangement.Top, content: @Composable ColumnScope.() -> Unit ) { Scaffold( @@ -24,7 +28,25 @@ fun BisqScrollScaffold( topBar = topBar ?: {}, bottomBar = bottomBar ?: {}, content = { - BisqScrollLayout(padding = if (topBar != null) it else padding) { content() } + BisqScrollLayout( + padding = if (topBar != null) it else padding, + verticalArrangement = verticalArrangement + ) { + // Padding logic: + // when topBar is set, Scaffold.content.it provides the padding + // to offset topBar height, which is passed to BisqStaticLayout + // But then the content()'s get attached to the screen edges. + // So in that case, we add another column to provde ScreenPadding on all sides. + if (topBar != null) + Column( + modifier = Modifier.padding(all = BisqUIConstants.ScreenPadding), + horizontalAlignment = horizontalAlignment + ) { + content() + } + else + content() + } }, ) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt index 76aacf0f..c4869f73 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt @@ -13,12 +13,13 @@ import network.bisq.mobile.presentation.ui.theme.BisqUIConstants @Composable fun BisqStaticLayout( padding: PaddingValues = PaddingValues(all = BisqUIConstants.ScreenPadding), + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, verticalArrangement: Arrangement.Vertical = Arrangement.SpaceBetween, content: @Composable ColumnScope.() -> Unit ) { Column( + horizontalAlignment = horizontalAlignment, verticalArrangement = verticalArrangement, - horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxSize() .background(color = BisqTheme.colors.backgroundColor) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticScaffold.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticScaffold.kt index a40c0b05..d98eba62 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticScaffold.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticScaffold.kt @@ -19,6 +19,7 @@ fun BisqStaticScaffold( ), topBar: @Composable (() -> Unit)? = null, bottomBar: @Composable (() -> Unit)? = null, + horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, verticalArrangement: Arrangement.Vertical = Arrangement.Top, content: @Composable ColumnScope.() -> Unit ) { @@ -29,6 +30,7 @@ fun BisqStaticScaffold( content = { it -> BisqStaticLayout( padding = if (topBar != null) it else padding, + horizontalAlignment = horizontalAlignment, verticalArrangement = verticalArrangement ) { // Padding logic: @@ -39,7 +41,7 @@ fun BisqStaticScaffold( if (topBar != null) Column( modifier = Modifier.padding(all = BisqUIConstants.ScreenPadding), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = horizontalAlignment ) { content() } 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 84ab0e41..698992f8 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,17 +1,19 @@ package network.bisq.mobile.presentation.ui.components.molecules +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.ExperimentalMaterial3Api +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 network.bisq.mobile.presentation.ui.components.atoms.BisqSlider -import network.bisq.mobile.presentation.ui.components.atoms.BisqText -import network.bisq.mobile.presentation.ui.components.atoms.SvgImage -import network.bisq.mobile.presentation.ui.components.atoms.SvgImageNames +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.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import kotlin.math.roundToInt @OptIn(ExperimentalMaterial3Api::class) @@ -20,7 +22,8 @@ fun BisqAmountSelector( minAmount: Float, maxAmount: Float, exchangeRate: Double, - currency: String + currency: String, + onValueChange: (Float) -> Unit ) { var sliderPosition by remember { mutableFloatStateOf((minAmount + maxAmount) * 0.5f) } val roundedNumber = (sliderPosition * 100).roundToInt() / 100.0 @@ -29,7 +32,18 @@ fun BisqAmountSelector( else roundedNumber.toString() // if it's 3.14, keep the same - val satsValue = (price.toDouble() / exchangeRate).toString() + 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) + } Column( verticalArrangement = Arrangement.Top @@ -40,7 +54,7 @@ fun BisqAmountSelector( horizontalAlignment = Alignment.CenterHorizontally ) { Row( - horizontalArrangement = Arrangement.spacedBy(24.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom ) { BisqText.h1Regular( @@ -52,18 +66,48 @@ fun BisqAmountSelector( } Row( horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.Top + verticalAlignment = Alignment.CenterVertically ) { - // TODO: Do the btc-sats display control, as suggested by cbeams - BisqText.h5Regular( - text = "$satsValue btc", - color = BisqTheme.colors.grey2 - ) - SvgImage( - image = SvgImageNames.INFO, - modifier = Modifier.size(16.dp), - colorFilter = ColorFilter.tint(BisqTheme.colors.grey2) + // 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() + } + } } } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentTypeCard.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentTypeCard.kt new file mode 100644 index 00000000..0ef38d36 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/PaymentTypeCard.kt @@ -0,0 +1,54 @@ +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.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +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.unit.dp +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun PaymentTypeCard( + image: String, + title: String, + onClick: (String) -> Unit, + isSelected: Boolean = false +) { + val backgroundColor = if (isSelected) { + BisqTheme.colors.primary + } else { + BisqTheme.colors.dark5 + } + + Row( + modifier = Modifier.fillMaxWidth() + .clip(shape = RoundedCornerShape(6.dp)) + .background(backgroundColor).padding(start = 18.dp) + .padding(vertical = 10.dp) + .clickable( + onClick = { onClick(title) }, + interactionSource = remember { MutableInteractionSource() }, + indication = null + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + DynamicImage( + path = image, + modifier = Modifier.size(20.dp) + ) + BisqText.baseRegular( + text = title + ) + } +} \ No newline at end of file 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/RageSlider.kt new file mode 100644 index 00000000..04bf1d90 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RageSlider.kt @@ -0,0 +1,69 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.RangeSlider +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BisqRangeSlider( + minAmount: Float, + maxAmount: Float, + tradeValue: ClosedFloatingPointRange, + onValueChange: (ClosedFloatingPointRange) -> Unit +){ + val colors = SliderColors( + thumbColor = BisqTheme.colors.primary, + activeTrackColor = BisqTheme.colors.grey2, + activeTickColor = Color.Unspecified, + inactiveTrackColor = BisqTheme.colors.grey2, + inactiveTickColor = Color.Unspecified, + disabledThumbColor = Color.Unspecified, + disabledActiveTrackColor = Color.Unspecified, + disabledActiveTickColor = Color.Unspecified, + disabledInactiveTrackColor = Color.Unspecified, + disabledInactiveTickColor = Color.Unspecified + ) + + RangeSlider( + modifier = Modifier.fillMaxWidth(), + value = tradeValue, + onValueChange = {onValueChange(it)}, + valueRange = minAmount .. maxAmount, + track = { rangeSliderState -> + SliderDefaults.Track( + trackInsideCornerSize = 0.dp, + thumbTrackGapSize = 0.dp, + modifier = Modifier.height(2.dp), + rangeSliderState = rangeSliderState , + colors = colors, + drawStopIndicator = null + ) + }, + startThumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + thumbSize = DpSize(16.dp, 16.dp), + colors = colors + ) + }, + endThumb = { + SliderDefaults.Thumb( + interactionSource = remember { MutableInteractionSource() }, + thumbSize = DpSize(16.dp, 16.dp), + colors = colors + ) + } + ) +} \ No newline at end of file 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 new file mode 100644 index 00000000..3438918d --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/RangeAmountSelector.kt @@ -0,0 +1,142 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.ExperimentalMaterial3Api +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 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.SvgImage +import network.bisq.mobile.presentation.ui.components.atoms.SvgImageNames +import network.bisq.mobile.presentation.ui.helpers.numberFormatter +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import kotlin.math.roundToInt + +@OptIn(ExperimentalMaterial3Api::class) +// TODO: This has more work to do +@Composable +fun RangeAmountSelector( + minAmount: Float, + maxAmount: Float, +) { + var sliderPosition by remember { mutableStateOf(minAmount .. maxAmount) } + var tradeValue = 873f..1200f + Column { + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Column(horizontalAlignment = Alignment.Start) { + BisqText.xsmallRegular( + text = "Min", + color = BisqTheme.colors.grey1 + ) + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.Bottom + ) { + BisqText.h5Regular( + text = sliderPosition.start.toString() + ) + BisqText.xsmallRegular( + text = "USD" + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + DynamicImage( + "drawable/bitcoin.png", + modifier = Modifier.size(16.dp) + ) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + BisqText.smallRegular( + text = "0.00", + color = BisqTheme.colors.grey2 + ) + BisqText.smallRegular( + text = "273 116 sats", + ) + } + SvgImage( + image = SvgImageNames.INFO, + modifier = Modifier.size(16.dp), + colorFilter = ColorFilter.tint(BisqTheme.colors.grey2) + ) + } + + } + Column(horizontalAlignment = Alignment.End) { + BisqText.xsmallRegular( + text = "Max", + color = BisqTheme.colors.grey1 + ) + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.Bottom + ) { + BisqText.h5Regular( + text = sliderPosition.endInclusive.toString() + ) + BisqText.xsmallRegular( + text = "USD" + ) + } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + DynamicImage( + "drawable/bitcoin.png", + modifier = Modifier.size(16.dp) + ) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + + BisqText.smallRegular( + text = "0.00", + color = BisqTheme.colors.grey2 + ) + BisqText.smallRegular( + text = "273 116 sats", + ) + } + SvgImage( + image = SvgImageNames.INFO, + modifier = Modifier.size(16.dp), + colorFilter = ColorFilter.tint(BisqTheme.colors.grey2) + ) + } + + } + + } + Column { + BisqRangeSlider( + minAmount, + maxAmount, + sliderPosition, + onValueChange = { + sliderPosition = it + } + ) + 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 USD", + color = BisqTheme.colors.grey2 + ) + BisqText.smallRegular( + text = "Max $maxString USD", + 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/ToggleTab.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ToggleTab.kt new file mode 100644 index 00000000..57c06c25 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/ToggleTab.kt @@ -0,0 +1,98 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +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.alpha +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun ToggleTab( + options: List, + initialOption: T, + onStateChange: (T) -> Unit, + getDisplayString: (T) -> String,// Custom function to display the label for each option + textWidth: Dp = 0.dp, + modifier: Modifier = Modifier +) { + var selectedOption by remember { mutableStateOf(initialOption) } + + val slideOffset by animateDpAsState( + targetValue = if (selectedOption == options[0]) 0.dp else textWidth + 64.dp, + animationSpec = tween(durationMillis = 300) + ) + + Surface( + shape = RoundedCornerShape(6.dp), + modifier = Modifier.wrapContentSize().then( + other = modifier + ) + ) { + Box( + modifier = Modifier + .background(BisqTheme.colors.dark5) + .padding(6.dp) + ) { + Box( + modifier = Modifier + .offset(x = slideOffset) + .background(BisqTheme.colors.primary, RoundedCornerShape(4.dp)) + ) { + BisqText.baseMedium( + text = getDisplayString(selectedOption), + color = BisqTheme.colors.light1, + modifier = Modifier + .padding(horizontal = 32.dp, vertical = 12.dp) + .width(textWidth) + .alpha(0f), + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + options.forEach { option -> + Box( + modifier = Modifier + .padding(horizontal = 32.dp, vertical = 12.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { + selectedOption = option + onStateChange(selectedOption) + } + ) + ) { + BisqText.baseMedium( + text = getDisplayString(option), + color = BisqTheme.colors.light1, + textAlign = TextAlign.Center, + modifier = Modifier.width(textWidth) + ) + } + } + } + } + } +} diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/PaymentMethodCard.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/PaymentMethodCard.kt new file mode 100644 index 00000000..778bdaf4 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/organisms/PaymentMethodCard.kt @@ -0,0 +1,51 @@ +package network.bisq.mobile.presentation.ui.components.organisms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.atoms.PaymentTypeCard +import network.bisq.mobile.presentation.ui.composeModels.PaymentTypeData +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.ICreateOfferPresenter +import org.koin.compose.koinInject + +@Composable +fun PaymentMethodCard( + paymentMethodTitle: String, + paymentTypeList: List +) { + val presenter: ICreateOfferPresenter = koinInject() + val state by presenter.state.collectAsState() + Column( + modifier = Modifier.padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + BisqText.largeLight( + text = paymentMethodTitle, + color = BisqTheme.colors.grey2 + ) + Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 38.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + repeat(paymentTypeList.size) { index -> + PaymentTypeCard( + image = paymentTypeList[index].image, + title = paymentTypeList[index].title, + onClick = { presenter.onSelectPayment(it) }, + isSelected = paymentTypeList[index].title == state.paymentMethod + ) + } + } + } +} \ 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 3bcee449..69681794 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 @@ -6,9 +6,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.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.components.atoms.* import network.bisq.mobile.presentation.ui.theme.BisqUIConstants import network.bisq.mobile.presentation.ui.uicases.trades.ITradeFlowPresenter import org.koin.compose.koinInject @@ -25,19 +23,19 @@ fun TradeFlowAccountDetails( val presenter: ITradeFlowPresenter = koinInject() Column { - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.baseRegular(text = strings.bisqEasy_tradeState_info_buyer_phase1a_seller_wait_message) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() BisqText.h6Regular( text = strings.bisqEasy_tradeState_info_buyer_phase1a_bitcoinPayment_headline_LN ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqTextField( label = strings.bisqEasy_tradeState_info_buyer_phase1a_bitcoinPayment_description_LN, value = presenter.receiveAddress.collectAsState().value, onValueChanged = { presenter.setReceiveAddress(it) }, ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqButton( text = strings.bisqEasy_tradeState_info_buyer_phase1a_send, onClick = onNext, @@ -46,7 +44,7 @@ fun TradeFlowAccountDetails( vertical = 6.dp ) ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.smallMedium( text = strings.bisqEasy_tradeState_info_buyer_phase1a_wallet_prompt_prefix ) 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 743db543..433cba5e 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,6 +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.BisqText import network.bisq.mobile.presentation.ui.components.atoms.CircularLoadingImage import network.bisq.mobile.presentation.ui.theme.BisqTheme @@ -39,7 +40,7 @@ fun TradeFlowBtcPayment( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() CircularLoadingImage( image = Res.drawable.img_bitcoin_payment_confirmation, isLoading = !isLoading @@ -76,7 +77,7 @@ fun ShowLoaderBtcPayment( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() CircularLoadingImage( image = Res.drawable.img_bitcoin_payment_waiting, isLoading = true 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 772efdef..9816aa19 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,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.BisqButton +import network.bisq.mobile.presentation.ui.components.atoms.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 @@ -27,23 +28,23 @@ fun TradeFlowCompleted( val btcValue = "0.00173399 BTC" Column { - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.h6Regular( text = strings.bisqEasy_tradeCompleted_title ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqTextField( value = sendAmount, onValueChanged = {}, label = strings.bisqEasy_tradeCompleted_body_you_have_receveid, ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqTextField( value = btcValue, onValueChanged = {}, label = strings.bisqEasy_tradeCompleted_body_you_have_sold ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, 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 c6671ffb..bd3dff54 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 @@ -12,10 +12,7 @@ import bisqapps.shared.presentation.generated.resources.Res 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.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.components.atoms.CircularLoadingImage +import network.bisq.mobile.presentation.ui.components.atoms.* 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 @@ -41,11 +38,11 @@ fun TradeFlowFiatPayment( } Column { - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.h6Regular( text = strings.bisqEasy_tradeState_info_buyer_phase2a_headline(sendAmount) ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqTextField( value = sendAmount, onValueChanged = {}, @@ -58,12 +55,12 @@ fun TradeFlowFiatPayment( label = strings.bisqEasy_tradeState_info_buyer_phase2a_sellersAccount, disabled = true, ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.smallRegular( text = strings.bisqEasy_tradeState_info_buyer_phase2a_reasonForPaymentInfo, color = BisqTheme.colors.grey1 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqButton( text = strings.bisqEasy_tradeState_info_buyer_phase2a_confirmFiatSent(sendAmount), onClick = { presenter.confirmFiatPayment() }, @@ -88,7 +85,7 @@ fun ShowLoaderFiatPayment( onNext() } - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) 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 9edf3267..e907c8ad 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,6 +19,7 @@ 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.BisqText import network.bisq.mobile.presentation.ui.components.atoms.ProfileRating import network.bisq.mobile.presentation.ui.components.atoms.icons.UpIcon @@ -101,7 +102,7 @@ fun TradeHeader( value2 = "29 Sep 2024", ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() InfoRow( style = InfoBoxStyle.Style2, @@ -111,7 +112,7 @@ fun TradeHeader( value2 = "9567056.04 USD/BTC", ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() InfoRow( style = InfoBoxStyle.Style2, 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 d1d4f1b9..d064d2e9 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 @@ -3,4 +3,6 @@ package network.bisq.mobile.presentation.ui.composeModels import org.jetbrains.compose.resources.DrawableResource data class BottomNavigationItem(val title: String, val route: String, val icon: DrawableResource) -data class PagerViewItem(val title: String, val image: DrawableResource, val desc: String) \ No newline at end of file +data class PagerViewItem(val title: String, val image: DrawableResource, val desc: String) + +data class PaymentTypeData(val image: String, val title: String) 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 new file mode 100644 index 00000000..464098c9 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.kt @@ -0,0 +1,7 @@ +package network.bisq.mobile.presentation.ui.helpers + +interface NumberFormatter { + fun satsFormat(value: Double): String +} + +expect val numberFormatter: NumberFormatter 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 new file mode 100644 index 00000000..e7355016 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/helpers/StringHelper.kt @@ -0,0 +1,23 @@ +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.unit.Dp +import androidx.compose.ui.unit.sp + +object StringHelper { + @Composable + fun calculateTotalWidthOfStrings(strings: List): Dp { + val textMeasurer: TextMeasurer = rememberTextMeasurer() + val textStyle = + TextStyle(fontSize = 14.sp) + val maxWidthOfText: List = strings.map { + textMeasurer.measure(it.toString(), style = textStyle).size.width + } + + return with(LocalDensity.current) { maxWidthOfText.max().toDp() } + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt index 643ce069..f5333990 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt @@ -22,4 +22,11 @@ enum class Routes(val title: String) { TakeOfferReviewTrade(title = "take_offer_review_trade"), TradeFlow(title = "trade_flow"), + + CreateOfferBuySell(title = "create_offer_buy_sel"), + CreateOfferCurrency(title = "create_offer_currency"), + CreateOfferAmount(title = "create_offer_amount"), + CreateOfferTradePrice(title = "create_offer_trade_price"), + CreateOfferPaymentMethod(title = "create_offer_payment_method"), + CreateOfferReviewOffer(title = "create_offer_review_offer"), } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt index 63bbb003..6dd47b05 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt @@ -2,6 +2,8 @@ package network.bisq.mobile.presentation.ui.navigation.graph import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -14,6 +16,7 @@ import network.bisq.mobile.presentation.ui.navigation.* import network.bisq.mobile.presentation.ui.theme.BisqTheme import network.bisq.mobile.presentation.ui.uicases.* import network.bisq.mobile.presentation.ui.uicases.offers.OffersListScreen +import network.bisq.mobile.presentation.ui.uicases.offers.createOffer.* import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.TakeOfferReviewTradeScreen import network.bisq.mobile.presentation.ui.uicases.offers.takeOffer.TakeOfferTradeAmountScreen import network.bisq.mobile.presentation.ui.uicases.startup.CreateProfileScreen @@ -61,11 +64,11 @@ fun RootNavGraph() { TakeOfferTradeAmountScreen() } - addScreen(Routes.TakeOfferPaymentMethod.name) { + addScreen(Routes.TakeOfferPaymentMethod.name, wizardTransition = true) { TakeOfferPaymentMethodScreen() } - addScreen(Routes.TakeOfferReviewTrade.name) { + addScreen(Routes.TakeOfferReviewTrade.name, wizardTransition = true) { TakeOfferReviewTradeScreen() } @@ -73,21 +76,52 @@ fun RootNavGraph() { TradeFlowScreen() } + addScreen(Routes.CreateOfferBuySell.name) { + CreateOfferBuySellScreen() + } + + addScreen(Routes.CreateOfferCurrency.name, wizardTransition = true) { + CreateOfferCurrencySelectorScreen() + } + + addScreen(Routes.CreateOfferAmount.name, wizardTransition = true) { + CreateOfferAmountSelectorScreen() + } + + addScreen(Routes.CreateOfferTradePrice.name, wizardTransition = true) { + CreateOfferTradePriceSelectorScreen() + } + + addScreen(Routes.CreateOfferPaymentMethod.name, wizardTransition = true) { + CreateOfferPaymentMethodSelectorScreen() + } + + addScreen(Routes.CreateOfferReviewOffer.name, wizardTransition = true) { + CreateOfferReviewOfferScreen() + } + + } } fun NavGraphBuilder.addScreen( route: String, + wizardTransition: Boolean = false, content: @Composable () -> Unit ) { composable( route = route, enterTransition = { - // When a screen is pushed in, slide in from right edge of the screen to left - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = tween(300) - ) + if (wizardTransition) { + // When user presses 'Next', fadeIn the next step screen + fadeIn(animationSpec = tween(150)) + } else { + // When a screen is pushed in, slide in from right edge of the screen to left + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(300) + ) + } }, exitTransition = { // When a new screen is pushed over current screen, don't do exit animation @@ -98,14 +132,19 @@ fun NavGraphBuilder.addScreen( null }, popExitTransition = { - // When current screen is poped out, slide if from screen to screen's right edge - slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = tween(300) - ) + if (wizardTransition) { + // When user presses 'Back', fadeOut the current step screen + fadeOut(animationSpec = tween(150)) + } else { + // When current screen is poped out, slide if from screen to screen's right edge + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween(300) + ) + } } ) { content() } -} \ No newline at end of file +} 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 0c802426..905604d4 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 @@ -5,6 +5,10 @@ import androidx.compose.ui.unit.dp object BisqUIConstants { val ScreenPadding = 12.dp val ScreenPadding2X = 24.dp + val ScreenPadding3X = 36.dp + val ScreenPadding4X = 48.dp + val ScreenPadding5X = 96.dp + val StaticTopPadding = 36.dp val ScrollTopPadding = 24.dp } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt index d8eef86c..67396965 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt @@ -8,23 +8,29 @@ import network.bisq.mobile.domain.data.repository.BisqStatsRepository import network.bisq.mobile.domain.service.market_price.MarketPriceServiceFacade import network.bisq.mobile.presentation.BasePresenter import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.navigation.Routes class GettingStartedPresenter( mainPresenter: MainPresenter, private val bisqStatsRepository: BisqStatsRepository, - private val marketPriceServiceFacade: MarketPriceServiceFacade -) : BasePresenter(mainPresenter) { + private val marketPriceServiceFacade: MarketPriceServiceFacade, +) : BasePresenter(mainPresenter), IGettingStarted { private val _offersOnline = MutableStateFlow(145) - val offersOnline: StateFlow = _offersOnline + override val offersOnline: StateFlow = _offersOnline private val _publishedProfiles = MutableStateFlow(1145) - val publishedProfiles: StateFlow = _publishedProfiles + override val publishedProfiles: StateFlow = _publishedProfiles val formattedMarketPrice: StateFlow = marketPriceServiceFacade.selectedFormattedMarketPrice private var job: Job? = null + override fun navigateToCreateOffer() { + rootNavigator.navigate(Routes.CreateOfferBuySell.name) + // rootNavigator.navigate(Routes.CreateOfferReviewOffer.name) + } + private fun refresh() { job = backgroundScope.launch { try { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt index 3a766a21..316cbacf 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt @@ -29,6 +29,9 @@ import bisqapps.shared.presentation.generated.resources.icon_star_outlined import bisqapps.shared.presentation.generated.resources.icon_tag_outlined import bisqapps.shared.presentation.generated.resources.img_fiat_btc import bisqapps.shared.presentation.generated.resources.img_learn_and_discover +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.BisqText import network.bisq.mobile.presentation.ui.components.layout.BisqScrollLayout import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle @@ -38,6 +41,13 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource import org.koin.compose.koinInject +interface IGettingStarted : ViewPresenter { + val offersOnline: StateFlow + val publishedProfiles: StateFlow + + fun navigateToCreateOffer() +} + @Composable fun GettingStartedScreen() { val presenter: GettingStartedPresenter = koinInject() @@ -82,6 +92,7 @@ fun GettingStartedScreen() { } } } + BisqButton("Create offer", onClick={ presenter.navigateToCreateOffer() }) WelcomeCard( title = "Get your first BTC", buttonText = "Enter Bisq Easy" 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 3e4acedf..a54c5483 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 @@ -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.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.atoms.icons.SortIcon import network.bisq.mobile.presentation.ui.components.layout.BisqStaticLayout @@ -25,7 +26,7 @@ fun MarketListScreen() { BisqStaticLayout(padding = PaddingValues(all = 0.dp), verticalArrangement = Arrangement.Top) { BisqTextField(label = "", placeholder = strings.common_search, value ="", onValueChanged = {}) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() LazyColumn { items(presenter.marketListItemWithNumOffers) { item -> 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 f414c962..c6f78809 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 @@ -15,6 +15,7 @@ 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.layout.BisqStaticScaffold import network.bisq.mobile.presentation.ui.components.molecules.DirectionToggle import network.bisq.mobile.presentation.ui.components.molecules.OfferCard @@ -65,7 +66,7 @@ fun OffersListScreen() { presenter.onSelectDirection(direction) } - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() LazyColumn( verticalArrangement = Arrangement.spacedBy(12.dp), 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 new file mode 100644 index 00000000..c364b49b --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/AmountSelectorScreen.kt @@ -0,0 +1,125 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import cafe.adriel.lyricist.LocalStrings +import com.ionspin.kotlin.bignum.decimal.toBigDecimalUsingSignificandAndExponent +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.atoms.NoteText +import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold +import network.bisq.mobile.presentation.ui.components.molecules.BisqAmountSelector +import network.bisq.mobile.presentation.ui.components.molecules.RangeAmountSelector +import network.bisq.mobile.presentation.ui.components.molecules.ToggleTab +import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle +import network.bisq.mobile.presentation.ui.helpers.StringHelper +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject + +@Composable +fun CreateOfferAmountSelectorScreen() { + val strings = LocalStrings.current.bisqEasyTradeWizard + val stringsEasy = LocalStrings.current.bisqEasy + val stringsCommon = LocalStrings.current.common + val presenter: ICreateOfferPresenter = koinInject() + + val offerMinFiatAmount = 800.0f + val offerMaxFiatAmount = 1500.0f + val state by presenter.state.collectAsState() + val isBuy = presenter.direction.collectAsState().value.isBuy + val fixedAmount = presenter.fixedAmount.collectAsState().value + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + stringsEasy.bisqEasy_openTrades_table_quoteAmount, + stepIndex = 3, + stepsLength = 6, + prevOnClick = { presenter.goBack() }, + nextButtonText = stringsCommon.buttons_next, + nextOnClick = { presenter.navigateToTradePriceSelector() } + ) { + val headerText = if (isBuy) + strings.bisqEasy_tradeWizard_amount_headline_buyer + else + strings.bisqEasy_tradeWizard_amount_headline_seller + + BisqText.h3Regular( + text = headerText, + color = BisqTheme.colors.light1, + modifier = Modifier.align(Alignment.Start) + ) + + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + + BisqText.largeLight( + text = strings.bisqEasy_tradeWizard_amount_description_fixAmount, + color = BisqTheme.colors.grey2, + modifier = Modifier.align(Alignment.Start) + ) + + Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + + Column(verticalArrangement = Arrangement.spacedBy(52.dp)) { + + ToggleTab( + options = AmountType.entries, + initialOption = AmountType.FIXED_AMOUNT, + onStateChange = { amountType -> presenter.onSelectAmountType(amountType) }, + getDisplayString = { direction -> + if (direction == AmountType.FIXED_AMOUNT) + strings.bisqEasy_tradeWizard_fixed_amount + else + strings.bisqEasy_tradeWizard_trade_amount + }, + textWidth = StringHelper.calculateTotalWidthOfStrings( + strings = listOf( + strings.bisqEasy_tradeWizard_fixed_amount, + strings.bisqEasy_tradeWizard_range_amount + ) + ), + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + + if (state.selectedAmountType == AmountType.FIXED_AMOUNT) { + BisqAmountSelector( + minAmount = offerMinFiatAmount, + maxAmount = offerMaxFiatAmount, + exchangeRate = 95000.0, + currency = "USD", + onValueChange = { value -> presenter.onFixedAmountChange(value) } + ) + } else { + RangeAmountSelector( + minAmount = offerMinFiatAmount, + maxAmount = offerMaxFiatAmount + ) + } + + val matchingSellerCount = 2 + val countString = if (matchingSellerCount == 0) { + strings.bisqEasy_tradeWizard_amount_numOffers_0 + } else if (matchingSellerCount == 1) { + strings.bisqEasy_tradeWizard_amount_numOffers_1 + } else { + strings.bisqEasy_tradeWizard_amount_numOffers_many(matchingSellerCount.toString()) + } + + val finalText = strings.bisqEasy_tradeWizard_amount_buyer_limitInfo(countString, "$fixedAmount USD") + NoteText( + notes = finalText, + linkText = stringsEasy.bisqEasy_takeOffer_amount_buyer_limitInfo_learnMore + ) + + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..fd7066e1 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/BuySellScreen.kt @@ -0,0 +1,54 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +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.atoms.* +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 +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject + +@Composable +fun CreateOfferBuySellScreen() { + val strings = LocalStrings.current.bisqEasyTradeWizard + val presenter: ICreateOfferPresenter = koinInject() + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + strings.bisqEasy_tradeWizard_review_nextButton_createOffer, + stepIndex = 1, + stepsLength = 6, + horizontalAlignment = Alignment.Start + ) { + BisqText.h3Regular(strings.bisqEasy_tradeWizard_directionAndMarket_headline) + + BisqGap.V2() + + BisqButton( + onClick = { presenter.buyBitcoinClicked() }, + backgroundColor = BisqTheme.colors.primary, + modifier = Modifier.fillMaxWidth(), + padding = PaddingValues(vertical = BisqUIConstants.ScreenPadding4X), + textComponent = { BisqText.h3Medium(text = strings.bisqEasy_tradeWizard_directionAndMarket_buy) } + ) + BisqText.largeLight(strings.bisqEasy_tradeWizard_buy_description, color = BisqTheme.colors.grey2) + + BisqGap.V2() + + BisqButton( + onClick = { presenter.sellBitcoinClicked() }, + backgroundColor = BisqTheme.colors.secondary, + modifier = Modifier.fillMaxWidth(), + padding = PaddingValues(vertical = BisqUIConstants.ScreenPadding4X), + textComponent = { BisqText.h3Medium(text = strings.bisqEasy_tradeWizard_directionAndMarket_sell) } + ) + BisqText.largeLight(strings.bisqEasy_tradeWizard_sell_description, color = BisqTheme.colors.grey2) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..7c4d28cc --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferCurrencySelectorScreen.kt @@ -0,0 +1,90 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +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.runtime.getValue +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.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.atoms.BisqTextField +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 +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject + +@Composable +fun CreateOfferCurrencySelectorScreen() { + val strings = LocalStrings.current.bisqEasyTradeWizard + val commonStrings = LocalStrings.current.common + val presenter: ICreateOfferPresenter = koinInject() + val state by presenter.state.collectAsState() + val isBuy = presenter.direction.collectAsState().value.isBuy + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + commonStrings.currency, + stepIndex = 2, + stepsLength = 6, + prevOnClick = { presenter.goBack() }, + nextOnClick = { presenter.navigateToAmountSelector() }, + useStaticScaffold = true, + horizontalAlignment = Alignment.Start + ) { + + val headerText = if (isBuy) + strings.bisqEasy_tradeWizard_market_headline_buyer + else + strings.bisqEasy_tradeWizard_market_headline_seller + + BisqText.h3Regular( + text = headerText, + color = BisqTheme.colors.light1, + ) + BisqGap.V1() + + BisqText.largeLight( + text = strings.bisqEasy_tradeWizard_market_subTitle, + color = BisqTheme.colors.grey2, + ) + + BisqGap.V2() + + BisqTextField( + placeholder = commonStrings.common_search, + onValueChanged = {}, + value = "", + label = "", + ) + + BisqGap.V1() + + LazyColumn( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(presenter.marketListItemWithNumOffers) { item -> + CurrencyProfileCard( + item, + isSelected = state.selectedOfferbookMarket.market.quoteCurrencyCode == item.market.quoteCurrencyCode, + onClick = { presenter.onSelectMarket(item) } + ) + } + } + + BisqGap.V1() + } +} \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferPresenter.kt new file mode 100644 index 00000000..c708ad81 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/CreateOfferPresenter.kt @@ -0,0 +1,236 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import network.bisq.mobile.client.replicated_model.offer.Direction +import network.bisq.mobile.client.replicated_model.user.reputation.ReputationScore +import network.bisq.mobile.domain.data.model.MarketListItem +import network.bisq.mobile.domain.data.model.OfferListItem +import network.bisq.mobile.domain.data.model.OfferbookMarket +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.ViewPresenter +import network.bisq.mobile.presentation.ui.navigation.Routes + +enum class AmountType { + FIXED_AMOUNT, + RANGE_AMOUNT, +} + +enum class PriceType { + PERCENTAGE, + FIXED, +} + + +interface ICreateOfferPresenter : ViewPresenter { + var marketListItemWithNumOffers: List + + val offerListItems: StateFlow> + + val state: StateFlow + + val direction: StateFlow + + val fixedAmount: StateFlow + + fun onSelectMarket(marketListItem: MarketListItem) + + fun onSelectAmountType(amountType: AmountType) + + fun goBack() + + fun navigateToCurrencySelector() + + fun buyBitcoinClicked() + + fun sellBitcoinClicked() + + fun navigateToAmountSelector() + + fun navigateToTradePriceSelector() + + fun navigateToPaymentMethod() + + fun navigateToReviewOffer() + + fun createOffer() + + fun onSelectPriceType(priceType: PriceType) + + fun onSelectPayment(paymentMethod: String) + + fun onFixedAmountChange(amount: Float) + +} + +data class CreateOffer( + val selectedAmountType: AmountType = AmountType.FIXED_AMOUNT, + val selectedPriceType: PriceType = PriceType.PERCENTAGE, + val selectedOfferbookMarket: OfferbookMarket = OfferbookMarket.EMPTY, + val paymentMethod: String = "" +) + +open class CreateOfferPresenter( + mainPresenter: MainPresenter, + private val offerbookServiceFacade: OfferbookServiceFacade, +) : BasePresenter(mainPresenter), ICreateOfferPresenter { + + override val offerListItems: StateFlow> = MutableStateFlow( + listOf( + OfferListItem( + messageId = "12345", + offerId = "abcde", + isMyMessage = true, + direction = Direction.BUY, + offerTitle = "Sample Offer", + date = 1638316800000, // Example timestamp + formattedDate = "Dec 5, 2024", + nym = "user123", + userName = "John Doe", + reputationScore = ReputationScore.NONE, + formattedQuoteAmount = "500 USD", + formattedPrice = "1000 USD", + quoteSidePaymentMethods = listOf("Amazon-Gift-Card", "ACH-Transfer", "Cash-App"), + baseSidePaymentMethods = listOf("Main-Chain", "Ln"), + supportedLanguageCodes = "en,es,de", + quoteCurrencyCode = "USD", + ) + ) + ) + private val _state = MutableStateFlow(CreateOffer()) + override val state = _state.asStateFlow() + + private val _direction = MutableStateFlow(Direction.BUY) + override val direction: StateFlow = _direction.asStateFlow() + + private val _fixedAmount = MutableStateFlow(0.0f) + override val fixedAmount: StateFlow = _fixedAmount.asStateFlow() + + private var mainCurrencies = OfferbookServiceFacade.mainCurrencies + + override var marketListItemWithNumOffers: List = + offerbookServiceFacade.offerbookMarketItems + .sortedWith( + compareByDescending { it.numOffers.value } + .thenByDescending { mainCurrencies.contains(it.market.quoteCurrencyCode.lowercase()) } // [1] + .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 + + override fun onSelectMarket(marketListItem: MarketListItem) { + _state.update { + it.copy(selectedOfferbookMarket = OfferbookMarket(marketListItem.market)) + } + log.i { state.value.selectedOfferbookMarket.market.quoteCurrencyCode } + } + + override fun onSelectAmountType(amountType: AmountType) { + _state.update { + it.copy(selectedAmountType = amountType) + } + } + + override fun onViewAttached() { + } + + override fun onViewUnattaching() { + } + + override fun buyBitcoinClicked() { + _direction.value = Direction.BUY + navigateToCurrencySelector() + } + + override fun sellBitcoinClicked() { + _direction.value = Direction.SELL + navigateToCurrencySelector() + } + + override fun goBack() { + log.i { "goBack" } + rootNavigator.popBackStack() + } + + override fun navigateToCurrencySelector() { + log.i { "navigateToCurrencySelector" } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.navigate(Routes.CreateOfferCurrency.name) + } + } + + override fun navigateToAmountSelector() { + log.i { "navigateToAmountSelector" } + + if (state.value.selectedOfferbookMarket == OfferbookMarket.EMPTY) { + log.e { "A currency must be selected!" } + return + } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.navigate(Routes.CreateOfferAmount.name) + } + } + + override fun navigateToTradePriceSelector() { + log.i { "navigateToTradePriceSelector" } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.navigate(Routes.CreateOfferTradePrice.name) + } + } + + override fun navigateToPaymentMethod() { + log.i { "navigateToPaymentMethod" } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.navigate(Routes.CreateOfferPaymentMethod.name) + } + } + + override fun navigateToReviewOffer() { + log.i { "navigateToReviewOffer" } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.navigate(Routes.CreateOfferReviewOffer.name) + } + } + + override fun createOffer() { + log.i { "createOffer" } + + CoroutineScope(Dispatchers.Main).launch { + rootNavigator.popBackStack(Routes.TabContainer.name, inclusive = false, saveState = false ) + } + } + + override fun onSelectPriceType(priceType: PriceType) { + _state.update { + it.copy(selectedPriceType = priceType) + } + log.i { "priceType" } + } + + override fun onSelectPayment(paymentMethod: String) { + _state.update { + it.copy(paymentMethod = paymentMethod) + } + } + + override fun onFixedAmountChange(amount: Float) { + _fixedAmount.value = amount + log.i { "Change fixed amount: ${amount.toString()}" } + } +} 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 new file mode 100644 index 00000000..bcabf84f --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/PaymentMethodSelectorScreen.kt @@ -0,0 +1,94 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +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 network.bisq.mobile.presentation.ui.components.atoms.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 +import network.bisq.mobile.presentation.ui.composeModels.PaymentTypeData +import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject + +val paymentTransferList = listOf( + PaymentTypeData( + image = "drawable/payment/fiat/ach_transfer.png", + title = "Venmo" + ), + PaymentTypeData( + image = "drawable/payment/fiat/ach_transfer.png", + title = "Strike" + ), + PaymentTypeData( + image = "drawable/payment/fiat/ach_transfer.png", + title = "ACH" + ), + PaymentTypeData( + image = "drawable/payment/fiat/ach_transfer.png", + title = "US Money Order" + ), +) + +val paymentReceiverList = listOf( + PaymentTypeData( + image = "drawable/payment/bitcoin/main_chain.png", + title = "OnChain" + ), + PaymentTypeData( + image = "drawable/payment/bitcoin/ln.png", + title = "Lightning" + ), +) + +@Composable +fun CreateOfferPaymentMethodSelectorScreen() { + val commonStrings = LocalStrings.current.common + val stringsBisqEasy = LocalStrings.current.bisqEasy + val presenter: ICreateOfferPresenter = koinInject() + val isBuy = presenter.direction.collectAsState().value.isBuy + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + stringsBisqEasy.bisqEasy_takeOffer_progress_method, + stepIndex = 5, + stepsLength = 6, + prevOnClick = { presenter.goBack() }, + nextButtonText = commonStrings.buttons_next, + nextOnClick = { presenter.navigateToReviewOffer() } + ) { + BisqText.h3Regular( + text = stringsBisqEasy.bisqEasy_takeOffer_paymentMethods_headline_fiatAndBitcoin, + color = BisqTheme.colors.light1 + ) + + BisqGap.V1() + + val paymentMethodText = if (isBuy) + stringsBisqEasy.bisqEasy_takeOffer_paymentMethods_subtitle_fiat_buyer("USD") + else + stringsBisqEasy.bisqEasy_takeOffer_paymentMethods_subtitle_fiat_seller("USD") + PaymentMethodCard( + paymentMethodTitle = paymentMethodText, + paymentTypeList = paymentTransferList + ) + + BisqGap.V2() + + val settlementMethodText = if (isBuy) + stringsBisqEasy.bisqEasy_takeOffer_paymentMethods_subtitle_bitcoin_buyer + else + stringsBisqEasy.bisqEasy_takeOffer_paymentMethods_subtitle_bitcoin_seller + PaymentMethodCard( + paymentMethodTitle = settlementMethodText, + paymentTypeList = paymentReceiverList + ) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..77ece910 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/ReviewOfferScreen.kt @@ -0,0 +1,125 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +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.BisqText +import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold +import network.bisq.mobile.presentation.ui.components.molecules.info.InfoBox +import network.bisq.mobile.presentation.ui.components.molecules.info.InfoRow +import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.koin.compose.koinInject + +@Composable +fun CreateOfferReviewOfferScreen() { + val strings = LocalStrings.current.bisqEasyTradeWizard + val stringsBisqEasy = LocalStrings.current.bisqEasy + val paymentMethodStrings = LocalStrings.current.paymentMethod + val presenter: ICreateOfferPresenter = koinInject() + val isBuy = presenter.direction.collectAsState().value.isBuy + + val tradeStateStrings = LocalStrings.current.bisqEasyTradeState + + val offer = presenter.offerListItems.collectAsState().value.first() + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + strings.bisqEasy_tradeWizard_review_headline_maker, + stepIndex = 6, + stepsLength = 6, + prevOnClick = { presenter.goBack() }, + nextButtonText = strings.bisqEasy_tradeWizard_review_nextButton_createOffer, + nextOnClick = { presenter.createOffer() }, + horizontalAlignment = Alignment.Start + ) { + BisqText.h3Regular( + text = strings.bisqEasy_tradeWizard_review_headline_maker, + color = BisqTheme.colors.light1, + textAlign = TextAlign.Start, + modifier = Modifier.fillMaxWidth() + ) + BisqGap.V2() + Column(verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding2X)) { + InfoBox( + label = tradeStateStrings.bisqEasy_tradeState_header_direction.uppercase(), + value = if (isBuy) + strings.bisqEasy_tradeWizard_directionAndMarket_buy + else + strings.bisqEasy_tradeWizard_directionAndMarket_sell + ) + InfoBox( + label = stringsBisqEasy.bisqEasy_takeOffer_review_method_fiat, + value = "Strike, National banks, Steem cards" + ) + InfoRow( + label1 = strings.bisqEasy_tradeWizard_review_toPay.uppercase(), + value1 = offer.formattedPrice, // TODO: Show selected amount (in case offer has range) + label2 = strings.bisqEasy_tradeWizard_review_toReceive.uppercase(), + value2 = offer.formattedQuoteAmount + ) + BisqHDivider() + InfoBox( + label = strings.bisqEasy_tradeWizard_review_priceDescription_taker, + valueComposable = { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + BisqText.h6Regular(text = "98,000.68") // TODO: Values? + BisqText.baseRegular( + text = "BTC/USD", + color = BisqTheme.colors.grey2 + ) // TODO: Values? + } + BisqText.smallRegular( + text = strings.bisqEasy_tradeWizard_review_priceDetails_float( + "1.00%", + "above", + "60,000 BTC/USD" + ), + color = BisqTheme.colors.grey4 + ) + } + } + ) + + InfoBox( + label = stringsBisqEasy.bisqEasy_takeOffer_review_method_bitcoin, + value = paymentMethodStrings.LN_SHORT + ) + InfoBox( + label = stringsBisqEasy.bisqEasy_takeOffer_review_method_fiat, + value = paymentMethodStrings.STRIKE + ) + + InfoBox( + label = strings.bisqEasy_tradeWizard_review_feeDescription, + valueComposable = { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + BisqText.h6Regular(text = strings.bisqEasy_tradeWizard_review_noTradeFees) + BisqText.smallRegular( + text = strings.bisqEasy_tradeWizard_review_sellerPaysMinerFeeLong, + color = BisqTheme.colors.grey4, + ) + } + } + ) + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..0e82a6f8 --- /dev/null +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/createOffer/TradePriceSelectorScreen.kt @@ -0,0 +1,121 @@ +package network.bisq.mobile.presentation.ui.uicases.offers.createOffer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextAlign +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_medium +import cafe.adriel.lyricist.LocalStrings +import network.bisq.mobile.presentation.ui.components.atoms.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 +import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold +import network.bisq.mobile.presentation.ui.components.molecules.ToggleTab +import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle +import network.bisq.mobile.presentation.ui.helpers.StringHelper +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import network.bisq.mobile.presentation.ui.theme.BisqUIConstants +import org.jetbrains.compose.resources.Font +import org.koin.compose.koinInject + +@Composable +fun CreateOfferTradePriceSelectorScreen() { + val strings = LocalStrings.current.bisqEasyTradeWizard + val commonStrings = LocalStrings.current.common + val bisqEasyStrings = LocalStrings.current.bisqEasy + val presenter: ICreateOfferPresenter = koinInject() + + val state by presenter.state.collectAsState() + + RememberPresenterLifecycle(presenter) + + MultiScreenWizardScaffold( + bisqEasyStrings.bisqEasy_takeOffer_review_price_price, + stepIndex = 4, + stepsLength = 6, + prevOnClick = { presenter.goBack() }, + nextButtonText = commonStrings.buttons_next, + nextOnClick = { presenter.navigateToPaymentMethod() } + ) { + BisqText.h3Regular( + text = strings.bisqEasy_price_headline, + color = BisqTheme.colors.light1, + modifier = Modifier.align(Alignment.Start) + ) + BisqGap.V1() + BisqText.largeLight( + text = strings.bisqEasy_tradeWizard_price_subtitle, + color = BisqTheme.colors.grey2 + ) + Column( + modifier = Modifier.padding(vertical = BisqUIConstants.ScreenPadding2X), + verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding) + ) { + ToggleTab( + options = PriceType.entries, + initialOption = PriceType.PERCENTAGE, + onStateChange = { priceType -> presenter.onSelectPriceType(priceType) }, + getDisplayString = { direction -> + if (direction == PriceType.PERCENTAGE) + strings.bisqEasy_tradeWizard_trade_price_percentage + else + strings.bisqEasy_tradeWizard_trade_price_fixed + }, + textWidth = StringHelper.calculateTotalWidthOfStrings( + strings = PriceType.entries, + ), + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Column( + verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding) + ) { + if (state.selectedPriceType == PriceType.PERCENTAGE) { + BisqTextField( + label = strings.bisqEasy_price_percentage_inputBoxText, + value = "10%", + onValueChanged = {}, + ) + BisqTextField( + label = strings.bisqEasy_price_tradePrice_inputBoxText("USD"), + value = "106212.99", + onValueChanged = {}, + indicatorColor = BisqTheme.colors.grey1 + ) + } else { + BisqTextField( + label = strings.bisqEasy_price_tradePrice_inputBoxText("USD"), + value = "106212.99", + onValueChanged = {}, + ) + BisqTextField( + label = strings.bisqEasy_price_percentage_inputBoxText, + value = "10%", + onValueChanged = {}, + indicatorColor = BisqTheme.colors.grey1 + ) + } + } + NoteText( + notes = strings.bisqEasy_price_feedback_sentence, + linkText = strings.bisqEasy_price_feedback_learnWhySection_openButton, + textAlign = TextAlign.Center + ) + } + } +} \ No newline at end of file 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 855e9b0e..114be99f 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,6 +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.BisqText import network.bisq.mobile.presentation.ui.components.atoms.DynamicImage import network.bisq.mobile.presentation.ui.theme.BisqTheme @@ -48,8 +49,8 @@ fun TakeOfferPaymentMethodScreen() { text = strings.bisqEasy_takeOffer_paymentMethods_headline_fiatAndBitcoin, color = BisqTheme.colors.light1 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() + BisqGap.V2() Column( modifier = Modifier.padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -58,7 +59,7 @@ fun TakeOfferPaymentMethodScreen() { text = strings.bisqEasy_takeOffer_paymentMethods_subtitle_fiat_buyer("USD"), color = BisqTheme.colors.grey2 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 38.dp), horizontalAlignment = Alignment.Start, @@ -90,8 +91,8 @@ fun TakeOfferPaymentMethodScreen() { } } } - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() + BisqGap.V2() Column( modifier = Modifier.padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -100,7 +101,7 @@ fun TakeOfferPaymentMethodScreen() { text = strings.bisqEasy_takeOffer_paymentMethods_subtitle_bitcoin_seller, color = BisqTheme.colors.grey2 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V1() Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 38.dp), horizontalAlignment = Alignment.Start, 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 0c10e37d..d5299f5a 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 @@ -10,6 +10,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.BisqHDivider import network.bisq.mobile.presentation.ui.components.atoms.BisqText import network.bisq.mobile.presentation.ui.theme.BisqTheme @@ -30,29 +31,29 @@ interface ITakeOfferReviewTradePresenter : ViewPresenter { @Composable fun TakeOfferReviewTradeScreen() { val strings = LocalStrings.current.bisqEasyTradeWizard - val bisqEasyStrings = LocalStrings.current.bisqEasy - val tradeStateStrings = LocalStrings.current.bisqEasyTradeState + val stringsBisqEasy = LocalStrings.current.bisqEasy + val stringsTradeState = LocalStrings.current.bisqEasyTradeState val commonStrings = LocalStrings.current.common val presenter: ITakeOfferReviewTradePresenter = koinInject() val offer = presenter.offerListItems.collectAsState().value.first() MultiScreenWizardScaffold( - bisqEasyStrings.bisqEasy_takeOffer_progress_review, - stepIndex = 2, + stringsBisqEasy.bisqEasy_takeOffer_progress_review, + stepIndex = 3, stepsLength = 3, prevOnClick = { presenter.goBack() }, - nextButtonText = bisqEasyStrings.bisqEasy_takeOffer_review_takeOffer, + nextButtonText = stringsBisqEasy.bisqEasy_takeOffer_review_takeOffer, nextOnClick = { presenter.tradeConfirmed() } ) { BisqText.h3Regular( - text = bisqEasyStrings.bisqEasy_takeOffer_progress_review, + text = stringsBisqEasy.bisqEasy_takeOffer_progress_review, color = BisqTheme.colors.light1 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() Column(verticalArrangement = Arrangement.spacedBy(BisqUIConstants.ScreenPadding2X)) { InfoRow( - label1 = tradeStateStrings.bisqEasy_tradeState_header_direction.uppercase(), + label1 = stringsTradeState.bisqEasy_tradeState_header_direction.uppercase(), value1 = if (offer.direction.isBuy) strings.bisqEasy_tradeWizard_directionAndMarket_buy else diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountPresenter.kt index 2499074c..d06266eb 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/offers/takeOffer/TradeAmountPresenter.kt @@ -30,4 +30,7 @@ open class TradeAmountPresenter( rootNavigator.navigate(Routes.TakeOfferPaymentMethod.name) } + override fun onFixedAmountChange(amount: Float) { + log.i { "Change amount: ${amount.toString()}" } + } } 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 487f0875..14668816 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 @@ -9,6 +9,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.BisqText import network.bisq.mobile.presentation.ui.components.layout.MultiScreenWizardScaffold import network.bisq.mobile.presentation.ui.components.molecules.BisqAmountSelector @@ -22,6 +23,8 @@ interface ITakeOfferTradeAmountPresenter : ViewPresenter { fun goBack() fun amountConfirmed() + + fun onFixedAmountChange(amount: Float) } @Composable @@ -46,7 +49,7 @@ fun TakeOfferTradeAmountScreen() { text = strings.bisqEasy_takeOffer_amount_headline_buyer, color = BisqTheme.colors.light1 ) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding)) + BisqGap.V1() BisqText.largeLight( text = strings.bisqEasy_takeOffer_amount_description( offerMinFiatAmount.toDouble(), @@ -61,7 +64,8 @@ fun TakeOfferTradeAmountScreen() { minAmount = offerMinFiatAmount, maxAmount = offerMaxFiatAmount, exchangeRate = 95000.0, - currency = "USD" + currency = "USD", + onValueChange = {value -> } ) } } \ No newline at end of file diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt index c32e2706..f47024fa 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt @@ -2,19 +2,19 @@ package network.bisq.mobile.presentation.ui.uicases.startup import androidx.compose.foundation.pager.PagerState import bisqapps.shared.presentation.generated.resources.Res -import bisqapps.shared.presentation.generated.resources.img_bisq_Easy -import bisqapps.shared.presentation.generated.resources.img_fiat_btc -import bisqapps.shared.presentation.generated.resources.img_learn_and_discover +import bisqapps.shared.presentation.generated.resources.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import network.bisq.mobile.domain.data.model.Settings +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.PagerViewItem import network.bisq.mobile.presentation.ui.navigation.Routes - open class OnBoardingPresenter( - mainPresenter: MainPresenter + mainPresenter: MainPresenter, + private val settingsRepository: SettingsRepository ) : BasePresenter(mainPresenter), IOnboardingPresenter { override val indexesToShow = listOf(0) @@ -42,8 +42,19 @@ open class OnBoardingPresenter( override fun onNextButtonClick(coroutineScope: CoroutineScope, pagerState: PagerState) { coroutineScope.launch { + + settingsRepository.fetch() + val settings: Settings? = settingsRepository.data.value + if (pagerState.currentPage == indexesToShow.lastIndex) { rootNavigator.navigate(Routes.CreateProfile.name) + + val updatedSettings = (settings ?: Settings()).apply { + firstLaunch = false + } + + settingsRepository.update(updatedSettings) + } else { pagerState.animateScrollToPage(pagerState.currentPage + 1) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt index 6eaf8feb..0cc3f8e5 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt @@ -6,6 +6,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import network.bisq.mobile.domain.data.BackgroundDispatcher +import network.bisq.mobile.domain.data.model.Settings +import network.bisq.mobile.domain.data.repository.SettingsRepository import network.bisq.mobile.domain.data.repository.main.bootstrap.ApplicationBootstrapFacade import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade import network.bisq.mobile.presentation.BasePresenter @@ -15,7 +17,8 @@ import network.bisq.mobile.presentation.ui.navigation.Routes open class SplashPresenter( mainPresenter: MainPresenter, private val applicationBootstrapFacade: ApplicationBootstrapFacade, - private val userProfileService: UserProfileServiceFacade + private val userProfileService: UserProfileServiceFacade, + private val settingsRepository: SettingsRepository ) : BasePresenter(mainPresenter) { val state: StateFlow = applicationBootstrapFacade.state @@ -39,17 +42,38 @@ open class SplashPresenter( private fun navigateToNextScreen() { CoroutineScope(Dispatchers.Main).launch { - // TODO: Conditional nav - Will implement once we got persistant storage from nish to save flags - // If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient) - // If not, goto TabContainerScreen + + settingsRepository.fetch() + val settings: Settings = settingsRepository.data.value ?: Settings() + if (userProfileService.hasUserProfile()) { - // rootNavigator.navigate(Routes.TrustedNodeSetup.name) { + // rootNavigator.navigate(Routes.TrustedNodeSetup.name) { + // [DONE] For androidNode, goto TabContainer rootNavigator.navigate(Routes.TabContainer.name) { popUpTo(Routes.Splash.name) { inclusive = true } } + + // TODO: This is only for xClient. + // How to handle between xClient and androidNode + if (settings.bisqApiUrl.isEmpty()) { + // Test if the Bisq remote instance is up and responding + // If yes, goto TabContainer screen. + // If no, goto TrustedNodeSetupScreen + } else { + // If no, goto TrustedNodeSetupScreen + } + } else { - rootNavigator.navigate(Routes.CreateProfile.name) { - popUpTo(Routes.Splash.name) { inclusive = true } + // If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient) + // If not, goto CreateProfile + if (settings.firstLaunch) { + rootNavigator.navigate(Routes.Onboarding.name) { + popUpTo(Routes.Splash.name) { inclusive = true } + } + } else { + rootNavigator.navigate(Routes.CreateProfile.name) { + popUpTo(Routes.Splash.name) { inclusive = true } + } } } } 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 ac4ff7e6..75d0ff3f 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 @@ -69,7 +69,7 @@ fun TradeFlowScreen() { TradeHeader(offer) - Spacer(modifier = Modifier.height(BisqUIConstants.ScreenPadding2X)) + BisqGap.V2() TradeFlowStepper() diff --git a/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.ios.kt b/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.ios.kt new file mode 100644 index 00000000..507ca056 --- /dev/null +++ b/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/ui/helpers/NumberFormatter.ios.kt @@ -0,0 +1,9 @@ +package network.bisq.mobile.presentation.ui.helpers + +import platform.Foundation.NSString +import platform.Foundation.stringWithFormat + +actual val numberFormatter: NumberFormatter = object : NumberFormatter { + override fun satsFormat(value: Double): String = + NSString.stringWithFormat("%.8f", value) +}