diff --git a/bisqapps/README.md b/bisqapps/README.md index d0771830..c5d01716 100644 --- a/bisqapps/README.md +++ b/bisqapps/README.md @@ -47,7 +47,10 @@ Addicionally, for the `androidNode` module to build you need to have its depende Done! Alternatively if you are interested only in contributing for the `xClients` you can just build them individually instead of building the whole project. -### UI Designs +### UI + +**Designs** + androidNode + xClient screens are designed in Figma. Yet to differentiate between which screens goes into which. @@ -55,6 +58,10 @@ Figma link: https://www.figma.com/design/IPnuicxGKIZXq28gybxOgp/Xchange?node-id= Though the figma design captures most of the functionality, it's an evolving document. It will be updated with new screens, flow updates, based on discussions happening in GH issues / matrix. +**Navigation Implementation** + +Please refer to [this README](shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/README.md) + ### Configuring dev env: known issues - Some Apple M chips have trouble with cocoapods, follow [this guide](https://stackoverflow.com/questions/64901180/how-to-run-cocoapods-on-apple-silicon-m1/66556339#66556339) to fix it diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt index 394c8a55..b6d68c96 100644 --- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt @@ -2,17 +2,7 @@ package network.bisq.mobile.android.node.presentation import android.app.Activity import android.os.Build -import network.bisq.mobile.presentation.MainPresenter -import bisq.common.facades.FacadeProvider -import bisq.common.facades.android.AndroidGuavaFacade -import bisq.common.facades.android.AndroidJdkFacade -import bisq.common.network.AndroidEmulatorLocalhostFacade -import org.bouncycastle.jce.provider.BouncyCastleProvider -import java.security.Security import android.os.Process -import network.bisq.mobile.android.node.domain.model.UserProfileModel -import bisq.common.observable.Observable -import bisq.user.identity.UserIdentityService import bisq.application.State import bisq.bonded_roles.market_price.MarketPrice import bisq.chat.ChatChannelDomain @@ -21,8 +11,13 @@ import bisq.chat.common.CommonPublicChatMessage import bisq.chat.two_party.TwoPartyPrivateChatChannel import bisq.chat.two_party.TwoPartyPrivateChatMessage import bisq.common.currency.MarketRepository +import bisq.common.facades.FacadeProvider +import bisq.common.facades.android.AndroidGuavaFacade +import bisq.common.facades.android.AndroidJdkFacade import bisq.common.locale.LanguageRepository +import bisq.common.network.AndroidEmulatorLocalhostFacade import bisq.common.network.TransportType +import bisq.common.observable.Observable import bisq.common.observable.Pin import bisq.common.observable.collection.CollectionObserver import bisq.common.timer.Scheduler @@ -34,16 +29,20 @@ import bisq.security.DigestUtil import bisq.security.SecurityService import bisq.user.identity.NymIdGenerator import bisq.user.identity.UserIdentity +import bisq.user.identity.UserIdentityService import bisq.user.profile.UserProfile import kotlinx.coroutines.* import network.bisq.mobile.android.node.AndroidNodeGreeting import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository +import network.bisq.mobile.android.node.domain.model.UserProfileModel import network.bisq.mobile.android.node.service.AndroidApplicationService import network.bisq.mobile.android.node.service.AndroidMemoryReportService import network.bisq.mobile.domain.data.model.Greeting import network.bisq.mobile.domain.data.repository.GreetingRepository -import network.bisq.mobile.domain.data.repository.SingleObjectRepository -import java.util.Optional +import network.bisq.mobile.presentation.MainPresenter +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.security.Security +import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong @@ -105,9 +104,9 @@ class MainNodePresenter(greetingRepository: NodeGreetingRepository): MainPresent launchServices() } - override fun onDestroy() { + override fun onDestroying() { applicationService.shutdown() - super.onDestroy() + super.onDestroying() } private fun log(message: String) { diff --git a/bisqapps/gradle.properties b/bisqapps/gradle.properties index 0245fefd..cb5994a9 100644 --- a/bisqapps/gradle.properties +++ b/bisqapps/gradle.properties @@ -1,5 +1,5 @@ #Gradle -org.gradle.jvmargs=-Xmx4096M -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M" +org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx8g" org.gradle.caching=true org.gradle.configuration-cache=true diff --git a/bisqapps/gradle/libs.versions.toml b/bisqapps/gradle/libs.versions.toml index 295d58d8..868b6e77 100644 --- a/bisqapps/gradle/libs.versions.toml +++ b/bisqapps/gradle/libs.versions.toml @@ -5,13 +5,15 @@ android-targetSdk = "34" android-minSdk = "24" android-node-minSdk = "31" androidx-activityCompose = "1.9.2" + androidx-appcompat = "1.7.0" androidx-constraintlayout = "2.1.4" androidx-core-ktx = "1.13.1" androidx-espresso-core = "3.6.1" -androidx-lifecycle = "2.8.2" androidx-material = "1.12.0" androidx-test-junit = "1.2.1" + +androidx-lifecycle = "2.8.2" androidx-test-compose-ver = "1.6.8" androidx-multidex = "2.0.1" bisq-core = "2.1.2" @@ -23,52 +25,57 @@ kotlinTestJunit = "2.0.20" kotlinx = "1.9.0" kermit = "2.0.4" buildconfig = "5.5.0" +navigationCompose = "2.7.0-alpha07" protobuf = "0.9.4" protob = "4.28.2" +ksp = "2.0.20-1.0.25" chimp-jsocks-lib = { strictly = '567e1cd6' } -chimp-jtorctl-lib = { strictly = '9b5ba203' } failsafe-lib = { strictly = '3.2.4' } + +bouncycastle-lib = { strictly = '1.78.1' } + +google-guava-lib = { strictly = '33.2.1-jre' } + +apache-httpcomponents-httpclient-lib = { strictly = '4.5.14' } # 5.3.1 would be better but has lot of API changes + +chimp-jtorctl-lib = { strictly = '9b5ba203' } apache-commons-lang-lib = { strictly = '3.14.0' } apache-httpcomponents-core-lib = { strictly = '4.4.16' } -apache-httpcomponents-httpclient-lib = { strictly = '4.5.14' } # 5.3.1 would be better but has lot of API changes apache-tomcat-annotations-api = { strictly = '6.0.53' } - assertj-core-lib = { strictly = '3.22.0' } -bouncycastle-lib = { strictly = '1.78.1' } - glassfish-jersey-lib = { strictly = '3.1.8' } glassfish-jaxb-runtime-lib = { strictly = '4.0.5' } - google-gson-lib = { strictly = '2.9.0' } -google-guava-lib = { strictly = '33.2.1-jre' } - grpc = { strictly = '1.61.0' } - i2p-lib = { strictly = '1.8.0' } i2p-v2 = { strictly = '2.4.0' } jackson-lib = { strictly = '2.17.2' } +lyricist = "1.7.0" + koin = "4.0.0" lombok-lib = { strictly = '1.18.34' } typesafe-config-lib = { strictly = '1.4.3' } [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlin-test-junit-v180 = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlinTestJunit" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } mock-io = { module = "io.mockk:mockk", version.ref = "mockio" } junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } -androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } androidx-multidex = { group = "androidx.multidex", name = "multidex", version.ref = "androidx-multidex" } androidx-test-compose = { group = "androidx.compose.ui", name = "ui-test-junit4-android", version.ref = "androidx-test-compose-ver" } androidx-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "androidx-test-compose-ver" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } -androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } -androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } + +#kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +#androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } +#androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } +#androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } +#androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } +#androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } +#androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } + androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } @@ -82,11 +89,11 @@ typesafe-config = { module = 'com.typesafe:config', version.ref = 'typesafe-conf bouncycastle = { module = 'org.bouncycastle:bcprov-jdk18on', version.ref = 'bouncycastle-lib' } bouncycastle-pg = { module = 'org.bouncycastle:bcpg-jdk18on', version.ref = 'bouncycastle-lib' } -#protobuf-lite = { group ="com.google.protobuf", name = "protobuf-javalite", version.ref = "protoblite"} +lyricist = { module = "cafe.adriel.lyricist:lyricist", version.ref = "lyricist" } +navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } protobuf-gradle-plugin = { module = 'com.google.protobuf:protobuf-gradle-plugin', version.ref = 'protobuf' } protoc = { module = "com.google.protobuf:protoc", version.ref = "protob" } - bisq-core-common = { module = "bisq:common", version.ref = "bisq-core" } bisq-core-security = { module = "bisq:security", version.ref = "bisq-core" } bisq-core-settings = { module = "bisq:settings", version.ref = "bisq-core" } @@ -110,36 +117,37 @@ bisq-core-identity = { module = "bisq:identity", version.ref = "bisq-core" } # bisq core transitive dependencies chimp-jsocks = { module = 'com.github.chimp1984:jsocks', version.ref = 'chimp-jsocks-lib' } -chimp-jtorctl = { module = 'com.github.chimp1984:jtorctl', version.ref = 'chimp-jtorctl-lib' } failsafe = { module = 'dev.failsafe:failsafe', version.ref = 'failsafe-lib' } -apache-commons-lang = { module = 'org.apache.commons:commons-lang3', version.ref = 'apache-commons-lang-lib' } -apache-httpcomponents-core = { module = 'org.apache.httpcomponents:httpcore', version.ref = 'apache-httpcomponents-core-lib' } apache-httpcomponents-httpclient = { module = 'org.apache.httpcomponents:httpclient', version.ref = 'apache-httpcomponents-httpclient-lib' } -apache-tomcat-annotations-api = { module = 'org.apache.tomcat:annotations-api', version.ref = 'apache-tomcat-annotations-api' } -assertj-core = { module = 'org.assertj:assertj-core', version.ref = 'assertj-core-lib' } -glassfish-jersey-jdk-http = { module = 'org.glassfish.jersey.containers:jersey-container-jdk-http', version.ref = 'glassfish-jersey-lib' } -glassfish-jersey-json-jackson = { module = 'org.glassfish.jersey.media:jersey-media-json-jackson', version.ref = 'glassfish-jersey-lib' } -glassfish-jersey-inject-hk2 = { module = 'org.glassfish.jersey.inject:jersey-hk2', version.ref = 'glassfish-jersey-lib' } -glassfish-jaxb-runtime = { module = 'org.glassfish.jaxb:jaxb-runtime', version.ref = 'glassfish-jaxb-runtime-lib' } - -google-gson = { module = 'com.google.code.gson:gson', version.ref = 'google-gson-lib' } google-guava = { module = 'com.google.guava:guava', version.ref = 'google-guava-lib' } -grpc-netty-shaded = { module = 'io.grpc:grpc-netty-shaded', version.ref = 'grpc' } -grpc-protobuf = { module = 'io.grpc:grpc-protobuf', version.ref = 'grpc' } -grpc-services = { module = 'io.grpc:grpc-services', version.ref = 'grpc' } -grpc-stub = { module = 'io.grpc:grpc-stub', version.ref = 'grpc' } - - -i2p-core = { module = 'net.i2p:i2p', version.ref = 'i2p-lib' } -i2p-core-v2 = { module = 'net.i2p:i2p', version.ref = 'i2p-v2' } -i2p-streaming = { module = 'net.i2p.client:streaming', version.ref = 'i2p-lib' } -i2p-streaming-v2 = { module = 'net.i2p.client:streaming', version.ref = 'i2p-v2' } -i2p-router = { module = 'net.i2p:router', version.ref = 'i2p-lib' } - -jackson-core = { module = 'com.fasterxml.jackson.core:jackson-core', version.ref = 'jackson-lib' } -jackson-annotations = { module = 'com.fasterxml.jackson.core:jackson-annotations', version.ref = 'jackson-lib' } -jackson-databind = { module = 'com.fasterxml.jackson.core:jackson-databind', version.ref = 'jackson-lib' } +#chimp-jtorctl = { module = 'com.github.chimp1984:jtorctl', version.ref = 'chimp-jtorctl-lib' } +#apache-commons-lang = { module = 'org.apache.commons:commons-lang3', version.ref = 'apache-commons-lang-lib' } +#apache-httpcomponents-core = { module = 'org.apache.httpcomponents:httpcore', version.ref = 'apache-httpcomponents-core-lib' } +#apache-tomcat-annotations-api = { module = 'org.apache.tomcat:annotations-api', version.ref = 'apache-tomcat-annotations-api' } +#assertj-core = { module = 'org.assertj:assertj-core', version.ref = 'assertj-core-lib' } +#glassfish-jersey-jdk-http = { module = 'org.glassfish.jersey.containers:jersey-container-jdk-http', version.ref = 'glassfish-jersey-lib' } +#glassfish-jersey-json-jackson = { module = 'org.glassfish.jersey.media:jersey-media-json-jackson', version.ref = 'glassfish-jersey-lib' } +#glassfish-jersey-inject-hk2 = { module = 'org.glassfish.jersey.inject:jersey-hk2', version.ref = 'glassfish-jersey-lib' } +#glassfish-jaxb-runtime = { module = 'org.glassfish.jaxb:jaxb-runtime', version.ref = 'glassfish-jaxb-runtime-lib' } +# +#google-gson = { module = 'com.google.code.gson:gson', version.ref = 'google-gson-lib' } + +#grpc-netty-shaded = { module = 'io.grpc:grpc-netty-shaded', version.ref = 'grpc' } +#grpc-protobuf = { module = 'io.grpc:grpc-protobuf', version.ref = 'grpc' } +#grpc-services = { module = 'io.grpc:grpc-services', version.ref = 'grpc' } +#grpc-stub = { module = 'io.grpc:grpc-stub', version.ref = 'grpc' } +# +# +#i2p-core = { module = 'net.i2p:i2p', version.ref = 'i2p-lib' } +#i2p-core-v2 = { module = 'net.i2p:i2p', version.ref = 'i2p-v2' } +#i2p-streaming = { module = 'net.i2p.client:streaming', version.ref = 'i2p-lib' } +#i2p-streaming-v2 = { module = 'net.i2p.client:streaming', version.ref = 'i2p-v2' } +#i2p-router = { module = 'net.i2p:router', version.ref = 'i2p-lib' } +# +#jackson-core = { module = 'com.fasterxml.jackson.core:jackson-core', version.ref = 'jackson-lib' } +#jackson-annotations = { module = 'com.fasterxml.jackson.core:jackson-annotations', version.ref = 'jackson-lib' } +#jackson-databind = { module = 'com.fasterxml.jackson.core:jackson-databind', version.ref = 'jackson-lib' } # koin koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } @@ -147,11 +155,11 @@ koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } [bundles] -glassfish-jersey = ['glassfish-jersey-jdk-http', 'glassfish-jersey-json-jackson', 'glassfish-jersey-inject-hk2', 'glassfish-jaxb-runtime'] -grpc = ['grpc-protobuf', 'grpc-services', 'grpc-stub'] -i2p = ['i2p-core', 'i2p-router', 'i2p-streaming'] -i2p-v2 = ['i2p-core-v2', 'i2p-streaming-v2'] -jackson = ['jackson-core', 'jackson-annotations', 'jackson-databind'] +#glassfish-jersey = ['glassfish-jersey-jdk-http', 'glassfish-jersey-json-jackson', 'glassfish-jersey-inject-hk2', 'glassfish-jaxb-runtime'] +#grpc = ['grpc-protobuf', 'grpc-services', 'grpc-stub'] +#i2p = ['i2p-core', 'i2p-router', 'i2p-streaming'] +#i2p-v2 = ['i2p-core-v2', 'i2p-streaming-v2'] +#jackson = ['jackson-core', 'jackson-annotations', 'jackson-databind'] [plugins] kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } @@ -162,3 +170,4 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = " kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } buildconfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildconfig" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf" } +ksp = { id = "com.google.devtools.ksp", version.ref="ksp" } diff --git a/bisqapps/gradle/wrapper/gradle-wrapper.jar b/bisqapps/gradle/wrapper/gradle-wrapper.jar index b498d244..a4b76b95 100644 Binary files a/bisqapps/gradle/wrapper/gradle-wrapper.jar and b/bisqapps/gradle/wrapper/gradle-wrapper.jar differ diff --git a/bisqapps/gradle/wrapper/gradle-wrapper.properties b/bisqapps/gradle/wrapper/gradle-wrapper.properties index df97d72b..94113f20 100644 --- a/bisqapps/gradle/wrapper/gradle-wrapper.properties +++ b/bisqapps/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/bisqapps/gradlew b/bisqapps/gradlew index 17a91706..f5feea6d 100755 --- a/bisqapps/gradlew +++ b/bisqapps/gradlew @@ -1,78 +1,130 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,96 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -if $JAVACMD --add-opens java.base/java.lang=ALL-UNNAMED -version ; then - DEFAULT_JVM_OPTS="--add-opens java.base/java.lang=ALL-UNNAMED $DEFAULT_JVM_OPTS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/bisqapps/gradlew.bat b/bisqapps/gradlew.bat index e95643d6..9b42019c 100644 --- a/bisqapps/gradlew.bat +++ b/bisqapps/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,25 +27,29 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,48 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift b/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift index c800a17a..5916a3b3 100644 --- a/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift +++ b/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift @@ -18,12 +18,22 @@ class LifecycleAwareComposeViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) presenter.onPause() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) presenter.onResume() + NotificationCenter.default.addObserver( + self, + selector: #selector(handleDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil) + } + + @objc private func handleDidBecomeActive() { + presenter.onResume() } override func viewWillAppear(_ animated: Bool) { diff --git a/bisqapps/shared/domain/build.gradle.kts b/bisqapps/shared/domain/build.gradle.kts index 68a37263..53d222bb 100644 --- a/bisqapps/shared/domain/build.gradle.kts +++ b/bisqapps/shared/domain/build.gradle.kts @@ -35,6 +35,7 @@ kotlin { //put your multiplatform dependencies here implementation(libs.koin.core) implementation(libs.kotlinx.coroutines) + } commonTest.dependencies { implementation(libs.kotlin.test) @@ -56,4 +57,4 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } -} \ No newline at end of file +} diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BisqStats.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BisqStats.kt new file mode 100644 index 00000000..2a5452da --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BisqStats.kt @@ -0,0 +1,16 @@ + +package network.bisq.mobile.domain.data.model + +class BisqStats: BaseModel() { + val offersOnline = 150 + + val publishedProfiles = 1275 +} + +interface BisqStatsFactory { + fun createBisqStats(): BisqStats +} + +class DefaultBisqStatsFactory : BisqStatsFactory { + override fun createBisqStats() = BisqStats() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BtcFiatPrice.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BtcFiatPrice.kt new file mode 100644 index 00000000..726092ad --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BtcFiatPrice.kt @@ -0,0 +1,17 @@ +package network.bisq.mobile.domain.data.model + +class BtcPrice: BaseModel() { + val prices: Map = mapOf( + "USD" to 64000.50, + "EUR" to 58000.75, + "GBP" to 52000.30, + ) +} + +interface BtcPriceFactory { + fun createBtcPrice(): BtcPrice +} + +class DefaultBtcPriceeFactory : BtcPriceFactory { + override fun createBtcPrice() = BtcPrice() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt index cde04698..ad6afba4 100644 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt @@ -2,6 +2,9 @@ package network.bisq.mobile.domain.data.model import network.bisq.mobile.domain.getPlatform +/** + * In general the models should remain closed, this is just an example from the time when we didn't have repositories and presenter + */ open class Greeting: BaseModel() { protected val platform = getPlatform() protected open val greetText = "Hello, ${platform.name}!" diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt new file mode 100644 index 00000000..7917724f --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Settings.kt @@ -0,0 +1,14 @@ +package network.bisq.mobile.domain.data.model + +open class Settings : BaseModel() { + open var bisqUrl: String = "" + open var isConnected: Boolean = false +} + +interface SettingsFactory { + fun createSettings(): Settings +} + +class DefaultSettingsFactory : SettingsFactory { + override fun createSettings() = Settings() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt new file mode 100644 index 00000000..b945806d --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/UserProfile.kt @@ -0,0 +1,13 @@ +package network.bisq.mobile.domain.data.model + +class UserProfile: BaseModel() { + var name = "" +} + +interface UserProfileFactory { + fun createUserProfile(): UserProfile +} + +class DefaultUserProfileFactory : UserProfileFactory { + override fun createUserProfile() = UserProfile() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt index fa593a7d..0ebf0612 100644 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt @@ -1,7 +1,11 @@ package network.bisq.mobile.domain.data.repository -import network.bisq.mobile.domain.data.model.Greeting +import network.bisq.mobile.domain.data.model.* // this way of definingsupports both platforms // add your repositories here and then in your DI module call this classes for instanciation open class GreetingRepository: SingleObjectRepository() +open class BisqStatsRepository: SingleObjectRepository() +open class BtcPriceRepository: SingleObjectRepository() +open class UserProfileRepository: SingleObjectRepository() +open class SettingsRepository: SingleObjectRepository() diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt index 583f629d..99374209 100644 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt @@ -1,9 +1,13 @@ package network.bisq.mobile.domain.di -import network.bisq.mobile.domain.data.model.Greeting -import network.bisq.mobile.domain.data.repository.GreetingRepository +import network.bisq.mobile.domain.data.model.* +import network.bisq.mobile.domain.data.repository.* import org.koin.dsl.module val domainModule = module { single> { GreetingRepository() } + single { BisqStatsRepository() } + single { BtcPriceRepository() } + single { UserProfileRepository() } + single { SettingsRepository() } } diff --git a/bisqapps/shared/presentation/build.gradle.kts b/bisqapps/shared/presentation/build.gradle.kts index f744cba1..02b64446 100644 --- a/bisqapps/shared/presentation/build.gradle.kts +++ b/bisqapps/shared/presentation/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.jetbrainsCompose) alias(libs.plugins.compose.compiler) alias(libs.plugins.buildconfig) + alias(libs.plugins.ksp) } dependencies { @@ -79,7 +80,7 @@ kotlin { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) + implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) @@ -90,6 +91,9 @@ kotlin { implementation(libs.koin.core) implementation(libs.koin.compose) + implementation(libs.navigation.compose) + implementation(libs.lyricist) + } val commonTest by getting { dependencies { @@ -114,3 +118,17 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } } + +dependencies { + add("kspCommonMainMetadata", "cafe.adriel.lyricist:lyricist-processor:1.7.0") +} + +tasks.withType>().all { + if(name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } +} + +kotlin.sourceSets.commonMain { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo.png new file mode 100644 index 00000000..42400b3e Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo_small.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo_small.png new file mode 100644 index 00000000..9912b281 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bisq_logo_small.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bitcoin.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bitcoin.png new file mode 100644 index 00000000..63084db4 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/bitcoin.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/compose-multiplatform.xml deleted file mode 100644 index c0bcfb28..00000000 --- a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/compose-multiplatform.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_euro.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_euro.png new file mode 100644 index 00000000..31d48fe0 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_euro.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_gpb.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_gpb.png new file mode 100644 index 00000000..643fb3b6 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_gpb.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_usd.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_usd.png new file mode 100644 index 00000000..f272f94e Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/currency_usd.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_bell.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_bell.png new file mode 100644 index 00000000..eda31a64 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_bell.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_chat_outlined.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_chat_outlined.png new file mode 100644 index 00000000..e6097f7f Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_chat_outlined.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_copy.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_copy.png new file mode 100644 index 00000000..97c75138 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_copy.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_home.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_home.png new file mode 100644 index 00000000..8e63f66d Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_home.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_market.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_market.png new file mode 100644 index 00000000..ef981d6c Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_market.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_qr.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_qr.png new file mode 100644 index 00000000..f8a9042a Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_qr.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_question_mark.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_question_mark.png new file mode 100644 index 00000000..98ede35a Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_question_mark.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_settings.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_settings.png new file mode 100644 index 00000000..5f87423e Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_settings.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_sort.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_sort.png new file mode 100644 index 00000000..180d48ae Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_sort.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_star_outlined.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_star_outlined.png new file mode 100644 index 00000000..634f9f1c Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_star_outlined.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_tag_outlined.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_tag_outlined.png new file mode 100644 index 00000000..9d6c95bc Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_tag_outlined.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_trades.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_trades.png new file mode 100644 index 00000000..be216578 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/icon_trades.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bisq_Easy.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bisq_Easy.png new file mode 100644 index 00000000..88ba938a Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bisq_Easy.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bot_image.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bot_image.png new file mode 100644 index 00000000..4f39c56b Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_bot_image.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_fiat_btc.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_fiat_btc.png new file mode 100644 index 00000000..067f9693 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_fiat_btc.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_learn_and_discover.png b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_learn_and_discover.png new file mode 100644 index 00000000..d05285fd Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/drawable/img_learn_and_discover.png differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_bold.ttf b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_bold.ttf new file mode 100644 index 00000000..e5389d83 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_bold.ttf differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_light.ttf b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_light.ttf new file mode 100755 index 00000000..b3d035d5 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_light.ttf differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_medium.ttf b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_medium.ttf new file mode 100755 index 00000000..9395402b Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_medium.ttf differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_regular.ttf b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_regular.ttf new file mode 100755 index 00000000..b5819647 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_regular.ttf differ diff --git a/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_thin.ttf b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_thin.ttf new file mode 100755 index 00000000..910458a9 Binary files /dev/null and b/bisqapps/shared/presentation/src/commonMain/composeResources/font/ibm_plex_sans_thin.ttf differ diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/EnStrings.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/EnStrings.kt new file mode 100644 index 00000000..5e432979 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/EnStrings.kt @@ -0,0 +1,149 @@ +package network.bisq.mobile.i18n + +import cafe.adriel.lyricist.LyricistStrings + +@LyricistStrings(languageTag = Locales.EN, default = true) +val EnStrings = Strings( + splash_details_tooltip = "Click to toggle details", + splash_applicationServiceState_INITIALIZE_APP = "Starting Bisq", + splash_applicationServiceState_INITIALIZE_NETWORK = "Initialize P2P network", + splash_applicationServiceState_INITIALIZE_WALLET = "Initialize wallet", + splash_applicationServiceState_INITIALIZE_SERVICES = "Initialize services", + splash_applicationServiceState_APP_INITIALIZED = "Bisq started", + splash_applicationServiceState_FAILED = "Startup failed", + splash_bootstrapState_service_CLEAR = "Server", + splash_bootstrapState_service_TOR = "Onion Service", + splash_bootstrapState_service_I2P = "I2P Service", + splash_bootstrapState_network_CLEAR = "Clear net", + splash_bootstrapState_network_TOR = "Tor", + splash_bootstrapState_network_I2P = "I2P", + splash_bootstrapState_BOOTSTRAP_TO_NETWORK = { networkType -> "Bootstrap to $networkType network" }, + splash_bootstrapState_START_PUBLISH_SERVICE = "Start publishing {0}", + splash_bootstrapState_SERVICE_PUBLISHED = "{0} published", + splash_bootstrapState_CONNECTED_TO_PEERS = "Connecting to peers", + tac_headline = "User Agreement", + tac_confirm = "I have read and understood", + tac_accept = "Accept user Agreement", + tac_reject = "Reject and quit application", + unlock_headline = "Enter password to unlock", + unlock_button = "Unlock", + unlock_failed = "Could not unlock with the provided password.\n\n Try again and be sure to have the caps lock disabled.", + updater_headline = "A new Bisq update is available", + updater_headline_isLauncherUpdate = "A new Bisq installer is available", + updater_releaseNotesHeadline = "Release notes for version {0}:", + updater_furtherInfo = "This update will be loaded after restart and does not require a new installation.\n More details can be found at the release page at:", + updater_furtherInfo_isLauncherUpdate = "This update requires a new Bisq installation.\n If you have problems when installing Bisq on macOS, please read the instructions at:", + updater_download = "Download and verify", + updater_downloadLater = "Download later", + updater_ignore = "Ignore this version", + updater_shutDown = "Shut down", + updater_shutDown_isLauncherUpdate = "Open download directory and shut down", + updater_downloadAndVerify_headline = "Download and verify new version", + updater_downloadAndVerify_info = "Once all files are downloaded, the signing key is compared with the keys provided in the application and those available on the Bisq website. This key is then used to verify the downloaded new version ('desktop.jar').", + updater_downloadAndVerify_info_isLauncherUpdate = "Once all files are downloaded, the signing key is compared with the keys provided in the application and those available on the Bisq website. This key is then used to verify the downloaded new Bisq installer. After download and verification are complete, navigate to the download directory to install the new Bisq version.", + updater_table_file = "File", + updater_table_progress = "Download progress", + updater_table_progress_completed = "Completed", + updater_table_verified = "Signature verified", + notificationPanel_trades_headline_single = "New trade message for trade ''{0}''", + notificationPanel_trades_headline_multiple = "New trade messages", + notificationPanel_trades_button = "Go to 'Open Trades'", + notificationPanel_mediationCases_headline_single = "New message for mediation case with trade ID ''{0}''", + notificationPanel_mediationCases_headline_multiple = "New messages for mediation", + notificationPanel_mediationCases_button = "Go to 'Mediator'", + onboarding_bisq2_headline = "Welcome to Bisq 2", + onboarding_bisq2_teaserHeadline1 = "Introducing Bisq Easy", + onboarding_bisq2_line1 = "Getting your first Bitcoin privately\n has never been easier.", + onboarding_bisq2_teaserHeadline2 = "Learn & discover", + onboarding_bisq2_line2 = "Get a gentle introduction into Bitcoin\n through our guides and community chat.", + onboarding_bisq2_teaserHeadline3 = "Coming soon", + onboarding_bisq2_line3 = "Choose how to trade: Bisq MuSig, Lightning, Submarine Swaps,...", + onboarding_button_create_profile = "Create profile", + onboarding_createProfile_headline = "Create your profile", + onboarding_createProfile_subTitle = "Your public profile consists of a nickname (picked by you) and a bot icon (generated cryptographically)", + onboarding_createProfile_nym = "Bot ID:", + onboarding_createProfile_regenerate = "Generate new bot icon", + onboarding_createProfile_nym_generating = "Calculating proof of work...", + onboarding_createProfile_createProfile = "Next", + onboarding_createProfile_createProfile_busy = "Initializing network node...", + onboarding_createProfile_nickName_prompt = "Choose your nickname", + onboarding_createProfile_nickName = "Profile nickname", + onboarding_createProfile_nickName_tooLong = "Nickname must not be longer than {0} characters", + onboarding_password_button_skip = "Skip", + onboarding_password_subTitle = "Set up password protection now or skip and do it later in 'User options/Password'.", + onboarding_password_headline_setPassword = "Set password protection", + onboarding_password_button_savePassword = "Save password", + onboarding_password_enterPassword = "Enter password (min. 8 characters)", + onboarding_password_confirmPassword = "Confirm password", + onboarding_password_savePassword_success = "Password protection enabled.", + navigation_dashboard = "Dashboard", + navigation_bisqEasy = "Bisq Easy", + navigation_reputation = "Reputation", + navigation_tradeApps = "Trade protocols", + navigation_wallet = "Wallet", + navigation_academy = "Learn", + navigation_chat = "Chat", + navigation_support = "Support", + navigation_userOptions = "User options", + navigation_settings = "Settings", + navigation_network = "Network", + navigation_authorizedRole = "Authorized role", + navigation_expandIcon_tooltip = "Expand menu", + navigation_collapseIcon_tooltip = "Minimize menu", + navigation_vertical_expandIcon_tooltip = "Expand sub menu", + navigation_vertical_collapseIcon_tooltip = "Collapse sub menu", + navigation_network_info_clearNet = "Clear-net", + navigation_network_info_tor = "Tor", + navigation_network_info_i2p = "I2P", + navigation_network_info_tooltip = "{0} network\n Number of connections: {1}\n Target connections: {2}", + navigation_network_info_inventoryRequest_requesting = "Requesting network data", + navigation_network_info_inventoryRequest_completed = "Network data received", + navigation_network_info_inventoryRequests_tooltip = "Network data request state:\n Number of pending requests: {0}\n Max. requests: {1}\n All data received: {2}", + topPanel_wallet_balance = "Balance", + dashboard_marketPrice = "Latest market price", + dashboard_offersOnline = "Offers online", + dashboard_activeUsers = "Published user profiles", + dashboard_activeUsers_tooltip = "Profiles stay published on the network\nif the user was online in the last 15 days.", + dashboard_main_headline = "Get your first BTC", + dashboard_main_content1 = "Start trading or browse open offers in the offerbook", + dashboard_main_content2 = "Chat based and guided user interface for trading", + dashboard_main_content3 = "Security is based on seller's reputation", + dashboard_main_button = "Enter Bisq Easy", + dashboard_second_headline = "Multiple trade protocols", + dashboard_second_content = "Check out the roadmap for upcoming trade protocols. Get an overview about the features of the different protocols.", + dashboard_second_button = "Explore trade protocols", + dashboard_third_headline = "Build up reputation", + dashboard_third_content = "You want to sell Bitcoin on Bisq Easy? Learn how the Reputation system works and why it is important.", + dashboard_third_button = "Build Reputation", + popup_headline_instruction = "Please note:", + popup_headline_attention = "Attention", + popup_headline_backgroundInfo = "Background information", + popup_headline_feedback = "Completed", + popup_headline_confirmation = "Confirmation", + popup_headline_information = "Information", + popup_headline_warning = "Warning", + popup_headline_invalid = "Invalid input", + popup_headline_error = "Error", + popup_reportBug = "Report bug to Bisq developers", + popup_reportError = "To help us to improve the software please report this bug by opening a new issue at: 'https://github.com/bisq-network/bisq2/issues'.\n The error message will be copied to the clipboard when you click the 'report' button below.\n\n It will make debugging easier if you include log files in your bug report. Log files do not contain sensitive data.", + popup_reportBug_report = "Bisq version: {0}\n Operating system: {1}\n Error message:\n {2}", + popup_reportError_log = "Open log file", + popup_reportError_zipLogs = "Zip log files", + popup_reportError_gitHub = "Report to Bisq GitHub repository", + popup_startup_error = "An error occurred at initializing Bisq: {0}.", + popup_shutdown = "Shut down is in process.\n\n It might take up to {0} seconds until shut down is completed.", + popup_shutdown_error = "An error occurred at shut down: {0}.", + popup_hyperlink_openInBrowser_tooltip = "Open link in browser: {0}.", + popup_hyperlink_copy_tooltip = "Copy link: {0}.", + hyperlinks_openInBrowser_attention_headline = "Open web link", + hyperlinks_openInBrowser_attention = "Do you want to open the link to `{0}` in your default web browser?", + hyperlinks_openInBrowser_no = "No, copy link", + hyperlinks_copiedToClipboard = "Link was copied to clipboard", + video_mp4NotSupported_warning_headline = "Embedded video cannot be played", + video_mp4NotSupported_warning = "You can watch the video in your browser at: [HYPERLINK:{0}]", + version_versionAndCommitHash = "Version: v{0} / Commit hash: {1}", + + buttons_next = "Next", + buttons_submit = "Submit", + buttons_cancel = "Cancel", +) diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/FrStrings.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/FrStrings.kt new file mode 100644 index 00000000..c4d5d745 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/FrStrings.kt @@ -0,0 +1,151 @@ +package network.bisq.mobile.i18n + +import cafe.adriel.lyricist.LyricistStrings + + +// Generated Locales.FR strings +@LyricistStrings(languageTag = Locales.FR) +val FRStrings = Strings( + splash_details_tooltip = "[FR] Click to toggle details", + splash_applicationServiceState_INITIALIZE_APP = "[FR] Starting Bisq", + splash_applicationServiceState_INITIALIZE_NETWORK = "[FR] Initialize P2P network", + splash_applicationServiceState_INITIALIZE_WALLET = "[FR] Initialize wallet", + splash_applicationServiceState_INITIALIZE_SERVICES = "[FR] Initialize services", + splash_applicationServiceState_APP_INITIALIZED = "[FR] Bisq started", + splash_applicationServiceState_FAILED = "[FR] Startup failed", + splash_bootstrapState_service_CLEAR = "[FR] Server", + splash_bootstrapState_service_TOR = "[FR] Onion Service", + splash_bootstrapState_service_I2P = "[FR] I2P Service", + splash_bootstrapState_network_CLEAR = "[FR] Clear net", + splash_bootstrapState_network_TOR = "[FR] Tor", + splash_bootstrapState_network_I2P = "[FR] I2P", + splash_bootstrapState_BOOTSTRAP_TO_NETWORK = { networkType -> "[FR] Bootstrap to $networkType network" }, + splash_bootstrapState_START_PUBLISH_SERVICE = "[FR] Start publishing {0}", + splash_bootstrapState_SERVICE_PUBLISHED = "[FR] {0} published", + splash_bootstrapState_CONNECTED_TO_PEERS = "[FR] Connecting to peers", + tac_headline = "[FR] User Agreement", + tac_confirm = "[FR] I have read and understood", + tac_accept = "[FR] Accept user Agreement", + tac_reject = "[FR] Reject and quit application", + unlock_headline = "[FR] Enter password to unlock", + unlock_button = "[FR] Unlock", + unlock_failed = "[FR] Could not unlock with the provided password.\n\n Try again and be sure to have the caps lock disabled.", + updater_headline = "[FR] A new Bisq update is available", + updater_headline_isLauncherUpdate = "[FR] A new Bisq installer is available", + updater_releaseNotesHeadline = "[FR] Release notes for version {0}:", + updater_furtherInfo = "[FR] This update will be loaded after restart and does not require a new installation.\n More details can be found at the release page at:", + updater_furtherInfo_isLauncherUpdate = "[FR] This update requires a new Bisq installation.\n If you have problems when installing Bisq on macOS, please read the instructions at:", + updater_download = "[FR] Download and verify", + updater_downloadLater = "[FR] Download later", + updater_ignore = "[FR] Ignore this version", + updater_shutDown = "[FR] Shut down", + updater_shutDown_isLauncherUpdate = "[FR] Open download directory and shut down", + updater_downloadAndVerify_headline = "[FR] Download and verify new version", + updater_downloadAndVerify_info = "[FR] Once all files are downloaded, the signing key is compared with the keys provided in the application and those available on the Bisq website. This key is then used to verify the downloaded new version ('desktop.jar').", + updater_downloadAndVerify_info_isLauncherUpdate = "[FR] Once all files are downloaded, the signing key is compared with the keys provided in the application and those available on the Bisq website. This key is then used to verify the downloaded new Bisq installer. After download and verification are complete, navigate to the download directory to install the new Bisq version.", + updater_table_file = "[FR] File", + updater_table_progress = "[FR] Download progress", + updater_table_progress_completed = "[FR] Completed", + updater_table_verified = "[FR] Signature verified", + notificationPanel_trades_headline_single = "[FR] New trade message for trade ''{0}''", + notificationPanel_trades_headline_multiple = "[FR] New trade messages", + notificationPanel_trades_button = "[FR] Go to 'Open Trades'", + notificationPanel_mediationCases_headline_single = "[FR] New message for mediation case with trade ID ''{0}''", + notificationPanel_mediationCases_headline_multiple = "[FR] New messages for mediation", + notificationPanel_mediationCases_button = "[FR] Go to 'Mediator'", + onboarding_bisq2_headline = "[FR] Welcome to Bisq 2", + onboarding_bisq2_teaserHeadline1 = "[FR] Introducing Bisq Easy", + onboarding_bisq2_line1 = "[FR] Getting your first Bitcoin privately\n has never been easier.", + onboarding_bisq2_teaserHeadline2 = "[FR] Learn & discover", + onboarding_bisq2_line2 = "[FR] Get a gentle introduction into Bitcoin\n through our guides and community chat.", + onboarding_bisq2_teaserHeadline3 = "[FR] Coming soon", + onboarding_bisq2_line3 = "[FR] Choose how to trade: Bisq MuSig, Lightning, Submarine Swaps,...", + onboarding_button_create_profile = "[FR] Create profile", + onboarding_createProfile_headline = "[FR] Create your profile", + onboarding_createProfile_subTitle = "[FR] Your public profile consists of a nickname (picked by you) and a bot icon (generated cryptographically)", + onboarding_createProfile_nym = "[FR] Bot ID:", + onboarding_createProfile_regenerate = "[FR] Generate new bot icon", + onboarding_createProfile_nym_generating = "[FR] Calculating proof of work...", + onboarding_createProfile_createProfile = "[FR] Next", + onboarding_createProfile_createProfile_busy = "[FR] Initializing network node...", + onboarding_createProfile_nickName_prompt = "[FR] Choose your nickname", + onboarding_createProfile_nickName = "[FR] Profile nickname", + onboarding_createProfile_nickName_tooLong = "[FR] Nickname must not be longer than {0} characters", + onboarding_password_button_skip = "[FR] Skip", + onboarding_password_subTitle = "[FR] Set up password protection now or skip and do it later in 'User options/Password'.", + onboarding_password_headline_setPassword = "[FR] Set password protection", + onboarding_password_button_savePassword = "[FR] Save password", + onboarding_password_enterPassword = "[FR] Enter password (min. 8 characters)", + onboarding_password_confirmPassword = "[FR] Confirm password", + onboarding_password_savePassword_success = "[FR] Password protection enabled.", + navigation_dashboard = "[FR] Dashboard", + navigation_bisqEasy = "[FR] Bisq Easy", + navigation_reputation = "[FR] Reputation", + navigation_tradeApps = "[FR] Trade protocols", + navigation_wallet = "[FR] Wallet", + navigation_academy = "[FR] Learn", + navigation_chat = "[FR] Chat", + navigation_support = "[FR] Support", + navigation_userOptions = "[FR] User options", + navigation_settings = "[FR] Settings", + navigation_network = "[FR] Network", + navigation_authorizedRole = "[FR] Authorized role", + navigation_expandIcon_tooltip = "[FR] Expand menu", + navigation_collapseIcon_tooltip = "[FR] Minimize menu", + navigation_vertical_expandIcon_tooltip = "[FR] Expand sub menu", + navigation_vertical_collapseIcon_tooltip = "[FR] Collapse sub menu", + navigation_network_info_clearNet = "[FR] Clear-net", + navigation_network_info_tor = "[FR] Tor", + navigation_network_info_i2p = "[FR] I2P", + navigation_network_info_tooltip = "[FR] {0} network\n Number of connections: {1}\n Target connections: {2}", + navigation_network_info_inventoryRequest_requesting = "[FR] Requesting network data", + navigation_network_info_inventoryRequest_completed = "[FR] Network data received", + navigation_network_info_inventoryRequests_tooltip = "[FR] Network data request state:\n Number of pending requests: {0}\n Max. requests: {1}\n All data received: {2}", + topPanel_wallet_balance = "[FR] Balance", + dashboard_marketPrice = "[FR] Latest market price", + dashboard_offersOnline = "[FR] Offers online", + dashboard_activeUsers = "[FR] Published user profiles", + dashboard_activeUsers_tooltip = "[FR] Profiles stay published on the network\nif the user was online in the last 15 days.", + dashboard_main_headline = "[FR] Get your first BTC", + dashboard_main_content1 = "[FR] Start trading or browse open offers in the offerbook", + dashboard_main_content2 = "[FR] Chat based and guided user interface for trading", + dashboard_main_content3 = "[FR] Security is based on seller's reputation", + dashboard_main_button = "[FR] Enter Bisq Easy", + dashboard_second_headline = "[FR] Multiple trade protocols", + dashboard_second_content = "[FR] Check out the roadmap for upcoming trade protocols. Get an overview about the features of the different protocols.", + dashboard_second_button = "[FR] Explore trade protocols", + dashboard_third_headline = "[FR] Build up reputation", + dashboard_third_content = "[FR] You want to sell Bitcoin on Bisq Easy? Learn how the Reputation system works and why it is important.", + dashboard_third_button = "[FR] Build Reputation", + popup_headline_instruction = "[FR] Please note:", + popup_headline_attention = "[FR] Attention", + popup_headline_backgroundInfo = "[FR] Background information", + popup_headline_feedback = "[FR] Completed", + popup_headline_confirmation = "[FR] Confirmation", + popup_headline_information = "[FR] Information", + popup_headline_warning = "[FR] Warning", + popup_headline_invalid = "[FR] Invalid input", + popup_headline_error = "[FR] Error", + popup_reportBug = "[FR] Report bug to Bisq developers", + popup_reportError = "[FR] To help us to improve the software please report this bug by opening a new issue at: 'https://github.com/bisq-network/bisq2/issues'.\n The error message will be copied to the clipboard when you click the 'report' button below.\n\n It will make debugging easier if you include log files in your bug report. Log files do not contain sensitive data.", + popup_reportBug_report = "[FR] Bisq version: {0}\n Operating system: {1}\n Error message:\n {2}", + popup_reportError_log = "[FR] Open log file", + popup_reportError_zipLogs = "[FR] Zip log files", + popup_reportError_gitHub = "[FR] Report to Bisq GitHub repository", + popup_startup_error = "[FR] An error occurred at initializing Bisq: {0}.", + popup_shutdown = "[FR] Shut down is in process.\n\n It might take up to {0} seconds until shut down is completed.", + popup_shutdown_error = "[FR] An error occurred at shut down: {0}.", + popup_hyperlink_openInBrowser_tooltip = "[FR] Open link in browser: {0}.", + popup_hyperlink_copy_tooltip = "[FR] Copy link: {0}.", + hyperlinks_openInBrowser_attention_headline = "[FR] Open web link", + hyperlinks_openInBrowser_attention = "[FR] Do you want to open the link to `{0}` in your default web browser?", + hyperlinks_openInBrowser_no = "[FR] No, copy link", + hyperlinks_copiedToClipboard = "[FR] Link was copied to clipboard", + video_mp4NotSupported_warning_headline = "[FR] Embedded video cannot be played", + video_mp4NotSupported_warning = "[FR] You can watch the video in your browser at: [HYPERLINK:{0}]", + version_versionAndCommitHash = "[FR] Version: v{0} / Commit hash: {1}", + + buttons_next = "[FR] Next", + buttons_submit = "[FR] Submit", + buttons_cancel = "[FR] Cancel", +) diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Locales.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Locales.kt new file mode 100644 index 00000000..635e3f11 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Locales.kt @@ -0,0 +1,43 @@ +package network.bisq.mobile.i18n + +/** + * An object that holds locale codes used for the application’s supported languages. + * + * Currently, this object supports English (`EN`) as the default language and French ('FR') only for testing. + * Additional languages can be added in the future. + * + * ## Usage + * ### Locales.kt + * Defines all the supported languages + * + * ### Strigs.kt + * A class that defines variable for all text resources used in the app. + * TODO: This is going to be an insanely huge file. Look for ways to modularize it (for later) + * + * ### {Lang}Strings.kt + * One file for each language. Assigns actual text for each variable in that specific language. + * + * ### Actual usage: + * 1. Each view should be enclosed by `ProviderStrings`. This is now done at top level in App.kt + * + * val lyricist = rememberStrings() + * ProvideStrings(lyricist) {} + * + * 2. And current language can be changed anytime by doing + * + * lyricist.languageTag = Locales.FR + * + * 3. In the views, string resources can be accessed by + * + * LocalStrings.current.{stringResourceName} + * + * + * ## References: + * For more detailed usage, please refer lyricist documentation @ + * https://github.com/adrielcafe/lyricist/ + * https://github.com/adrielcafe/lyricist/tree/main/sample-multiplatform/src/commonMain/kotlin/cafe/adriel/lyricist/sample/multiplatform (sample project) + */ +object Locales { + const val EN = "en" + const val FR = "fr" +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt new file mode 100644 index 00000000..b23042f6 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/i18n/Strings.kt @@ -0,0 +1,147 @@ +package network.bisq.mobile.i18n + +// TODO: Breakdown this later +data class Strings( + val splash_details_tooltip: String, + val splash_applicationServiceState_INITIALIZE_APP: String, + val splash_applicationServiceState_INITIALIZE_NETWORK: String, + val splash_applicationServiceState_INITIALIZE_WALLET: String, + val splash_applicationServiceState_INITIALIZE_SERVICES: String, + val splash_applicationServiceState_APP_INITIALIZED: String, + val splash_applicationServiceState_FAILED: String, + val splash_bootstrapState_service_CLEAR: String, + val splash_bootstrapState_service_TOR: String, + val splash_bootstrapState_service_I2P: String, + val splash_bootstrapState_network_CLEAR: String, + val splash_bootstrapState_network_TOR: String, + val splash_bootstrapState_network_I2P: String, + val splash_bootstrapState_BOOTSTRAP_TO_NETWORK: (String) -> String, + val splash_bootstrapState_START_PUBLISH_SERVICE: String, + val splash_bootstrapState_SERVICE_PUBLISHED: String, + val splash_bootstrapState_CONNECTED_TO_PEERS: String, + val tac_headline: String, + val tac_confirm: String, + val tac_accept: String, + val tac_reject: String, + val unlock_headline: String, + val unlock_button: String, + val unlock_failed: String, + val updater_headline: String, + val updater_headline_isLauncherUpdate: String, + val updater_releaseNotesHeadline: String, + val updater_furtherInfo: String, + val updater_furtherInfo_isLauncherUpdate: String, + val updater_download: String, + val updater_downloadLater: String, + val updater_ignore: String, + val updater_shutDown: String, + val updater_shutDown_isLauncherUpdate: String, + val updater_downloadAndVerify_headline: String, + val updater_downloadAndVerify_info: String, + val updater_downloadAndVerify_info_isLauncherUpdate: String, + val updater_table_file: String, + val updater_table_progress: String, + val updater_table_progress_completed: String, + val updater_table_verified: String, + val notificationPanel_trades_headline_single: String, + val notificationPanel_trades_headline_multiple: String, + val notificationPanel_trades_button: String, + val notificationPanel_mediationCases_headline_single: String, + val notificationPanel_mediationCases_headline_multiple: String, + val notificationPanel_mediationCases_button: String, + val onboarding_bisq2_headline: String, + val onboarding_bisq2_teaserHeadline1: String, + val onboarding_bisq2_line1: String, + val onboarding_bisq2_teaserHeadline2: String, + val onboarding_bisq2_line2: String, + val onboarding_bisq2_teaserHeadline3: String, + val onboarding_bisq2_line3: String, + val onboarding_button_create_profile: String, + val onboarding_createProfile_headline: String, + val onboarding_createProfile_subTitle: String, + val onboarding_createProfile_nym: String, + val onboarding_createProfile_regenerate: String, + val onboarding_createProfile_nym_generating: String, + val onboarding_createProfile_createProfile: String, + val onboarding_createProfile_createProfile_busy: String, + val onboarding_createProfile_nickName_prompt: String, + val onboarding_createProfile_nickName: String, + val onboarding_createProfile_nickName_tooLong: String, + val onboarding_password_button_skip: String, + val onboarding_password_subTitle: String, + val onboarding_password_headline_setPassword: String, + val onboarding_password_button_savePassword: String, + val onboarding_password_enterPassword: String, + val onboarding_password_confirmPassword: String, + val onboarding_password_savePassword_success: String, + val navigation_dashboard: String, + val navigation_bisqEasy: String, + val navigation_reputation: String, + val navigation_tradeApps: String, + val navigation_wallet: String, + val navigation_academy: String, + val navigation_chat: String, + val navigation_support: String, + val navigation_userOptions: String, + val navigation_settings: String, + val navigation_network: String, + val navigation_authorizedRole: String, + val navigation_expandIcon_tooltip: String, + val navigation_collapseIcon_tooltip: String, + val navigation_vertical_expandIcon_tooltip: String, + val navigation_vertical_collapseIcon_tooltip: String, + val navigation_network_info_clearNet: String, + val navigation_network_info_tor: String, + val navigation_network_info_i2p: String, + val navigation_network_info_tooltip: String, + val navigation_network_info_inventoryRequest_requesting: String, + val navigation_network_info_inventoryRequest_completed: String, + val navigation_network_info_inventoryRequests_tooltip: String, + val topPanel_wallet_balance: String, + val dashboard_marketPrice: String, + val dashboard_offersOnline: String, + val dashboard_activeUsers: String, + val dashboard_activeUsers_tooltip: String, + val dashboard_main_headline: String, + val dashboard_main_content1: String, + val dashboard_main_content2: String, + val dashboard_main_content3: String, + val dashboard_main_button: String, + val dashboard_second_headline: String, + val dashboard_second_content: String, + val dashboard_second_button: String, + val dashboard_third_headline: String, + val dashboard_third_content: String, + val dashboard_third_button: String, + val popup_headline_instruction: String, + val popup_headline_attention: String, + val popup_headline_backgroundInfo: String, + val popup_headline_feedback: String, + val popup_headline_confirmation: String, + val popup_headline_information: String, + val popup_headline_warning: String, + val popup_headline_invalid: String, + val popup_headline_error: String, + val popup_reportBug: String, + val popup_reportError: String, + val popup_reportBug_report: String, + val popup_reportError_log: String, + val popup_reportError_zipLogs: String, + val popup_reportError_gitHub: String, + val popup_startup_error: String, + val popup_shutdown: String, + val popup_shutdown_error: String, + val popup_hyperlink_openInBrowser_tooltip: String, + val popup_hyperlink_copy_tooltip: String, + val hyperlinks_openInBrowser_attention_headline: String, + val hyperlinks_openInBrowser_attention: String, + val hyperlinks_openInBrowser_no: String, + val hyperlinks_copiedToClipboard: String, + val video_mp4NotSupported_warning_headline: String, + val video_mp4NotSupported_warning: String, + val version_versionAndCommitHash: String, + + val buttons_next: String, + val buttons_submit: String, + val buttons_cancel: String, +) \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt index 5ee95101..1220047c 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/BasePresenter.kt @@ -1,15 +1,30 @@ package network.bisq.mobile.presentation import co.touchlab.kermit.Logger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel /** * Presenter for any type of view. * The view should define its own interface that the child presenter should implement as well, but * this class provide generic useful and common behaviour for presenters + * + * Base class allows to have a tree hierarchy of presenters. If the rootPresenter is null, this presenter acts as root + * if root present is passed, this present attach itself to the root to get updates (consequently its dependants will be always empty */ -abstract class BasePresenter { +abstract class BasePresenter(private val rootPresenter: MainPresenter?) { protected var view: Any? = null private val log = Logger.withTag("BasePresenter") + // Coroutine scope for the presenter + protected val presenterScope = CoroutineScope(Dispatchers.Main + Job()) + + private val dependants = if (isRoot()) mutableListOf() else null + + init { + rootPresenter?.registerChild(child = this) + } /** * This can be used as initialization method AFTER view gets attached (so view is available) @@ -21,32 +36,65 @@ abstract class BasePresenter { */ open fun onViewUnattaching() { } + /** + * This can be used to do cleanup when the view is getting destroyed + * Base Presenter corouting scope gets cancelled right before this method is called + */ + open fun onDestroying() { } + open fun onStart() { log.i { "Lifecycle: START" } + this.dependants?.forEach { it.onStart() } } open fun onResume() { log.i { "Lifecycle: RESUME" } + this.dependants?.forEach { it.onResume() } } open fun onPause() { log.i { "Lifecycle: PAUSE" } + this.dependants?.forEach { it.onPause() } } open fun onStop() { log.i { "Lifecycle: STOP" } + this.dependants?.forEach { it.onStop() } } - open fun onDestroy() { + fun onDestroy() { log.i { "Lifecycle: DESTROY" } + dependants?.forEach { it.onDestroy() } + rootPresenter?.unregisterChild(this) + presenterScope.cancel() + onDestroying() } fun attachView(view: Any) { + // at the moment the attach view is with the activity/ main view in ios + // unless we change this there is no point in sharing with dependents this.view = view log.i { "Lifecycle: View Attached from Presenter" } onViewAttached() } - open fun detachView() { + fun detachView() { onViewUnattaching() this.view = null log.i { "Lifecycle: View Dettached from Presenter" } } + + protected fun registerChild(child: BasePresenter) { + if (!isRoot()) { + throw IllegalStateException("You can't register to a non root presenter") + } + this.dependants!!.add(child) + } + + // TODO we need to test to find what are exactly the best places to register/unregister + protected fun unregisterChild(child: BasePresenter) { + if (!isRoot()) { + throw IllegalStateException("You can't unregister to a non root presenter") + } + this.dependants!!.remove(child) + } + + private fun isRoot() = rootPresenter == null } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt index b87dd2b5..82dcd922 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt @@ -15,13 +15,13 @@ import network.bisq.mobile.presentation.ui.AppPresenter /** * Main Presenter as an example of implementation for now. */ -open class MainPresenter(private val greetingRepository: GreetingRepository) : BasePresenter(), AppPresenter { +open class MainPresenter(private val greetingRepository: GreetingRepository) : BasePresenter(null), AppPresenter { private val log = Logger.withTag("MainPresenter") // Observable state private val _isContentVisible = MutableStateFlow(false) override val isContentVisible: StateFlow = _isContentVisible - // The following bounds the specific field I want to grab from the model using the stateflow to automatically observe updates + // TODO remove we don't need all the gretting example stuff anymore - but this code to passthrough should be a helper for presentation private val _greetingText: StateFlow = greetingRepository.data .map { it?.greet() ?: "" } // Transform Greeting to String, we don't want the null .stateIn( @@ -29,6 +29,7 @@ open class MainPresenter(private val greetingRepository: GreetingRepository = _greetingText init { @@ -49,4 +50,8 @@ open class MainPresenter(private val greetingRepository: GreetingRepository("RootNavController") } + single(named("TabNavController")) { getKoin().getProperty("TabNavController") } + single { MainPresenter(get()) } bind AppPresenter::class + + // TODO: Since NavController will be required for almost all Presenters for basic navigation + // Added this as top constructor level param. Is this okay? + single { + (navController: NavController) -> SplashPresenter( + get(), + navController = navController + ) + } bind ISplashPresenter::class + + single { (navController: NavController) -> OnBoardingPresenter(get(), navController) } bind IOnboardingPresenter::class + + single { + GettingStartedPresenter( + get(), + priceRepository = get(), + bisqStatsRepository = get() + ) + } bind IGettingStarted::class + + single { + (navController: NavController) -> CreateProfilePresenter( + get(), + navController = navController, + userProfileRepository = get() + ) + } bind ICreateProfilePresenter::class + + single { + (navController: NavController) -> TrustedNodeSetupPresenter( + get(), + navController = navController, + settingsRepository = get() + ) + } bind ITrustedNodeSetupPresenter::class } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt index 40e2505f..8e5aa6e1 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt @@ -1,22 +1,21 @@ package network.bisq.mobile.presentation.ui -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import org.jetbrains.compose.resources.painterResource +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import cafe.adriel.lyricist.ProvideStrings +import cafe.adriel.lyricist.rememberStrings import org.jetbrains.compose.ui.tooling.preview.Preview -import bisqapps.shared.presentation.generated.resources.Res -import bisqapps.shared.presentation.generated.resources.compose_multiplatform import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.i18n.Locales import org.koin.compose.koinInject +import network.bisq.mobile.presentation.ui.navigation.Routes + +import network.bisq.mobile.presentation.ui.navigation.graph.RootNavGraph +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.koin.mp.KoinPlatform.getKoin interface AppPresenter { // Observables for state @@ -33,21 +32,31 @@ interface AppPresenter { @Composable @Preview fun App() { + + val rootNavController = rememberNavController() + val tabNavController = rememberNavController() + + var isNavControllerSet by remember { mutableStateOf(false) } + + LaunchedEffect(rootNavController) { + getKoin().setProperty("RootNavController", rootNavController) + getKoin().setProperty("TabNavController", tabNavController) + isNavControllerSet = true + } + val presenter: AppPresenter = koinInject() - MaterialTheme { - // Collecting state from presenter - val showContent by presenter.isContentVisible.collectAsState() - val greeting by presenter.greetingText.collectAsState() - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Button(onClick = { presenter.toggleContentVisibility() }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } + + val lyricist = rememberStrings() + // lyricist.languageTag = Locales.FR + + BisqTheme(darkTheme = true) { + ProvideStrings(lyricist) { + if (isNavControllerSet) { + RootNavGraph( + startDestination = Routes.Splash.name + ) } } } + } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt new file mode 100644 index 00000000..3bfdcca4 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/CurrencyProfileCard.kt @@ -0,0 +1,55 @@ +package network.bisq.mobile.presentation.ui.components + +import androidx.compose.foundation.Image +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.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun CurrencyProfileCard(currencyName: String, currencyShort: String, image: DrawableResource) { + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image(painterResource(image), null, modifier = Modifier.size(36.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Column { + BisqText.baseRegular( + text = currencyName, + color = BisqTheme.colors.light1, + ) + Spacer(modifier = Modifier.height(8.dp)) + BisqText.baseRegular( + text = currencyShort, + color = BisqTheme.colors.grey2, + ) + } + } + BisqText.smallRegular( + text = "43 offers", + color = BisqTheme.colors.primary, + ) + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/MaterialTextField.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/MaterialTextField.kt new file mode 100644 index 00000000..d6441498 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/MaterialTextField.kt @@ -0,0 +1,75 @@ +package network.bisq.mobile.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.* + +@Composable +fun MaterialTextField(text: String, placeholder: String = "", onValueChanged: (String) -> Unit) { + var isFocused by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .clip(shape = RoundedCornerShape(6.dp)) + .background(color = BisqTheme.colors.secondary) + ) { + TextField( + value = text, + singleLine = true, + modifier = Modifier.fillMaxWidth().clickable { isFocused = true } + .onFocusChanged { focusState -> + isFocused = focusState.isFocused + }, + textStyle = TextStyle(fontSize = 22.sp), + onValueChange = onValueChanged, + colors = TextFieldDefaults.colors( + focusedTextColor = BisqTheme.colors.light3, + unfocusedTextColor = BisqTheme.colors.secondaryHover, + unfocusedIndicatorColor = BisqTheme.colors.secondary, + focusedIndicatorColor = Color.Transparent, + focusedContainerColor = BisqTheme.colors.secondary, + cursorColor = Color.Blue, + unfocusedContainerColor = BisqTheme.colors.secondary + ), + placeholder = { + BisqText.h5Regular( + text = placeholder, + color = BisqTheme.colors.secondaryHover, + ) + } + ) + if (isFocused) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .align(Alignment.BottomCenter) + .background(BisqTheme.colors.primary) + ) + } + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt new file mode 100644 index 00000000..4aa6527e --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Button.kt @@ -0,0 +1,47 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +// TODO: +// leftIcon, rightIcon -> Not happening right +@Composable +fun BisqButton( + text: String, + onClick: () -> Unit, + color: Color = BisqTheme.colors.light1, + backgroundColor: Color = BisqTheme.colors.primary, + padding: PaddingValues = PaddingValues(horizontal = 48.dp, vertical = 4.dp), + leftIcon: Unit? = null, + rightIcon: Unit? = null, + modifier: Modifier = Modifier +) { + + // Apply proper rounded corner + Button( + onClick = { onClick() }, + contentPadding = padding, + colors = ButtonColors( + containerColor = backgroundColor, + disabledContainerColor = backgroundColor, + contentColor = color, + disabledContentColor = color) + ) { + Row { + if(leftIcon != null) leftIcon + if(leftIcon != null) Spacer(modifier = Modifier.width(10.dp)) + BisqText.baseMedium( + text = text, + color = BisqTheme.colors.light1, + ) + if(rightIcon != null) Spacer(modifier = Modifier.width(10.dp)) + if(rightIcon != null) rightIcon + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProgressBar.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProgressBar.kt new file mode 100644 index 00000000..fff981cc --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/ProgressBar.kt @@ -0,0 +1,41 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.ProgressIndicatorDefaults +import androidx.compose.material3.ProgressIndicatorDefaults.drawStopIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +@Composable +fun BisqProgressBar( + progress: Float, + modifier: Modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 100.dp) + .padding(bottom = 20.dp) + .height(2.dp) +) { + + val grey2Color = BisqTheme.colors.grey2 + + LinearProgressIndicator( + progress = {progress}, + modifier = modifier, + trackColor = BisqTheme.colors.grey2, + color = BisqTheme.colors.primary, + gapSize = 0.dp, + drawStopIndicator = { + drawStopIndicator( + drawScope = this, + stopSize = 0.dp, + color = grey2Color, + strokeCap = ProgressIndicatorDefaults.LinearStrokeCap + ) + } + ) +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt new file mode 100644 index 00000000..2d77c639 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/Text.kt @@ -0,0 +1,756 @@ +package network.bisq.mobile.presentation.ui.components.atoms + +import androidx.compose.runtime.Composable +import androidx.compose.material3.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.resources.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_thin +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_light +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_regular +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_medium +import bisqapps.shared.presentation.generated.resources.ibm_plex_sans_bold +import network.bisq.mobile.presentation.ui.theme.BisqTheme + +enum class FontWeight { + LIGHT, + REGULAR, + MEDIUM, + BOLD, +} + +enum class FontSize(val size: TextUnit) { + XSMALL(10.sp), + SMALL(12.sp), + BASE(14.sp), + LARGE(16.sp), + H6(18.sp), + H5(20.sp), + H4(22.sp), + H3(25.sp), + H2(28.sp), + H1(32.sp); +} + +object BisqText { + @Composable + fun styledText( + text: String, + color: Color = BisqTheme.colors.light1, + fontSize: FontSize = FontSize.BASE, + fontWeight: FontWeight = FontWeight.REGULAR, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + + val fontFamily = when(fontWeight) { + FontWeight.LIGHT -> FontFamily(Font(Res.font.ibm_plex_sans_light)) + FontWeight.REGULAR -> FontFamily(Font(Res.font.ibm_plex_sans_regular)) + FontWeight.MEDIUM -> FontFamily(Font(Res.font.ibm_plex_sans_medium)) + FontWeight.BOLD -> FontFamily(Font(Res.font.ibm_plex_sans_bold)) + } + + return Text( + text = text, + color = color, + fontSize = fontSize.size, + fontFamily = fontFamily, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun xsmallLight( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.XSMALL, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun xsmallRegular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.XSMALL, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun xsmallMedium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.XSMALL, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun xsmallBold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.XSMALL, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun smallLight( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.SMALL, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun smallRegular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.SMALL, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun smallMedium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.SMALL, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun smallBold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.SMALL, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun baseLight( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.BASE, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun baseRegular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.BASE, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun baseMedium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.BASE, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun baseBold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.BASE, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun largeLight( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.LARGE, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun largeRegular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.LARGE, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun largeMedium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.LARGE, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun largeBold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.LARGE, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h6Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H6, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h6Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H6, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h6Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H6, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h6Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H6, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h5Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H5, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h5Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H5, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h5Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H5, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h5Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H5, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h4Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H4, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h4Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H4, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h4Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H4, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h4Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H4, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h3Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H3, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h3Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H3, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h3Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H3, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h3Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H3, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h2Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H2, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h2Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H2, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h2Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H2, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h2Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H2, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + + @Composable + fun h1Light( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H1, + fontWeight = FontWeight.LIGHT, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h1Regular( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H1, + fontWeight = FontWeight.REGULAR, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h1Medium( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H1, + fontWeight = FontWeight.MEDIUM, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } + + @Composable + fun h1Bold( + text: String, + color: Color = BisqTheme.colors.light1, + textAlign: TextAlign = TextAlign.Start, + modifier: Modifier = Modifier, + ) { + styledText( + text = text, + fontSize = FontSize.H1, + fontWeight = FontWeight.BOLD, + color=color, + textAlign = textAlign, + modifier = modifier, + ) + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt new file mode 100644 index 00000000..4b0551ef --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Icons.kt @@ -0,0 +1,24 @@ +package network.bisq.mobile.presentation.ui.components.atoms.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.* +import org.jetbrains.compose.resources.painterResource + +@Composable +fun BellIcon(modifier: Modifier = Modifier) { + Image(painterResource(Res.drawable.icon_bell), "Bell icon", modifier = modifier) +} + +@Composable +fun UserIcon(modifier: Modifier = Modifier) { + Image(painterResource(Res.drawable.img_bot_image), "User icon", modifier = modifier) +} + + +@Composable +fun SortIcon(modifier: Modifier = Modifier) { + Image(painterResource(Res.drawable.icon_sort), "Sort icon", modifier = modifier) +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt new file mode 100644 index 00000000..fcbf0f18 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/icons/Logo.kt @@ -0,0 +1,19 @@ +package network.bisq.mobile.presentation.ui.components.atoms.icons + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.bisq_logo +import bisqapps.shared.presentation.generated.resources.bisq_logo_small +import org.jetbrains.compose.resources.painterResource + +@Composable +fun BisqLogo(modifier: Modifier = Modifier) { + Image(painterResource(Res.drawable.bisq_logo), "Bisq Logo", modifier = modifier) +} + +@Composable +fun BisqLogoSmall(modifier: Modifier = Modifier) { + Image(painterResource(Res.drawable.bisq_logo_small), "Bisq Logo small", modifier = modifier) +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollLayout.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollLayout.kt new file mode 100644 index 00000000..e276d6be --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/ScrollLayout.kt @@ -0,0 +1,33 @@ +package network.bisq.mobile.presentation.ui.components.layout + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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 + +@Composable +fun BisqScrollLayout( + innerPadding: PaddingValues = PaddingValues(top = 24.dp, bottom = 12.dp, start = 12.dp, end = 12.dp), + content: @Composable ColumnScope.() -> Unit +) { + Scaffold( + containerColor = BisqTheme.colors.backgroundColor, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .background(color = BisqTheme.colors.backgroundColor) + .padding(innerPadding) + .verticalScroll(rememberScrollState()) + ) { + content() + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt new file mode 100644 index 00000000..3e2b6643 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/layout/StaticLayout.kt @@ -0,0 +1,31 @@ +package network.bisq.mobile.presentation.ui.components.layout + +import androidx.compose.foundation.background +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 + +@Composable +fun BisqStaticLayout( + innerPadding: PaddingValues = PaddingValues(top = 48.dp, bottom = 30.dp), + content: @Composable ColumnScope.() -> Unit +) { + Scaffold( + containerColor = BisqTheme.colors.backgroundColor, + ) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .background(color = BisqTheme.colors.backgroundColor) + .padding(innerPadding) + ) { + content() + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt new file mode 100644 index 00000000..9f0b2d01 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt @@ -0,0 +1,49 @@ +package network.bisq.mobile.presentation.ui.components.molecules + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +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.components.atoms.icons.BellIcon +import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogoSmall +import network.bisq.mobile.presentation.ui.components.atoms.icons.UserIcon +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalResourceApi::class) +@Composable +fun TopBar(title: String = "",isHome:Boolean = false) { + TopAppBar( + modifier = Modifier.padding(horizontal = 16.dp).padding(end = 16.dp), + colors = TopAppBarDefaults.topAppBarColors( + containerColor = BisqTheme.colors.backgroundColor, + ), + title = { + if (isHome) { + BisqLogoSmall(modifier = Modifier.height(34.dp).width(100.dp),) + } else { + BisqText.h4Medium( + text = title, + color = BisqTheme.colors.light1, + ) + } + }, + actions = { + Row(verticalAlignment = Alignment.CenterVertically) { + BellIcon(modifier = Modifier.size(30.dp)) + Spacer(modifier = Modifier.width(12.dp)) + UserIcon(modifier = Modifier.size(30.dp)) + } + }, + ) +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt new file mode 100644 index 00000000..f5e9f215 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/composeModels/model.kt @@ -0,0 +1,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 OnBoardingPage(val title: String, val image: DrawableResource, val desc: String) diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/BottomNavigation.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/BottomNavigation.kt new file mode 100644 index 00000000..356317c8 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/BottomNavigation.kt @@ -0,0 +1,62 @@ +package network.bisq.mobile.presentation.ui.navigation + +import androidx.compose.foundation.Image +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.size +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemColors +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.graphics.ColorFilter +import androidx.compose.ui.unit.dp +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.composeModels.BottomNavigationItem +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun BottomNavigation( + items: List, + currentRoute: String, + onItemClick: (BottomNavigationItem) -> Unit +) { + + NavigationBar( + containerColor = BisqTheme.colors.backgroundColor + ) { + items.forEach { navigationItem -> + NavigationBarItem( + colors = NavigationBarItemColors( + selectedIndicatorColor = BisqTheme.colors.backgroundColor, + selectedIconColor = BisqTheme.colors.primary, + selectedTextColor = BisqTheme.colors.primary, + unselectedIconColor = Color.White, + unselectedTextColor = Color.White, + disabledIconColor = Color.Red, + disabledTextColor = Color.Red + ), + interactionSource = remember { MutableInteractionSource() }, + selected = currentRoute == navigationItem.route, + onClick = { onItemClick(navigationItem) }, + icon = { + Image( + painterResource(navigationItem.icon), "", + modifier = Modifier.size(24.dp), + colorFilter = ColorFilter.tint(color = if (navigationItem.route == currentRoute) BisqTheme.colors.primary else Color.White) + ) + }, + label = { + BisqText.baseRegular( + text = navigationItem.title, + color = if (navigationItem.route == currentRoute) BisqTheme.colors.primary else BisqTheme.colors.light1, + ) + } + ) + } + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/README.md b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/README.md new file mode 100644 index 00000000..6c46b5dd --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/README.md @@ -0,0 +1,28 @@ +# Navigation Overview + +The app uses **Jetpack Compose Navigation**. + +### 1. Routes +- All routes are defined in `Routes.kt` as an `enum` for unique screen identifiers. +- Like + - /splash + - /onboarding + + +### 2. Root Navigation +- `RootNavGraph.kt` defines the top-level flow using `NavHost`. +- Maps routes to Screens with transitions like + - /splash - SplashScreen + - /onboarding -> OnboardingScreen + +### 3. Tab Navigation +- `TabNavGraph.kt` manages nested tab-based screens (`Home`, `Exchange`, `MyTrades`, `Settings`). + +### 4. Bottom Navigation +- Composable that renders the bottom navigation bar. +- Exact tabbar items are received via `items` prop. + +Ref links: + - https://developer.android.com/develop/ui/compose/navigation + - https://developer.android.com/codelabs/basic-android-kotlin-compose-navigation#0 + - https://medium.com/@KaushalVasava/navigation-in-jetpack-compose-full-guide-beginner-to-advanced-950c1133740 diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt new file mode 100644 index 00000000..25dc0b40 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/Routes.kt @@ -0,0 +1,19 @@ +package network.bisq.mobile.presentation.ui.navigation + +import org.jetbrains.compose.resources.StringResource + +object Graph { + const val MAIN_SCREEN_GRAPH_KEY = "mainScreenGraph" +} + +enum class Routes(val title: String) { + Splash(title = "splash"), + Onboarding(title = "onboarding"), + CreateProfile(title = "create_profile"), + TrustedNodeSetup(title = "trusted_node_setup"), + TabContainer(title = "tab_container"), + TabHome(title = "tab_home"), + TabExchange(title = "tab_exchange"), + TabMyTrades(title = "tab_my_trades"), + TabSettings(title = "tab_settings"), +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt new file mode 100644 index 00000000..6dd1f2c4 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/RootNavGraph.kt @@ -0,0 +1,72 @@ +package network.bisq.mobile.presentation.ui.navigation.graph + +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +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.startup.CreateProfileScreen +import network.bisq.mobile.presentation.ui.uicases.startup.OnBoardingScreen +import network.bisq.mobile.presentation.ui.uicases.startup.SplashScreen +import network.bisq.mobile.presentation.ui.uicases.startup.TrustedNodeSetupScreen +import org.koin.compose.koinInject +import org.koin.core.qualifier.named + +@Composable +fun RootNavGraph( + startDestination: String +) { + val navControllerDIName = if (startDestination == Routes.Splash.name) "RootNavController" else "TabNavController" + val navController: NavHostController = koinInject(named(navControllerDIName)) + + //TODO: [Need refactor] This is confusing / not proper. But for now, it works! + //To have both primary screens and Tab bar screens inside a single NavHost. + //At runtime, 2 actually instances are created. + //If the code also reflects the same, it will be even easy to understand. + + NavHost( + modifier = Modifier.background(color = BisqTheme.colors.backgroundColor), + navController = navController, + startDestination = startDestination, + ) { + composable(route = Routes.Splash.name) { + SplashScreen() + } + composable(route = Routes.Onboarding.name, enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(300) + ) + }) { + OnBoardingScreen() + } + composable(route = Routes.CreateProfile.name, enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(300) + ) + }) { + CreateProfileScreen() + } + composable(route = Routes.TrustedNodeSetup.name, enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween(300) + ) + }) { + TrustedNodeSetupScreen() + } + composable(route = Routes.TabContainer.name) { + TabContainerScreen() + } + TabNavGraph() + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/TabNavGraph.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/TabNavGraph.kt new file mode 100644 index 00000000..4a2aaf61 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/navigation/graph/TabNavGraph.kt @@ -0,0 +1,32 @@ +package network.bisq.mobile.presentation.ui.navigation.graph + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import network.bisq.mobile.presentation.ui.navigation.Routes +import network.bisq.mobile.presentation.ui.navigation.Graph +import network.bisq.mobile.presentation.ui.uicases.GettingStartedScreen +import network.bisq.mobile.presentation.ui.uicases.exchange.ExchangeScreen +import network.bisq.mobile.presentation.ui.uicases.settings.SettingsScreen +import network.bisq.mobile.presentation.ui.uicases.trades.MyTradesScreen + +fun NavGraphBuilder.TabNavGraph() { + navigation( + startDestination = Routes.TabHome.name, + route = Graph.MAIN_SCREEN_GRAPH_KEY + ) { + composable(route = Routes.TabHome.name) { + GettingStartedScreen() + } + composable(route = Routes.TabExchange.name) { + ExchangeScreen() + } + composable(route = Routes.TabMyTrades.name) { + MyTradesScreen() + } + composable(route = Routes.TabSettings.name) { + SettingsScreen() + } + } + +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqColor.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqColor.kt new file mode 100644 index 00000000..e4fb14de --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqColor.kt @@ -0,0 +1,137 @@ +package network.bisq.mobile.presentation.ui.theme + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +@Immutable +data class BisqColors ( + val dark1: Color, + val dark2: Color, + val dark3: Color, + val dark4: Color, + val dark5: Color, + val light1: Color, + val light2: Color, + val light3: Color, + val light4: Color, + val light5: Color, + val grey1: Color, + val grey2: Color, + val grey3: Color, + val grey4: Color, + val grey5: Color, + val primary: Color, + val primaryHover: Color, + val primaryDisabled: Color, + val primary2: Color, + val secondary: Color, + val secondaryHover: Color, + val secondaryDisabled: Color, + val danger: Color, + val dangerHover: Color, + val dangerDisabled: Color, + val warning: Color, + val warningHover: Color, + val warningDisabled: Color, + val backgroundColor: Color, +) + + +val LocalBisqColors = staticCompositionLocalOf { + BisqColors( + dark1 = Color.Unspecified, + dark2 = Color.Unspecified, + dark3 = Color.Unspecified, + dark4 = Color.Unspecified, + dark5 = Color.Unspecified, + light1 = Color.Unspecified, + light2 = Color.Unspecified, + light3 = Color.Unspecified, + light4 = Color.Unspecified, + light5 = Color.Unspecified, + grey1 = Color.Unspecified, + grey2 = Color.Unspecified, + grey3 = Color.Unspecified, + grey4 = Color.Unspecified, + grey5 = Color.Unspecified, + primary = Color.Unspecified, + primaryHover = Color.Unspecified, + primaryDisabled = Color.Unspecified, + primary2 = Color.Unspecified, + secondary = Color.Unspecified, + secondaryHover = Color.Unspecified, + secondaryDisabled = Color.Unspecified, + danger = Color.Unspecified, + dangerHover = Color.Unspecified, + dangerDisabled = Color.Unspecified, + warning = Color.Unspecified, + warningHover = Color.Unspecified, + warningDisabled = Color.Unspecified, + backgroundColor = Color.Unspecified, + ) +} + +val lightColors = BisqColors( + dark1 = Color(0xFFFFFFFF), + dark2 = Color(0xFFF8F8F8), + dark3 = Color(0xFFF4F4F4), + dark4 = Color(0xFFF0F0F0), + dark5 = Color(0xFFEFEFEF), + light1 = Color(0xFF1D1D1D), + light2 = Color(0xFF212121), + light3 = Color(0xFF262626), + light4 = Color(0xFF282828), + light5 = Color(0xFF333333), + grey1 = Color(0xFF999999), + grey2 = Color(0xFF747474), + grey3 = Color(0xFF6B6B6B), + grey4 = Color(0xFF515151), + grey5 = Color(0xFF4F4F4F), + primary = Color(0xFF25B135), + primaryHover = Color(0xFF56C262), + primaryDisabled = Color(0x6625B135), + primary2 = Color(0xFF0A2F0F), + secondary = Color(0xFF2F2F2F), + secondaryHover = Color(0xFF525252), + secondaryDisabled = Color(0x662F2F2F), + danger = Color(0xFFDB0000), + dangerHover = Color(0xFFAC2B2B), + dangerDisabled = Color(0x66DB0000), + warning = Color(0xFFFF9823), + warningHover = Color(0xFFFFAC4E), + warningDisabled = Color(0xB3FF9823), + backgroundColor = Color(0xFF1C1C1C), +) + +val darkColors = BisqColors( + dark1 = Color(0xFF1D1D1D), + dark2 = Color(0xFF212121), + dark3 = Color(0xFF262626), + dark4 = Color(0xFF282828), + dark5 = Color(0xFF333333), + light1 = Color(0xFFFFFFFF), + light2 = Color(0xFFF8F8F8), + light3 = Color(0xFFF4F4F4), + light4 = Color(0xFFF0F0F0), + light5 = Color(0xFFEFEFEF), + grey1 = Color(0xFF999999), + grey2 = Color(0xFF747474), + grey3 = Color(0xFF6B6B6B), + grey4 = Color(0xFF515151), + grey5 = Color(0xFF4F4F4F), + primary = Color(0xFF25B135), + primaryHover = Color(0xFF56C262), + primaryDisabled = Color(0x6625B135), + primary2 = Color(0xFF0A2F0F), + secondary = Color(0xFF2F2F2F), + secondaryHover = Color(0xFF525252), + secondaryDisabled = Color(0x662F2F2F), + danger = Color(0xFFDB0000), + dangerHover = Color(0xFFAC2B2B), + dangerDisabled = Color(0x66DB0000), + warning = Color(0xFFFF9823), + warningHover = Color(0xFFFFAC4E), + warningDisabled = Color(0xB3FF9823), + backgroundColor = Color(0xFF1C1C1C), +) diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqTheme.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqTheme.kt new file mode 100755 index 00000000..ae567cc3 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/theme/BisqTheme.kt @@ -0,0 +1,30 @@ +package network.bisq.mobile.presentation.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider + +@Composable +fun BisqTheme( + darkTheme: Boolean = true, + content: @Composable () -> Unit +) { + + val extendedColors = if (darkTheme) { + darkColors + } else { + lightColors + } + + CompositionLocalProvider(LocalBisqColors provides extendedColors) { + MaterialTheme( + content = content + ) + } +} + +object BisqTheme { + val colors: BisqColors + @Composable + get() = LocalBisqColors.current +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt new file mode 100644 index 00000000..4739d0f6 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedPresenter.kt @@ -0,0 +1,54 @@ +package network.bisq.mobile.presentation.ui.uicases + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import network.bisq.mobile.domain.data.repository.BisqStatsRepository +import network.bisq.mobile.domain.data.repository.BtcPriceRepository +import network.bisq.mobile.presentation.BasePresenter +import network.bisq.mobile.presentation.MainPresenter + +class GettingStartedPresenter( + mainPresenter: MainPresenter, + private val priceRepository: BtcPriceRepository, + private val bisqStatsRepository: BisqStatsRepository +) : BasePresenter(mainPresenter), IGettingStarted { + private val _btcPrice = MutableStateFlow("Loading...")//("$75,000") + override val btcPrice: StateFlow = _btcPrice + + private val _offersOnline = MutableStateFlow(145) + override val offersOnline: StateFlow = _offersOnline + + private val _publishedProfiles = MutableStateFlow(1145) + override val publishedProfiles: StateFlow = _publishedProfiles + + private fun refresh() { + CoroutineScope(Dispatchers.IO).launch { + try { + val bisqStats = bisqStatsRepository.fetch() + _offersOnline.value = bisqStats?.offersOnline ?: 0 + _publishedProfiles.value = bisqStats?.publishedProfiles ?: 0 + + val btcPrice = priceRepository.fetch() + val priceList = btcPrice?.prices + _btcPrice.value = (priceList?.get("USD") ?: 0).toString() + } catch (e: Exception) { + // Handle errors + println("Error: ${e.message}") + } + } + } + + override fun onViewAttached() { + super.onViewAttached() + refresh() + } + + override fun onResume() { + super.onResume() + refresh() + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt new file mode 100644 index 00000000..65fd1008 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/GettingStartedScreen.kt @@ -0,0 +1,257 @@ +package network.bisq.mobile.presentation.ui.uicases + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import bisqapps.shared.presentation.generated.resources.* +import bisqapps.shared.presentation.generated.resources.Res +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.ui.components.molecules.TopBar +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.* +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.koin.compose.koinInject + +interface IGettingStarted { + val btcPrice: StateFlow + val offersOnline: StateFlow + val publishedProfiles: StateFlow +} + +@Composable +fun GettingStartedScreen() { + val presenter: IGettingStarted = koinInject() + val btcPrice:String = presenter.btcPrice.collectAsState().value + val offersOnline:Number = presenter.offersOnline.collectAsState().value + val publishedProfiles:Number = presenter.publishedProfiles.collectAsState().value + + // TODO attach view should happen here to let the presenter know? + + Column( + modifier = Modifier.fillMaxSize(), + ) { + // TODO: Should be a child of Scaffold, in TabContainerScreen + TopBar(isHome = true) + + // TODO: Should use BisqScrollLayout. But it has Scaffold inside it already! + Column( + modifier = Modifier.padding(horizontal = 32.dp, vertical = 15.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + + Column { + PriceProfileCard( + price = btcPrice, + priceText = "Market price" + ) + Spacer(modifier = Modifier.height(16.dp)) + Row(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier.weight(1f)) { + PriceProfileCard( + price = offersOnline.toString(), + priceText = "Offers online" + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Box(modifier = Modifier.weight(1f)) { + PriceProfileCard( + price = publishedProfiles.toString(), + priceText = "Published profiles" + ) + } + } + } + WelcomeCard( + title = "Get your first BTC", + buttonText = "Enter Bisq Easy" + ) + Column { + InstructionCard( + image = Res.drawable.img_fiat_btc, + title = "Multiple trade protocols", + description = "Checkout the roadmap for upcoming trade protocols. Get an overview about the features of the different protocols.", + buttonText = "Explore trade protocols" + ) + Spacer(modifier = Modifier.height(24.dp)) + InstructionCard( + image = Res.drawable.img_learn_and_discover, + title = "Learn & discover", + description = "Learn about Bitcoin and checkout upcoming events. Meet other Bisq users in the discussion chat.", + buttonText = "Learn more" + ) + } + } + } +} + +@Composable +fun WelcomeCard(title: String, buttonText: String) { + NeumorphicCard{ + Column( + modifier = Modifier.shadow( + ambientColor = Color.Blue, spotColor = BisqTheme.colors.primary, + elevation = 2.dp, + shape = RoundedCornerShape(5.dp), + + ).clip(shape = RoundedCornerShape(5.dp)).background(color = BisqTheme.colors.dark2) + .padding(24.dp), + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + BisqText.h4Regular( + text = title, + color = BisqTheme.colors.light1, + ) + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + FeatureCard( + image = Res.drawable.icon_tag_outlined, + title = "Start trading or browser open offers in the offerbook" + ) + FeatureCard( + image = Res.drawable.icon_chat_outlined, + title = "Chat based and guided user interface for trading" + ) + FeatureCard( + image = Res.drawable.icon_star_outlined, + title = "Security is based on seller’s reputation" + ) + } + BisqText.baseMedium( + text = buttonText, + color = BisqTheme.colors.light1, + textAlign = TextAlign.Center, + modifier = Modifier + .clip(shape = RoundedCornerShape(4.dp)) + .background(color = BisqTheme.colors.primary) + .fillMaxWidth() + .padding(vertical = 12.dp), + ) + } + } + +} + +@Composable +fun PriceProfileCard(price: String, priceText: String) { + Column( + modifier = Modifier + .clip(shape = RoundedCornerShape(4.dp)) + .background(color = BisqTheme.colors.dark3) + .padding(vertical = 12.dp).fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + BisqText.largeRegular( + text = price, + color = BisqTheme.colors.light1, + textAlign = TextAlign.Center, + ) + + Spacer(modifier = Modifier.height(12.dp)) + + BisqText.smallRegular( + text = priceText, + color = BisqTheme.colors.grey1, + textAlign = TextAlign.Center, + ) + } +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun FeatureCard(image: DrawableResource, title: String) { + Row(verticalAlignment = Alignment.CenterVertically) { +// AsyncImage( +// model = Res.getUri(imagePath), +// contentDescription = null, +// modifier = Modifier.size(20.dp) +// ) + Image(painterResource(image), null, Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(9.dp)) + BisqText.smallRegular( + text = title, + color = BisqTheme.colors.light1, + ) + + } +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun InstructionCard(image: DrawableResource, title: String, description: String, buttonText: String) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.clip(shape = RoundedCornerShape(8.dp)).background(color = BisqTheme.colors.dark3) + .padding(vertical = 18.dp, horizontal = 12.dp), + verticalArrangement = Arrangement.spacedBy(18.dp) + ) { +// AsyncImage( +// model = Res.getUri(imagePath), +// contentDescription = null, +// modifier = Modifier.size(50.dp) +// ) + Image(painterResource(image), "") + BisqText.baseRegular( + text = title, + color = BisqTheme.colors.light1, + ) + BisqText.baseRegular( + text = description, + color = BisqTheme.colors.grey3, + textAlign = TextAlign.Center, + ) + BisqText.smallRegular( + text = buttonText, + color = BisqTheme.colors.light1, + modifier = Modifier + .clip(shape = RoundedCornerShape(4.dp)) + .background(color = BisqTheme.colors.dark5) + .padding(horizontal = 18.dp, vertical = 6.dp), + ) + } +} + +@Composable +fun NeumorphicCard( + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + + Box( + modifier = modifier + .shadow(elevation = 8.dp, shape = RoundedCornerShape(5.dp), spotColor = BisqTheme.colors.primary) + .padding(2.dp) + ) { + content() + } + +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/TabContainerScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/TabContainerScreen.kt new file mode 100644 index 00000000..aaf23955 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/TabContainerScreen.kt @@ -0,0 +1,61 @@ +package network.bisq.mobile.presentation.ui.uicases + +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.* +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import bisqapps.shared.presentation.generated.resources.* +import bisqapps.shared.presentation.generated.resources.Res +import network.bisq.mobile.presentation.ui.composeModels.BottomNavigationItem +import network.bisq.mobile.presentation.ui.navigation.BottomNavigation +import network.bisq.mobile.presentation.ui.navigation.Graph +import network.bisq.mobile.presentation.ui.navigation.Routes +import network.bisq.mobile.presentation.ui.navigation.graph.RootNavGraph +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.koin.compose.koinInject +import org.koin.core.qualifier.named +import org.koin.mp.KoinPlatform.getKoin + +val navigationListItem = listOf( + BottomNavigationItem("Home", Routes.TabHome.name, Res.drawable.icon_home), + BottomNavigationItem("Buy/Sell", Routes.TabExchange.name, Res.drawable.icon_market), + BottomNavigationItem("My Trades", Routes.TabMyTrades.name, Res.drawable.icon_trades), + BottomNavigationItem("Settings", Routes.TabSettings.name, Res.drawable.icon_settings), +) + + +@Composable +fun TabContainerScreen() { + val navController: NavHostController = koinInject(named("TabNavController")) + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute by remember(navBackStackEntry) { + derivedStateOf { + navBackStackEntry?.destination?.route + } + } + + Scaffold( + containerColor = BisqTheme.colors.dark3, + bottomBar = { + BottomNavigation( + items = navigationListItem, + currentRoute = currentRoute.orEmpty(), + onItemClick = { currentNavigationItem -> + navController.navigate(currentNavigationItem.route) { + navController.graph.startDestinationRoute?.let { route -> + popUpTo(route) { + saveState = true + } + } + launchSingleTop = true + restoreState = true + } + }) + } + + ) { innerPadding -> + RootNavGraph(startDestination = Graph.MAIN_SCREEN_GRAPH_KEY) + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/exchange/ExchangeScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/exchange/ExchangeScreen.kt new file mode 100644 index 00000000..e3751023 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/exchange/ExchangeScreen.kt @@ -0,0 +1,59 @@ +package network.bisq.mobile.presentation.ui.uicases.exchange + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.currency_euro +import bisqapps.shared.presentation.generated.resources.currency_gpb +import bisqapps.shared.presentation.generated.resources.currency_usd +import network.bisq.mobile.presentation.ui.components.CurrencyProfileCard +import network.bisq.mobile.components.MaterialTextField +import network.bisq.mobile.presentation.ui.components.molecules.TopBar +import network.bisq.mobile.presentation.ui.components.atoms.icons.SortIcon +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.koin.compose.koinInject +import org.koin.core.qualifier.named + +@Composable +fun ExchangeScreen() { + val navController: NavHostController = koinInject(named("RootNavController")) + val originDirection = LocalLayoutDirection.current + Column( + modifier = Modifier.fillMaxSize() + ) { + TopBar("Buy/Sell") + Column(modifier = Modifier.padding(vertical = 12.dp, horizontal = 32.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Box(modifier = Modifier.width(250.dp)) { + MaterialTextField(text = "Search", onValueChanged = {}) + } + SortIcon(modifier = Modifier.size(24.dp)) + } + Spacer(modifier = Modifier.height(12.dp)) + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + CurrencyProfileCard("US Dollars", "USD", Res.drawable.currency_usd) + CurrencyProfileCard("Euro", "EUR", Res.drawable.currency_euro) + CurrencyProfileCard("British Pounds", "GPB", Res.drawable.currency_gpb) + } + } + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsScreen.kt new file mode 100644 index 00000000..5c7a6347 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsScreen.kt @@ -0,0 +1,34 @@ + +package network.bisq.mobile.presentation.ui.uicases.settings + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.koin.compose.koinInject +import org.koin.core.qualifier.named + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun SettingsScreen( +) { + val navController: NavHostController = koinInject(named("RootNavController")) + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center // Centers the content within the Box + ) { + BisqText.h2Regular( + text = "Settings", + color = BisqTheme.colors.light1, + ) + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt new file mode 100644 index 00000000..d4d4c4ee --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfilePresenter.kt @@ -0,0 +1,74 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.collectLatest +import network.bisq.mobile.domain.data.repository.UserProfileRepository +import network.bisq.mobile.domain.data.model.UserProfile +import network.bisq.mobile.presentation.BasePresenter +import network.bisq.mobile.presentation.ui.navigation.Routes +import kotlinx.coroutines.delay +import network.bisq.mobile.presentation.MainPresenter + +open class CreateProfilePresenter( + mainPresenter: MainPresenter, + private val navController: NavController, + private val userProfileRepository: UserProfileRepository +) : BasePresenter(mainPresenter), ICreateProfilePresenter { + + private val _profileName = MutableStateFlow("") + override val profileName: StateFlow = _profileName + + // TODO: Not working + init { + CoroutineScope(Dispatchers.IO).launch { + userProfileRepository.data.collectLatest { userProfile -> + userProfile?.let { + _profileName.value = it.name + } + } + } + } + + override fun onProfileNameChanged(newName: String) { + _profileName.value = newName + } + + override fun navigateToNextScreen() { + if (_profileName.value.isNotEmpty()) { + saveUserProfile() + navController.navigate(Routes.TrustedNodeSetup.name) { + popUpTo(Routes.CreateProfile.name) { inclusive = true } + } + } + } + + fun saveUserProfile() { + CoroutineScope(Dispatchers.IO).launch { + val updatedProfile = UserProfile().apply { + name = _profileName.value + } + userProfileRepository.update(updatedProfile) + } + } + + /* + fun onNicknameChanged(newNickname: String) { + _nickname.value = newNickname + } + + fun navigateToNextScreen() { + if (_nickname.value.isNotEmpty()) { + navController.navigate(Routes.TrustedNodeSetup.name) { + popUpTo(Routes.CreateProfile.name) { inclusive = true } + } + } + } + */ + +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt new file mode 100644 index 00000000..c862556e --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/CreateProfileScreen.kt @@ -0,0 +1,101 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.img_bot_image +import network.bisq.mobile.components.MaterialTextField +import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo +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.navigation.Routes +import network.bisq.mobile.presentation.ui.theme.* +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.koin.compose.koinInject +import org.koin.core.qualifier.named +import kotlinx.coroutines.flow.StateFlow +import cafe.adriel.lyricist.LocalStrings +import org.koin.core.parameter.parametersOf + +interface ICreateProfilePresenter { + val profileName: StateFlow + + fun onProfileNameChanged(newName: String) + fun navigateToNextScreen() +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun CreateProfileScreen( +) { + val strings = LocalStrings.current + val navController: NavHostController = koinInject(named("RootNavController")) + val presenter: ICreateProfilePresenter = koinInject { parametersOf(navController) } + + val profileName = presenter.profileName.collectAsState().value + + BisqScrollLayout() { + BisqLogo() + Spacer(modifier = Modifier.height(24.dp)) + BisqText.h1Light( + text = strings.onboarding_createProfile_headline, + color = BisqTheme.colors.grey1, + ) + Spacer(modifier = Modifier.height(12.dp)) + BisqText.baseRegular( + text = strings.onboarding_createProfile_subTitle, + color = BisqTheme.colors.grey3, + modifier = Modifier.padding(horizontal = 24.dp), + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(36.dp)) + Column(modifier = Modifier.padding(horizontal = 24.dp)) { + //TODO: Convert this into a Form field component, which is Label + TextField + BisqText.baseRegular( + text = strings.onboarding_createProfile_nickName, + color = BisqTheme.colors.light2, + ) + MaterialTextField( + text = profileName, + placeholder = strings.onboarding_createProfile_nickName_prompt, + onValueChanged = { presenter.onProfileNameChanged(it) }) + } + Spacer(modifier = Modifier.height(36.dp)) + Image(painterResource(Res.drawable.img_bot_image), "Crypto generated image (PoW)") // TODO: Translation + Spacer(modifier = Modifier.height(32.dp)) + BisqText.baseRegular( + text = "Sleepily-Distracted-Zyophyte-257", + color = BisqTheme.colors.light1, + ) + Spacer(modifier = Modifier.height(12.dp)) + BisqText.baseRegular( + text = strings.onboarding_createProfile_nym, + color = BisqTheme.colors.grey2, + ) + Spacer(modifier = Modifier.height(38.dp)) + BisqButton( + text = strings.onboarding_createProfile_regenerate, + backgroundColor = BisqTheme.colors.dark5, + padding = PaddingValues(horizontal = 64.dp, vertical = 12.dp), + onClick = {} + ) + Spacer(modifier = Modifier.height(40.dp)) + BisqButton( + strings.buttons_next, + onClick = { presenter.navigateToNextScreen() }, + backgroundColor = if (profileName.isEmpty()) BisqTheme.colors.primaryDisabled else BisqTheme.colors.primary + ) + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt new file mode 100644 index 00000000..d1bcdd43 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingPresenter.kt @@ -0,0 +1,63 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.compose.foundation.pager.PagerState +import androidx.navigation.NavController +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 kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import network.bisq.mobile.presentation.BasePresenter +import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.composeModels.OnBoardingPage +import network.bisq.mobile.presentation.ui.navigation.Routes + +val onBoardingPages = listOf( + OnBoardingPage( + title = "Introducing Bisq Easy", + image = Res.drawable.img_bisq_Easy, + desc = "Getting your first Bitcoin privately has never been easier" + ), + OnBoardingPage( + title = "Learn & Discover", + image = Res.drawable.img_learn_and_discover, + desc = "Get a gentle introduction into Bitcoin through our guides and community chat" + ), + OnBoardingPage( + title = "Coming soon", + image = Res.drawable.img_fiat_btc, + desc = "Choose how to trade: Bisq MuSig, Lightning, Submarine Swaps,..." + ) +) + +open class OnBoardingPresenter( + mainPresenter: MainPresenter, + private val navController: NavController +) : BasePresenter(mainPresenter), IOnboardingPresenter { + + private val _pagerState = MutableStateFlow(null) + override val pagerState: StateFlow = _pagerState + + fun setPagerState(pagerState: PagerState) { + _pagerState.value = pagerState + } + + override fun onNextButtonClick(coroutineScope: CoroutineScope) { + coroutineScope.launch { + val state = pagerState.value + if (state != null) { + if (state.currentPage == onBoardingPages.lastIndex) { + navController.navigate(Routes.CreateProfile.name) { + popUpTo(Routes.Onboarding.name) { inclusive = true } + } + } else { + state.animateScrollToPage(state.currentPage + 1) + } + } + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingScreen.kt new file mode 100644 index 00000000..2f339b12 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/OnBoardingScreen.kt @@ -0,0 +1,201 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PageSize +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import bisqapps.shared.presentation.generated.resources.* +import cafe.adriel.lyricist.LocalStrings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo +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.theme.* +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.koin.compose.koinInject +import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named + +interface IOnboardingPresenter { + val pagerState: StateFlow + + fun onNextButtonClick(coroutineScope: CoroutineScope) +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun OnBoardingScreen() { + val strings = LocalStrings.current + val navController: NavHostController = koinInject(named("RootNavController")) + val presenter: IOnboardingPresenter = koinInject { parametersOf(navController) } + + val coroutineScope = rememberCoroutineScope() + val pagerState = rememberPagerState(pageCount = { onBoardingPages.size }) + + // TODO: Any other better way to do this? + LaunchedEffect(pagerState) { + (presenter as? OnBoardingPresenter)?.setPagerState(pagerState) + } + + BisqScrollLayout() { + BisqLogo() + Spacer(modifier = Modifier.height(24.dp)) + BisqText.h1Light( + text = strings.onboarding_bisq2_headline, + color = BisqTheme.colors.grey1, + ) + Spacer(modifier = Modifier.height(56.dp)) + PagerView(presenter) + Spacer(modifier = Modifier.height(56.dp)) + + BisqButton( + text = if (pagerState.currentPage == onBoardingPages.lastIndex) strings.onboarding_button_create_profile else strings.buttons_next, + onClick = { presenter.onNextButtonClick(coroutineScope) } + ) + + } + +} + +@Composable +fun PagerView(presenter: IOnboardingPresenter) { + + val pagerState = presenter.pagerState.collectAsState().value + + pagerState?.let { + CompositionLocalProvider(values = arrayOf()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(36.dp, Alignment.CenterVertically), + ) { + HorizontalPager( + pageSpacing = 56.dp, + contentPadding = PaddingValues(horizontal = 36.dp), + pageSize = PageSize.Fill, + verticalAlignment = Alignment.CenterVertically, + state = it + ) { index -> + onBoardingPages.getOrNull( + index % (onBoardingPages.size) + )?.let { item -> + BannerItem( + image = item.image, + title = item.title, + desc = item.desc, + index = index, + ) + } + } + LineIndicator(pagerState = it) + } + } + } +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun BannerItem( + title: String, + image: DrawableResource, + desc: String, + index: Int +) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(18.dp)), + contentAlignment = Alignment.Center + ) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .background(color = BisqTheme.colors.dark3) + .padding(vertical = 56.dp) + ) { + Image(painterResource(image), title, modifier = Modifier.size(120.dp),) + Spacer(modifier = Modifier.height(if (index == 1) 48.dp else 70.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + BisqText.h4Regular( + text = title, + color = BisqTheme.colors.light1, + ) + Spacer(modifier = Modifier.height(24.dp)) + BisqText.largeRegular( + text = desc, + color = BisqTheme.colors.grey2, + modifier = Modifier.padding(horizontal = 16.dp), + textAlign = TextAlign.Center, + ) + } + } + } +} + +@Composable +fun LineIndicator(pagerState: PagerState) { + Box( + contentAlignment = Alignment.CenterStart + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + repeat(pagerState.pageCount) { + Box( + modifier = Modifier + .size(width = 76.dp, height = 2.dp) + .background( + color = BisqTheme.colors.grey2, + ) + ) + } + } + Box( + Modifier + .slidingLineTransition( + pagerState, + 76f * LocalDensity.current.density + ) + .size(width = 76.dp, height = 3.dp) + .background( + color = BisqTheme.colors.primary, + shape = RoundedCornerShape(4.dp), + ) + ) + } +} + +fun Modifier.slidingLineTransition(pagerState: PagerState, distance: Float) = + graphicsLayer { + val scrollPosition = pagerState.currentPage + pagerState.currentPageOffsetFraction + translationX = scrollPosition * distance + } diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt new file mode 100644 index 00000000..ee32925a --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt @@ -0,0 +1,48 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import network.bisq.mobile.presentation.BasePresenter +import network.bisq.mobile.presentation.ui.navigation.Routes +import kotlinx.coroutines.delay +import network.bisq.mobile.presentation.MainPresenter + +open class SplashPresenter( + mainPresenter: MainPresenter, + private val navController: NavController +) : BasePresenter(mainPresenter), ISplashPresenter { + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + override fun startLoading(onProgressUpdate: (Float) -> Unit) { + coroutineScope.launch { + initializeNetwork{ progress -> + onProgressUpdate(progress) + } + navigateToNextScreen() + } + } + + // TODO refactor into a service once networking is done + protected open suspend fun initializeNetwork(updateProgress: (Float) -> Unit) { + + //1. Initialize Tor here + //2. Connect to peers (for androidNode), to Bisq instance (for xClients) + //3. Do any other app initialization + for (i in 1..100) { + updateProgress(i.toFloat() / 100) + delay(25) + } + } + + private fun navigateToNextScreen() { + // TODO: Conditional nav + // If firstTimeApp launch, goto Onboarding[clientMode] (androidNode / xClient) + // If not, goto TabContainerScreen + navController.navigate(Routes.Onboarding.name) { + popUpTo(Routes.Splash.name) { inclusive = true } + } + } + +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt new file mode 100644 index 00000000..911fb62a --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashScreen.kt @@ -0,0 +1,61 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.navigation.NavController +import androidx.navigation.NavHostController + +import cafe.adriel.lyricist.LocalStrings +import network.bisq.mobile.presentation.ui.components.atoms.BisqProgressBar +import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.components.layout.BisqStaticLayout +import network.bisq.mobile.presentation.ui.theme.* +import org.koin.compose.koinInject +import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named + +interface ISplashPresenter { + fun startLoading(onProgressUpdate: (Float) -> Unit) +} + +@Composable +fun SplashScreen( +) { + val strings = LocalStrings.current + val navController: NavHostController = koinInject(named("RootNavController")) + val presenter: ISplashPresenter = koinInject { parametersOf(navController) } + + var currentProgress by remember { mutableFloatStateOf(0f) } + + LaunchedEffect(Unit) { + presenter.startLoading { progress -> + currentProgress = progress + } + } + + BisqStaticLayout { + BisqLogo() + + Column { + BisqProgressBar(progress = currentProgress) + + // TODO: Get this from presenter + val networkType = strings.splash_bootstrapState_network_TOR + + BisqText.baseRegular( + text = strings.splash_bootstrapState_BOOTSTRAP_TO_NETWORK(networkType), + color = BisqTheme.colors.secondaryHover, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupPresenter.kt new file mode 100644 index 00000000..ef9315bf --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupPresenter.kt @@ -0,0 +1,50 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +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.navigation.Routes + +class TrustedNodeSetupPresenter( + mainPresenter: MainPresenter, + private val navController: NavController, + private val settingsRepository: SettingsRepository +) : BasePresenter(mainPresenter), ITrustedNodeSetupPresenter { + + private val _bisqUrl = MutableStateFlow("") + override val bisqUrl: StateFlow = _bisqUrl + + private val _isConnected = MutableStateFlow(false) + override val isConnected: StateFlow = _isConnected + + override fun updateBisqUrl(newUrl: String) { + _bisqUrl.value = newUrl + } + + override fun testConnection(isTested: Boolean) { + _isConnected.value = isTested + + CoroutineScope(Dispatchers.IO).launch { + val updatedSettings = Settings().apply { + bisqUrl = _bisqUrl.value + isConnected = _isConnected.value + } + + settingsRepository.update(updatedSettings) + } + } + + override fun navigateToNextScreen() { + navController.navigate(Routes.TabContainer.name) { + popUpTo(Routes.TrustedNodeSetup.name) { inclusive = true } + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupScreen.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupScreen.kt new file mode 100644 index 00000000..7d8aef36 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/TrustedNodeSetupScreen.kt @@ -0,0 +1,167 @@ +package network.bisq.mobile.presentation.ui.uicases.startup + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.slideInHorizontally +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import bisqapps.shared.presentation.generated.resources.Res +import bisqapps.shared.presentation.generated.resources.icon_question_mark +import network.bisq.mobile.components.MaterialTextField +import network.bisq.mobile.presentation.ui.components.atoms.icons.BisqLogo +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.navigation.Routes +import network.bisq.mobile.presentation.ui.theme.* +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.koin.compose.koinInject +import org.koin.core.qualifier.named +import org.koin.core.parameter.parametersOf +import cafe.adriel.lyricist.LocalStrings + +import kotlinx.coroutines.flow.StateFlow + +interface ITrustedNodeSetupPresenter { + val bisqUrl: StateFlow + val isConnected: StateFlow + + fun updateBisqUrl(newUrl: String) + + fun testConnection(isTested: Boolean) + + fun navigateToNextScreen() +} + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun TrustedNodeSetupScreen( +) { + val strings = LocalStrings.current + val navController: NavHostController = koinInject(named("RootNavController")) + val presenter: ITrustedNodeSetupPresenter = koinInject { parametersOf(navController) } + + val bisqUrl = presenter.bisqUrl.collectAsState().value + val isConnected = presenter.isConnected.collectAsState().value + + BisqScrollLayout() { + BisqLogo() + Spacer(modifier = Modifier.height(24.dp)) + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxSize().padding(horizontal = 0.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + BisqText.baseRegular( + text = "Bisq URL", + color = BisqTheme.colors.light1, + ) + Image(painterResource(Res.drawable.icon_question_mark), "Question mark") + } + + MaterialTextField(bisqUrl, onValueChanged = { presenter.updateBisqUrl(it) }) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + BisqButton( + text = "Paste", + onClick = {}, + backgroundColor = BisqTheme.colors.dark5, + color = BisqTheme.colors.light1, + //leftIcon=Image(painterResource(Res.drawable.icon_copy), "Copy button") + ) + + BisqButton( + text = "Scan", + onClick = {}, + //leftIcon=Image(painterResource(Res.drawable.icon_qr), "Scan button") + ) + } + Spacer(modifier = Modifier.height(36.dp)) + BisqText.baseRegular( + text = "STATUS", + color = BisqTheme.colors.grey2, + ) + Spacer(modifier = Modifier.height(12.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + BisqText.largeRegular( + text = if (isConnected) "Connected" else "Not Connected", + color = BisqTheme.colors.light1, + ) + Spacer(modifier = Modifier.width(12.dp)) + BisqText.baseRegular( + text = "", + modifier = Modifier.clip( + RoundedCornerShape(5.dp) + ).background(color = if (isConnected) BisqTheme.colors.primary else BisqTheme.colors.danger) + .size(10.dp), + ) + } + } + + Spacer(modifier = Modifier.height(56.dp)) + + if (!isConnected) { + BisqButton( + text = "Test Connection", + color = if (bisqUrl.isEmpty()) BisqTheme.colors.grey1 else BisqTheme.colors.light1, + onClick = { + presenter.testConnection(true) + }, + padding = PaddingValues(horizontal = 32.dp, vertical = 12.dp), + ) + } else { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(horizontal = 0.dp) + ) { + AnimatedVisibility( + visible = isConnected, + enter = slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(700)), + ) { + BisqButton( + text = "Test Connection", + color = if (bisqUrl.isEmpty()) BisqTheme.colors.grey1 else BisqTheme.colors.light1, + onClick = { presenter.testConnection(true) }, + padding = PaddingValues(horizontal = 32.dp, vertical = 12.dp), + ) + } + //Spacer(modifier = Modifier.width(20.dp)) + AnimatedVisibility( + visible = isConnected, + enter = fadeIn(animationSpec = tween(300)), + + ) { + BisqButton( + text = "Next", + color = BisqTheme.colors.light1, + onClick = { presenter.navigateToNextScreen() }, + padding = PaddingValues(horizontal = 32.dp, vertical = 12.dp), + ) + } + } + } + } +} diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/MyTrades.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/MyTrades.kt new file mode 100644 index 00000000..c2039696 --- /dev/null +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/trades/MyTrades.kt @@ -0,0 +1,33 @@ + +package network.bisq.mobile.presentation.ui.uicases.trades + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import network.bisq.mobile.presentation.ui.components.atoms.BisqText +import network.bisq.mobile.presentation.ui.theme.BisqTheme +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.koin.compose.koinInject +import org.koin.core.qualifier.named + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun MyTradesScreen() { + val rootNavController: NavHostController = koinInject(named("RootNavController")) + Box( + modifier = Modifier + .fillMaxSize(), + contentAlignment = Alignment.Center // Centers the content within the Box + ) { + BisqText.h2Regular( + text = "My Trades", + color = BisqTheme.colors.light1, + ) + } +} \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonTest/kotlin/network/bisq/mobile/presentation/ExampleTest.kt b/bisqapps/shared/presentation/src/commonTest/kotlin/network/bisq/mobile/presentation/ExampleTest.kt index 1a2d164c..9ede4af5 100644 --- a/bisqapps/shared/presentation/src/commonTest/kotlin/network/bisq/mobile/presentation/ExampleTest.kt +++ b/bisqapps/shared/presentation/src/commonTest/kotlin/network/bisq/mobile/presentation/ExampleTest.kt @@ -1,4 +1,5 @@ -import androidx.compose.material.* +import androidx.compose.material3.Button +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag diff --git a/bisqapps/tools/convert-i18n.js b/bisqapps/tools/convert-i18n.js new file mode 100644 index 00000000..9ba840ed --- /dev/null +++ b/bisqapps/tools/convert-i18n.js @@ -0,0 +1,114 @@ +// This JS script is to convert translation key, values from +// bisq2 repo (*.properties) to bisq-mobile repo (lyricist library structure) +// It's not fully automated yet, but with some configuration it works now. + +const fs = require('fs'); +const path = require('path'); + +// Define the output paths for English and French files +const outputEnFilePath = path.join(__dirname, 'GeneratedEnStrings.kt'); +const outputFrFilePath = path.join(__dirname, 'GeneratedFrStrings.kt'); + +// Function to convert the translation key to Kotlin variable name +function convertKeyToKotlinVariable(key) { + return key.replace(/\./g, '_').replace(/[{}]/g, ''); +} + +// Function to parse the .properties file with proper multi-line handling +function parsePropertiesFile(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + + const translations = {}; + let currentKey = null; + let currentValue = ''; + + lines.forEach(line => { + line = line.trim(); + + // Skip empty lines and comments + if (!line || line.startsWith('#')) return; + + // If line ends with "\", it's a continuation + if (line.endsWith('\\')) { + // If this is the start of a new entry, initialize currentKey and currentValue + if (line.includes('=') && !currentKey) { + const [key, ...valueParts] = line.split('='); + currentKey = key.trim(); + currentValue = valueParts.join('=').trim().slice(0, -1); // Remove the trailing "\" + } else { + // Append to the current value without the trailing "\" + currentValue += ' ' + line.slice(0, -1); + } + } else { + // Process the last line of a multi-line or a single line entry + if (line.includes('=') && !currentKey) { + // Single line entry + const [key, ...valueParts] = line.split('='); + translations[key.trim()] = valueParts.join('=').trim(); + } else if (currentKey) { + // Final line of a multi-line entry + currentValue += ' ' + line; + translations[currentKey] = currentValue; + currentKey = null; + currentValue = ''; + } + } + }); + + return translations; +} + +// Function to generate Kotlin code for English and French files +function generateKotlinCode(translations, languageTag, prefix = '') { + let stringsClass = 'data class Strings(\n'; + let stringsObject = `@LyricistStrings(languageTag = ${languageTag}, default = ${languageTag === "Locales.EN"})\nval ${languageTag}Strings = Strings(\n`; + + for (const [key, value] of Object.entries(translations)) { + const variableName = convertKeyToKotlinVariable(key); + stringsClass += ` val ${variableName}: String,\n`; + stringsObject += ` ${variableName} = "${prefix}${value.replace(/"/g, '\\"')}",\n`; + } + + stringsClass = stringsClass.trimEnd().slice(0, -1) + '\n)\n'; + stringsObject = stringsObject.trimEnd().slice(0, -1) + '\n)\n'; + + return ` +package network.bisq.mobile.presentation.i18n + +import cafe.adriel.lyricist.LyricistStrings + +// Generated Strings data class +${stringsClass} + +// Generated ${languageTag} strings +${stringsObject} +`; +} + +// Main function to run the conversion and create both files +function convertPropertiesToKotlin(propertiesFilePath) { + if (!fs.existsSync(propertiesFilePath)) { + console.error('Properties file not found:', propertiesFilePath); + return; + } + + const translations = parsePropertiesFile(propertiesFilePath); + const enKotlinCode = generateKotlinCode(translations, "Locales.EN"); + const frKotlinCode = generateKotlinCode(translations, "Locales.FR", "[FR] "); + + // Write the generated code to the output files + fs.writeFileSync(outputEnFilePath, enKotlinCode, 'utf-8'); + fs.writeFileSync(outputFrFilePath, frKotlinCode, 'utf-8'); + console.log('Kotlin strings files generated at:', outputEnFilePath, 'and', outputFrFilePath); +} + +// Run the conversion with the provided file path +const propertiesFilePath = process.argv[2]; +if (!propertiesFilePath) { + console.error('Please provide the path to the .properties file as an argument.'); + process.exit(1); +} + +convertPropertiesToKotlin(propertiesFilePath); +