From f0b863744dbd3aead1a9d2f441299d49e07c78cc Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Thu, 19 Dec 2024 22:08:13 +0700 Subject: [PATCH 01/32] Init watch app module --- gradle.properties | 33 +++--- gradle/libs.versions.toml | 11 ++ settings.gradle | 1 + watchapp/.gitignore | 1 + watchapp/build.gradle.kts | 69 +++++++++++ watchapp/lint.xml | 8 ++ watchapp/proguard-rules.pro | 21 ++++ watchapp/src/main/AndroidManifest.xml | 39 +++++++ .../watchapp/presentation/MainActivity.kt | 73 ++++++++++++ .../presentation/quickpairs/QuickPairItem.kt | 107 ++++++++++++++++++ .../quickpairs/QuickPairsScreen.kt | 8 ++ .../rate/watchapp/presentation/theme/Theme.kt | 17 +++ .../src/main/res/drawable/splash_icon.xml | 27 +++++ .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../src/main/res/values-round/strings.xml | 3 + watchapp/src/main/res/values/strings.xml | 8 ++ watchapp/src/main/res/values/styles.xml | 8 ++ 21 files changed, 414 insertions(+), 20 deletions(-) create mode 100644 watchapp/.gitignore create mode 100644 watchapp/build.gradle.kts create mode 100644 watchapp/lint.xml create mode 100644 watchapp/proguard-rules.pro create mode 100644 watchapp/src/main/AndroidManifest.xml create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/Theme.kt create mode 100644 watchapp/src/main/res/drawable/splash_icon.xml create mode 100644 watchapp/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 watchapp/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 watchapp/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 watchapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 watchapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 watchapp/src/main/res/values-round/strings.xml create mode 100644 watchapp/src/main/res/values/strings.xml create mode 100644 watchapp/src/main/res/values/styles.xml diff --git a/gradle.properties b/gradle.properties index a00c9439b..ef1e9b7ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,27 +1,20 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit +## For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -Dfile.encoding=UTF-8 +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# # When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects # org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true +#Mon Dec 02 22:06:10 ICT 2024 android.defaults.buildfeatures.buildconfig=true +android.enableJetifier=true android.nonFinalResIds=false +android.nonTransitiveRClass=true +android.useAndroidX=true +kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -Dfile.encoding\=UTF-8 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d936546e6..aef266a5d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,11 @@ workRuntimeKtx = "2.8.1" appcompat = "1.7.0" material = "1.12.0" gson = "2.11.0" +playServicesWearable = "18.2.0" +composeBom = "2023.08.00" +composeMaterial = "1.2.1" +composeFoundation = "1.2.1" +coreSplashscreen = "1.0.1" [libraries] ark-about = { module = "dev.arkbuilders.components:about", version.ref = "arkAbout" } @@ -85,7 +90,13 @@ timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } +play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } +androidx-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "composeFoundation" } +androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } [plugins] android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"} +androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/settings.gradle b/settings.gradle index faefe68e9..899a2aee0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,3 +39,4 @@ include ':feature:pairalert' include ':feature:quickwidget' include ':feature:search' include ':feature:settings' +include ':watchapp' diff --git a/watchapp/.gitignore b/watchapp/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/watchapp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts new file mode 100644 index 000000000..57a01bdb9 --- /dev/null +++ b/watchapp/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + id("com.android.application") version "8.1.3" + id("org.jetbrains.kotlin.android") +} +android { + namespace = "dev.arkbuilders.rate.watchapp" + compileSdk = 34 + + defaultConfig { + applicationId = "dev.arkbuilders.rate.watchapp" + minSdk = 29 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + vectorDrawables { + useSupportLibrary = true + } + + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.8" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(project(":cryptoicons")) + implementation(project(":fiaticons")) + implementation(project(":feature:quick")) + implementation(project(":core:domain")) + implementation(project(":core:presentation")) + + implementation(libs.play.services.wearable) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.foundation) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.core.splashscreen) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} diff --git a/watchapp/lint.xml b/watchapp/lint.xml new file mode 100644 index 000000000..44fac75b8 --- /dev/null +++ b/watchapp/lint.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/watchapp/proguard-rules.pro b/watchapp/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/watchapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/watchapp/src/main/AndroidManifest.xml b/watchapp/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8547e121e --- /dev/null +++ b/watchapp/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt new file mode 100644 index 000000000..02bc6090a --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -0,0 +1,73 @@ +/* While this template provides a good starting point for using Wear Compose, you can always + * take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter and + * https://github.com/android/wear-os-samples/tree/main/ComposeAdvanced to find the most up to date + * changes to the libraries and their usages. + */ + +package dev.arkbuilders.rate.watchapp.presentation + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.TimeText +import dev.arkbuilders.rate.watchapp.R +import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + + super.onCreate(savedInstanceState) + + setTheme(android.R.style.Theme_DeviceDefault) + + setContent { + WearApp("Android") + } + } +} + +@Composable +fun WearApp(greetingName: String) { + ArkrateTheme { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.background), + contentAlignment = Alignment.Center + ) { + TimeText() + Greeting(greetingName = greetingName) + } + } +} + +@Composable +fun Greeting(greetingName: String) { + Text( + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.primary, + text = stringResource(R.string.hello_world, greetingName) + ) +} + +@Preview(device = Devices.WEAR_OS_SMALL_ROUND, showSystemUi = true) +@Composable +fun DefaultPreview() { + WearApp("Preview Android") +} \ No newline at end of file diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt new file mode 100644 index 000000000..6361b095b --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt @@ -0,0 +1,107 @@ +package dev.arkbuilders.rate.watchapp.presentation.quickpairs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.domain.CurrUtils +import dev.arkbuilders.rate.core.domain.model.Amount +import dev.arkbuilders.rate.core.domain.model.CurrencyCode +import dev.arkbuilders.rate.core.presentation.utils.IconUtils +import dev.arkbuilders.rate.feature.quick.domain.model.PinnedQuickPair +import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair +import java.math.BigDecimal +import java.time.OffsetDateTime + +@Composable +fun QuickPairItem( + modifier: Modifier = Modifier, + quick: PinnedQuickPair, +) { + Column( + modifier = modifier + .padding(12.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(modifier = modifier.align(Alignment.Start)) { + CurrIcon( + modifier = modifier.size(16.dp), + code = quick.pair.from + ) + CurrIcon( + modifier = modifier.size(16.dp), + code = quick.pair.to.first().code + ) + } + Text( + modifier = modifier.fillMaxWidth(), + text = "${quick.pair.from} to ${ + quick.pair.to.joinToString( + separator = ", ", + ) { it.code } + }", + ) + Text( + modifier = modifier.fillMaxWidth(), + text = + "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = " + + "${CurrUtils.prepareToDisplay(quick.actualTo.first().value)} ${quick.actualTo.first().code}", + ) + Text( + modifier = modifier.fillMaxWidth(), + text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.actualTo.first().code}", + ) + } +} + +@Composable +fun CurrIcon( + modifier: Modifier = Modifier, + code: CurrencyCode, +) { + val ctx = LocalContext.current + Icon( + modifier = modifier, + painter = painterResource(id = IconUtils.iconForCurrCode(ctx, code)), + contentDescription = code, + tint = Color.Unspecified, + ) +} + + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun QuickPairItemPreview() { + QuickPairItem( + quick = PinnedQuickPair( + pair = QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf(Amount("USD", BigDecimal.valueOf(12.0))), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + actualTo = listOf(Amount("USD", BigDecimal.valueOf(12.0))), + refreshDate = OffsetDateTime.now(), + ) + ) +} + diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt new file mode 100644 index 000000000..cbb571f80 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -0,0 +1,8 @@ +package dev.arkbuilders.rate.watchapp.presentation.quickpairs + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun QuickPairsScreen(modifier: Modifier = Modifier ) { +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/Theme.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/Theme.kt new file mode 100644 index 000000000..370c5c7ff --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/Theme.kt @@ -0,0 +1,17 @@ +package dev.arkbuilders.rate.watchapp.presentation.theme + +import androidx.compose.runtime.Composable +import androidx.wear.compose.material.MaterialTheme + +@Composable +fun ArkrateTheme( + content: @Composable () -> Unit +) { + /** + * Empty theme to customize for your app. + * See: https://developer.android.com/jetpack/compose/designsystems/custom + */ + MaterialTheme( + content = content + ) +} \ No newline at end of file diff --git a/watchapp/src/main/res/drawable/splash_icon.xml b/watchapp/src/main/res/drawable/splash_icon.xml new file mode 100644 index 000000000..7874e83f0 --- /dev/null +++ b/watchapp/src/main/res/drawable/splash_icon.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/watchapp/src/main/res/mipmap-hdpi/ic_launcher.webp b/watchapp/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/watchapp/src/main/res/mipmap-mdpi/ic_launcher.webp b/watchapp/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!To6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/watchapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/watchapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..28d4b77f9f036a47549d47db79c16788749dca10 GIT binary patch literal 2884 zcmV-K3%m4ENk&FI3jhFDMM6+kP&il$0000G0001w0055w06|PpNY()W00EFA*|uso z=UmW3;Ri7@GcyiBW{ey$jes55b5S`|ZVZ{(x$xch{z?D+^{yErVgleVwa9qvGt40r z42;MG=7<0QySlzE=Ig6%01!FBK^$Fsxe@Hfe6aCy?Wh2r0~}@_lQAF90oTUi0FhEr z#(*;kTC(r!tQk6;gxj4h%FdHAt(^M3YvYj(!tOeN)+Hvj6+< zzyJRG?^lZfWuR#t!tUKP&(?%3v&Zd$R2YN>lB(Lq`OInY48%4%yTv2 zYe1{G`3)(PDEio5Y@-I5tUf`c%%OCJMtSW56g3iEg%3`$7XSJJHyA z<|7&N)5Xrlgv~%BO24eFd;Hd;uiK%D`EdK|quUeRZDqbh9l)%j%J#0lfrZumvA<_w zu&=AVvdChf6}eqh(bUz`(`Ue*p01{fBAcTgKyDYLs_I+YyJEk+rM@avU~>fB$n)HS zM7pfJydu`i%gfS<{PF94kZDv$t>06sAkheDzu40NJ$5CMW%n^Lls?8^p^QGWURbKu3ZduZQZ((s2? zzE`}<{;Zt7<$C|9R8A~DJ~@%x>TfP zF>TX8)@v|t)q4GjRt<}5s6hLHwRel7>V@&r-O|Av(yh;Q1A{E>Ir>p+%dHD|=l+lT zpr(Dg&>#Nu=!)6bCLr-ZS%|;h)Ij$+e@r8_{qO19QvDe=&1tmpY*0lcA^Cc-#{9fQ z<~$*<&P$Q<_jy#<$40PMofM7aQ}C=jphI`4kLg}Z7CIN#26D{-4v-_CA-LiE@(%{y!BzsU%gG`Q?sjLUf%qFSl0y)2#ae*+EI>s|i`d^V$Dn)qmzqRq6VJRY|{4ujsIU%#bnqU6MR&-1I_43=|5(6Jr;Jvert) zE?S|Tmn}Tv<-??sxV5@9t}3D=>YZ0JrQe$CO~|EY=Lj9RM&4svQHPQL6%pV5fPFiH zfXDx;l@~et{*{U*#c#Dvzu)|znDO7$#CRx)Z&yp-}SrD{&|(MQtfUz~n35@RLfUy=aqrhCX0M}J_r5QsK~NmRCR|Nm&L z41UdsLjWxSUlL41r^0K&nCCK>fdR-!MYjFg(z9_mF^C|#ZQw?`)f6uVzF^`bRnVY& zo}@M06J&_+>w9@jpaO4snmU;0t-(zYW1qVBHtuD!d?%?AtN7Plp><-1Y8Rqb20ZaP zTCgn*-Sri4Q8Xn>=gNaWQ57%!D35UkA@ksOlPB*Dvw}t02ENAqw|kFhn%ZyyW%+t{ zNdM!uqEM^;2}f+tECHbwLmH*!nZVrb$-az%t50Y2pg(HqhvY-^-lb}>^6l{$jOI6} zo_kBzj%8aX|6H5M0Y<)7pzz_wLkIpRm!;PzY)9+24wk2&TT{w--phDGDCOz{cN_ca zpnm7`$oDy=HX%0i-`769*0M6(e5j-?(?24%)<)&46y0e&6@HCDZAm9W6Ib#Y#BF6- z=30crHGg+RRTe%VBC>T00OV6F+gQDAK38Ne3N9bm|62tPccBJi)5{B z4zc^Db72XiBd}v$CF|yU{Z=M|DZ%-(XarYNclODlb1Kz1_EKLy(NSLCN`eUl(rBCL zT*jx@wNvze0|TSqgE(QArOZU)_?qH(sj#TwzElLs9q)(0u!_P|R%Cy_0JFQxgGV>1 zz4?_uq<8_gM0`c*Hh|;UMz~vrg1gQXp{ufg`hM_qU;U>+zmvc5blCLSq@PrEBSGR# z&8=2Z4uXN`F3p73ueD1l{s{k$WipAvSh5W7ABe?4)t;r@V?y`bNB5FvBuE|0VRTb< zM1Hn^?DSsJY+sX@T5xW=#>T9VEV|?<(=6|ge$X6Sb05!LFdjDcoq*gM(Zq=t;_)Le&jyt(&9jzR73noru`a# zN*<`KwGa^gZU3-)MSLF0aFag#f0<>E(bYTeHmtdbns#|I)-$)mJ`q9ctQ8g0=ET?| zdO}eZ*b_p>ygRTtR^5Ggdam=Zb5wmd{}np+Jn1d_=M`~P=M67jj})fH4ztb5yQqQW z^C|C&^LHAK-u+ooIK)yM)QM?t;|<{P;;{`p=BclzAN#JzL4jCwXkQB1Dy{=^KR`=~ zTrr)y7eiYBzSNs_DvO=4A6#EgGS-zY%Vi)N*Yb`U;6o}KR}dq{r9pT5wqZ@3NOE8- z9-(}D|Nc5732CSYQbL)!gPQ#RbD8BhK3dl{sUuPvei0tkvnJBxDEAYTesU8H$)g(Plra{VH(v3u^CO1~(+ zU0O7#)jaS4{NcwA+LuSm&VBcX2#Im3xg)W}ySNw%->orn1taZ&+d)}8gJTqA!u|5P z{yv?zol_3|(1(%M(EVU=cp?L`{Pi|ixk{U)*guFML3P!OSlz;zGA#T+E@8@cgQ_mv1o7RSU=Zo_82F?&&2r;WE z@wk}JHYEZ9nYUc(Vv~iTCa3u8e4q(yq<29VoNbKk|`mq%I6u)My=gPIDuUb&lzf4`MEA9^g8u z)vp8|$$HE9m_BTV?lOosIGa4jud=jIbw)O2eCMfyw2*S8?hjWw^nqws$O*M$3I1)x zR0PWFb3$ySOcGTe1dz%N0l;RPc`x%05FtT^f^j{Yo!9>IaV6aUZ*?W>} zs4%E?srLW`CJh0GCIK@hTkrW7A15Iu%N&?Q^$0+!{Tv&|t^Y@u%!L zglTg&?Q5q#ijZ;&HBQ?FNPp;k3J5!&{^+SGq?AX~SiOM9jJMRpyP?RCr@z38AQyy&WRMaC;n4una$~nJKSp?q|s8F00c9?Q! zY_ovvjTFm+DeQM^LXJ#v0}6HRt3R1%5PT*}W!k8BEM;Jrj8dIceFo2fhzTqaB3KKk zGlCLI)gU25(#u6ch6GeB1k@eHq7l{EHXv0n6xE#ws#ri}08kkCf8hUt{|Ejb`2YW* zvg}0nSSX1m=76s?sZhRY$K=3dpJ+y*eDULGnL2}4>4nvW^7_<~wIM_5fjvwt4h1|g z)g0Z6ZFq9j<~9~b8((~TN{Z?ZQfw|is&Xp~AC61sj;xItKyCHdI|tCMC_LbXF>~vR z=w6V3^H=W4CbAgR4#xw}ETTwu2guW~=Crl@SMXv85jQ=%y!s^?m4PI0My7MWICO;- z175jm%&PcPWh8QdOU(#8bp4!N7ET-+)N}N2zk2)8ch|4Q&lPFNQgT-thu053`r*h3 z_8dI@G;`zn;lH$zX3RzIk`E8~`J=BBdR}qD%n@vVG1834)!pS1Y?zVkJGtsa(sB~y zNfMYKsOJb%5J(0ivK8d+l2D2y&5X!cg3BG!AJ}910|_${nF}sC1QF^nLIhzXk-Y#x z0)&1iK!O;Og0Ky!;`b~v%b$`S4E&fB)1NB4v@8wr( z&+NX4e^&o)ecb=)dd~C!{(1e6t?&9j{l8%U*k4)?`(L3;Qjw z#w7FS+U(94MaJKS!J9O8^$)36_J8;thW#2$y9i{bB{?M{QS_inZIJ!jwqAbfXYVd$ zQ5fC$6Nc9hFi8m^;oI-%C#BS|c8vy+@{jx6hFcf^_;2VRgkoN(0h!_VSGmgNPRsxI z8$rTo0LaYq-H5i&gtj81=&xU?H-Y2==G@uQV7E`@+2E9XQW@{&j`?EOktk|Ho{HU>ZqDzvgjwBmdex z&uZNd2C1h{{}2k6Ys9$*nFP3;K%u!MhW`uZy7Sn`1M1zs@Es&;z*Z>Gsh@-3Fe6pE zQD2@cqF((NrRevgvLsvM_8;;iNyJ5nyPyy?e!kvKjGj`6diRFBEe49Oa7wwkJFV7Z z$YT&DWloYu-H?3<0BKn9L&JYDT-SK~*6c5pi18P26$JESKRYj{T7Zk6KiRJcbvOO*{P56Q6s8msbeI3>|j>K9}Q9UBeq*inXKemCm`-<5|-$ZyN4u$(3 z&HcvqehFD%5Yrmykg-^d`=BSa8(i=>ZoC77^mWY{evp(km@aHqhUECBz76YiR+VYK zY_avFC~V3$=`6C4JhfHAQ@DZtUOwH`L;oYX6zK0-uI^?hS$ALfq}A7evR;ohJHij} zHSZdW?EKv9U1s4oD*<(0oQ*;MaQ6@cvGL zuHCPgm_NhVsgp^sfr*ia^Db}swo1?O(_Q2)y+S$CBm+g=9wCOUPbz(x)_GbaKa@A7 zuI&!ynLiZRT#V%_y_-D`0Z5lT*auoe{(U5NylTzFSJW()W-#F6*&A`LNO1bV#Y;QJ zSbLBnp|B^dtK|KIWC|No>JjWBWE@n7O)x{&^E(WMeMvp57#qA8m* zeTow*U@_86B#Fm*rxyYu5PRWaWHx8y> z*qmHEp(AMDl0v)ij(AY8fnH=~ZwwjVAbu*m5;xPfidh@ov6d8g zfJsi&!QyK53Es%sC39ts;54V68koALD4b|%tNHW0bIkZAJKa=W&FomJSEDT>W1xIX z1x%Z>AvNIsSPLcn3RTcHXb@KB?cuM)=x6fcIx>&(GxqZ8w3p#jJ(GVgc*`c0HG}dv zIop&Qim!K1NFwic%07KcjWgHBPUkq7f~lj;TPqVGTiT#cUeim>;nY`>h@a*S{qQex zQ`z62WK|Mj)Y{tfF{;T4P;c8$Q|KU?Joh zIkA^z%X7z|r>4aTh@|StTi!-r1D!g=zb#3d#{{&K3CqE$Iz-UH<%37c zRfkO`&uM%#AD3PHv`g5t0e^O%nVL0d{Xlx^EjEC3#skF@`zl-7PF^0oxW)1!C!JxR zWvuAHH?)61FKA1QeT*_sY7;_Id#!GmV4n`MO{~sv}VLSK` zXRw=Y=Clz*00B(5y^K;gCZMAzjT5+c3IC=)l(9VIDdatpxj3y89WwI|bH&$!ZEvp` zPR!T@#!(|KfI-w?!&+7$N3F6>tD{YO4Qg$d_`nNEdfVCha9vaPn0jI0`)`@*72hq! zpU5ND^P*RoEkbD5o#az(-g=Y)L>HH>Oc%}$ zT3Rs_ih0;4+Lv4Y;@Iv(;fUbQ=i-G(#>vghec~*j(I#r|5mqFiJBpzi&hzEcD{u$< zRsm0BVYn=pT;0>R(itW|*D&;O%bOc7et9ACaH#J>z3A1A~6fdP>pmbM%xzm4>|;c_?B+%sl;Qs2{t!60$^u zH1t@9^6>;?!FuusnISi$f5CL&;z?EqJN$FBuWDA#D5`cy_UvCFIVvf{c?4N0teh;d zET$7aVbj08KTQS!x?Nd1Is8q8qFzs}a=!@nJ;7FSfCY^T@D-gpw`w<6e#X3+;O}1h z$%I!M)0bg|EKUA04Qjn@+x{Rj8vt6Wn!R|3A92z}^$KfF5(#CWr4y#~re1CN4i4w0 z#GsypBR{xA3Er7sgAi(|}1-W?s~n$7?K|9WL8kpVfw-;#b9 z+mn;=ep!162U5R>_t}fOt~tE?s#m( zO-S$7>Ay6*hHdZ)7_oU915WYYCIX;hFI-U2EWYX!pllONr@Q--2o~`!isi6vTPLJ4@(|o=%NHYjo0_S&q*UQIROw@*N-By@PaQ&;YxFZ0aR zX&}LeOEz);#m~Hwm^VAY8DK}b$F4bo{jMN?d!lxKPhNklzr^Cd`0f4oJr^z=I|l`* zm8AHm*fPV`0=lF3Pnnp}&J0N1X@}-D94YvmUabFrLGSnTz7Mu^21F#O5tN#CuY9Vh zUZBH=ez%h*wkf0hBtXJh1SN3d+IF{gzT7lp)j}n?03lt;XSQRAh7qd&v;RwTYDuQ# zbI2*r<>?x-G0@hM{;%{VBD7nLKt~D`T~-HAt5;h%i0_=Ifs=yHma5dhJ+QMG?Ux(a z|E?1CMy1!~oA`FP!k~iG=t&5#>bVdz=peT8HMB6Y)#7PpETtNryT^+Rv3vpJaF^zP z{H}0-LyV9Fu21ID%wO9f1IKlFr1p4c{o-?03vyB-tr5duk^&L$;m_|f$vs`^Sl{j2 z95}oY{LlY+=ZS%J+tZoXCd0*sSU7w^gjovXn+g7uyra5{cU49@yHf#Z^Jl-$9cIfo z+AJuxH$VLb=#+uBbVmUjnx zxb1pZ@-O9=AIk4@S)m6fJ2?{HrNYwwnL3a45muuNjr;6$O`bGEM0T4A2_S$t=86*- zcO+0mywg*j + From the Round world,\nHello, %1$s! + \ No newline at end of file diff --git a/watchapp/src/main/res/values/strings.xml b/watchapp/src/main/res/values/strings.xml new file mode 100644 index 000000000..7da1efdcb --- /dev/null +++ b/watchapp/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + watchapp + + From the Square world,\nHello, %1$s! + \ No newline at end of file diff --git a/watchapp/src/main/res/values/styles.xml b/watchapp/src/main/res/values/styles.xml new file mode 100644 index 000000000..85dec6d67 --- /dev/null +++ b/watchapp/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + From 85f55d7ca797a277d5346defe1baefa5f09f3150 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 21 Dec 2024 22:11:00 +0700 Subject: [PATCH 02/32] Implement quick pair item --- .../presentation/quickpairs/QuickPairItem.kt | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt index 6361b095b..644a24913 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt @@ -1,26 +1,37 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs +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.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape 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.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.wear.compose.material.Icon import androidx.wear.compose.material.Text import dev.arkbuilders.rate.core.domain.CurrUtils import dev.arkbuilders.rate.core.domain.model.Amount import dev.arkbuilders.rate.core.domain.model.CurrencyCode +import dev.arkbuilders.rate.core.presentation.theme.ArkColor import dev.arkbuilders.rate.core.presentation.utils.IconUtils import dev.arkbuilders.rate.feature.quick.domain.model.PinnedQuickPair import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair @@ -32,6 +43,9 @@ fun QuickPairItem( modifier: Modifier = Modifier, quick: PinnedQuickPair, ) { + var isExpanded by remember { + mutableStateOf(true) + } Column( modifier = modifier .padding(12.dp) @@ -44,29 +58,73 @@ fun QuickPairItem( modifier = modifier.size(16.dp), code = quick.pair.from ) - CurrIcon( - modifier = modifier.size(16.dp), - code = quick.pair.to.first().code + if (quick.pair.to.size > 1) { + Box( + modifier = + modifier + .size(16.dp) + .background(ArkColor.BGTertiary, CircleShape), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "+ ${quick.pair.to.size}", + fontWeight = FontWeight.SemiBold, + fontSize = 8.sp, + color = ArkColor.TextTertiary, + ) + } + } else { + CurrIcon( + modifier = modifier.size(16.dp), + code = quick.pair.to.first().code + ) + } + + + Text( + text = "2 mins ago", + modifier = modifier.fillMaxWidth(), + textAlign = TextAlign.End + ) + } + if (isExpanded) { + Text( + modifier = modifier.fillMaxWidth(), + text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = ", + ) + quick.pair.to.forEach { + Row( + modifier = Modifier.padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CurrIcon( + modifier = Modifier.size(20.dp), + code = it.code + ) + Text( + modifier = modifier + .fillMaxWidth() + .padding(start = 8.dp), + text = "${CurrUtils.prepareToDisplay(it.value)} ${it.code}", + ) + } + } + } else { + Text( + modifier = modifier.fillMaxWidth(), + text = "${quick.pair.from} to ${ + quick.pair.to.joinToString( + separator = ", ", + ) { it.code } + }", + ) + Text( + modifier = modifier.fillMaxWidth(), + text = + "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = " + + "${CurrUtils.prepareToDisplay(quick.pair.to.first().value)} ${quick.pair.to.first().code}", ) } - Text( - modifier = modifier.fillMaxWidth(), - text = "${quick.pair.from} to ${ - quick.pair.to.joinToString( - separator = ", ", - ) { it.code } - }", - ) - Text( - modifier = modifier.fillMaxWidth(), - text = - "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = " + - "${CurrUtils.prepareToDisplay(quick.actualTo.first().value)} ${quick.actualTo.first().code}", - ) - Text( - modifier = modifier.fillMaxWidth(), - text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.actualTo.first().code}", - ) } } @@ -94,12 +152,15 @@ fun QuickPairItemPreview() { id = 1, from = "BTC", amount = BigDecimal.valueOf(1.2), - to = listOf(Amount("USD", BigDecimal.valueOf(12.0))), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, group = null ), - actualTo = listOf(Amount("USD", BigDecimal.valueOf(12.0))), + actualTo = listOf(Amount("USD", BigDecimal.valueOf(12.0))), refreshDate = OffsetDateTime.now(), ) ) From 604e0e2f757db3984c7f3967b837150226eb5f0e Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sun, 22 Dec 2024 22:07:24 +0700 Subject: [PATCH 03/32] Update quick pair class --- .../presentation/quickpairs/QuickPairItem.kt | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt index 644a24913..df723531b 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt @@ -41,7 +41,7 @@ import java.time.OffsetDateTime @Composable fun QuickPairItem( modifier: Modifier = Modifier, - quick: PinnedQuickPair, + quick: QuickPair, ) { var isExpanded by remember { mutableStateOf(true) @@ -56,9 +56,9 @@ fun QuickPairItem( Row(modifier = modifier.align(Alignment.Start)) { CurrIcon( modifier = modifier.size(16.dp), - code = quick.pair.from + code = quick.from ) - if (quick.pair.to.size > 1) { + if (quick.to.size > 1) { Box( modifier = modifier @@ -67,7 +67,7 @@ fun QuickPairItem( ) { Text( modifier = Modifier.align(Alignment.Center), - text = "+ ${quick.pair.to.size}", + text = "+ ${quick.to.size}", fontWeight = FontWeight.SemiBold, fontSize = 8.sp, color = ArkColor.TextTertiary, @@ -76,7 +76,7 @@ fun QuickPairItem( } else { CurrIcon( modifier = modifier.size(16.dp), - code = quick.pair.to.first().code + code = quick.to.first().code ) } @@ -90,9 +90,9 @@ fun QuickPairItem( if (isExpanded) { Text( modifier = modifier.fillMaxWidth(), - text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = ", + text = "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = ", ) - quick.pair.to.forEach { + quick.to.forEach { Row( modifier = Modifier.padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically, @@ -112,8 +112,8 @@ fun QuickPairItem( } else { Text( modifier = modifier.fillMaxWidth(), - text = "${quick.pair.from} to ${ - quick.pair.to.joinToString( + text = "${quick.from} to ${ + quick.to.joinToString( separator = ", ", ) { it.code } }", @@ -121,8 +121,8 @@ fun QuickPairItem( Text( modifier = modifier.fillMaxWidth(), text = - "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = " + - "${CurrUtils.prepareToDisplay(quick.pair.to.first().value)} ${quick.pair.to.first().code}", + "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = " + + "${CurrUtils.prepareToDisplay(quick.to.first().value)} ${quick.to.first().code}", ) } } @@ -147,21 +147,17 @@ fun CurrIcon( @Composable fun QuickPairItemPreview() { QuickPairItem( - quick = PinnedQuickPair( - pair = QuickPair( - id = 1, - from = "BTC", - amount = BigDecimal.valueOf(1.2), - to = listOf( - Amount("USD", BigDecimal.valueOf(12.0)), - Amount("EUR", BigDecimal.valueOf(12.0)) - ), - calculatedDate = OffsetDateTime.now(), - pinnedDate = null, - group = null + quick = QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) ), - actualTo = listOf(Amount("USD", BigDecimal.valueOf(12.0))), - refreshDate = OffsetDateTime.now(), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null ) ) } From 9566b52d21e15ac6136b3f6da08760a86eb8aff3 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sun, 22 Dec 2024 22:27:35 +0700 Subject: [PATCH 04/32] Update composables --- .../quickpairs/composables/QuickPairsEmpty.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt new file mode 100644 index 000000000..35e3cf7d1 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt @@ -0,0 +1,59 @@ +package dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.CoreRDrawable +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun QuickPairsEmpty( + modifier: Modifier = Modifier +) { + Column( + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(id = CoreRDrawable.ic_empty_quick), + contentDescription = "", + tint = Color.Unspecified, + ) + Text( + modifier = Modifier.padding(top = 16.dp), + text = "Empty Here, But Full of Possibilities!", + fontWeight = FontWeight.SemiBold, + fontSize = 20.sp, + ) + Text( + modifier = Modifier.padding(top = 6.dp, start = 24.dp, end = 24.dp), + text = "Calculate currency from Rate App", + fontSize = 14.sp, + lineHeight = 20.sp, + color = ArkColor.TextTertiary, + textAlign = TextAlign.Center, + ) + + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun QuickPairItemPreview() { + QuickPairsEmpty() +} From 21c8768833ab8cae9dd0e9c1ab609afe16cc56b8 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sun, 22 Dec 2024 22:49:11 +0700 Subject: [PATCH 05/32] Tweak UI --- watchapp/build.gradle.kts | 5 ++--- .../quickpairs/composables/QuickPairsEmpty.kt | 13 ++++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts index 57a01bdb9..5bc638c8c 100644 --- a/watchapp/build.gradle.kts +++ b/watchapp/build.gradle.kts @@ -4,12 +4,11 @@ plugins { } android { namespace = "dev.arkbuilders.rate.watchapp" - compileSdk = 34 + compileSdk = libs.versions.compileSdk.get().toInt() defaultConfig { applicationId = "dev.arkbuilders.rate.watchapp" - minSdk = 29 - targetSdk = 34 + minSdk = libs.versions.minSdk.get().toInt() versionCode = 1 versionName = "1.0" vectorDrawables { diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt index 35e3cf7d1..db2cca03a 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt @@ -3,6 +3,7 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -35,16 +36,18 @@ fun QuickPairsEmpty( tint = Color.Unspecified, ) Text( - modifier = Modifier.padding(top = 16.dp), + modifier = modifier + .padding(horizontal = 8.dp) + .fillMaxWidth(), text = "Empty Here, But Full of Possibilities!", fontWeight = FontWeight.SemiBold, - fontSize = 20.sp, + fontSize = 16.sp, + textAlign = TextAlign.Center, ) Text( - modifier = Modifier.padding(top = 6.dp, start = 24.dp, end = 24.dp), + modifier = modifier.padding(start = 24.dp, end = 24.dp), text = "Calculate currency from Rate App", - fontSize = 14.sp, - lineHeight = 20.sp, + fontSize = 12.sp, color = ArkColor.TextTertiary, textAlign = TextAlign.Center, ) From f7f79796960a5fdbfee330bc45751e296a8e14cd Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Wed, 25 Dec 2024 22:43:47 +0700 Subject: [PATCH 06/32] Tweak spacing --- .../presentation/quickpairs/QuickPairItem.kt | 164 ----------------- .../quickpairs/composables/QuickPairItem.kt | 166 ++++++++++++++++++ 2 files changed, 166 insertions(+), 164 deletions(-) delete mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt deleted file mode 100644 index df723531b..000000000 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairItem.kt +++ /dev/null @@ -1,164 +0,0 @@ -package dev.arkbuilders.rate.watchapp.presentation.quickpairs - -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.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -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.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.wear.compose.material.Icon -import androidx.wear.compose.material.Text -import dev.arkbuilders.rate.core.domain.CurrUtils -import dev.arkbuilders.rate.core.domain.model.Amount -import dev.arkbuilders.rate.core.domain.model.CurrencyCode -import dev.arkbuilders.rate.core.presentation.theme.ArkColor -import dev.arkbuilders.rate.core.presentation.utils.IconUtils -import dev.arkbuilders.rate.feature.quick.domain.model.PinnedQuickPair -import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair -import java.math.BigDecimal -import java.time.OffsetDateTime - -@Composable -fun QuickPairItem( - modifier: Modifier = Modifier, - quick: QuickPair, -) { - var isExpanded by remember { - mutableStateOf(true) - } - Column( - modifier = modifier - .padding(12.dp) - .fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row(modifier = modifier.align(Alignment.Start)) { - CurrIcon( - modifier = modifier.size(16.dp), - code = quick.from - ) - if (quick.to.size > 1) { - Box( - modifier = - modifier - .size(16.dp) - .background(ArkColor.BGTertiary, CircleShape), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "+ ${quick.to.size}", - fontWeight = FontWeight.SemiBold, - fontSize = 8.sp, - color = ArkColor.TextTertiary, - ) - } - } else { - CurrIcon( - modifier = modifier.size(16.dp), - code = quick.to.first().code - ) - } - - - Text( - text = "2 mins ago", - modifier = modifier.fillMaxWidth(), - textAlign = TextAlign.End - ) - } - if (isExpanded) { - Text( - modifier = modifier.fillMaxWidth(), - text = "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = ", - ) - quick.to.forEach { - Row( - modifier = Modifier.padding(top = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - CurrIcon( - modifier = Modifier.size(20.dp), - code = it.code - ) - Text( - modifier = modifier - .fillMaxWidth() - .padding(start = 8.dp), - text = "${CurrUtils.prepareToDisplay(it.value)} ${it.code}", - ) - } - } - } else { - Text( - modifier = modifier.fillMaxWidth(), - text = "${quick.from} to ${ - quick.to.joinToString( - separator = ", ", - ) { it.code } - }", - ) - Text( - modifier = modifier.fillMaxWidth(), - text = - "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = " + - "${CurrUtils.prepareToDisplay(quick.to.first().value)} ${quick.to.first().code}", - ) - } - } -} - -@Composable -fun CurrIcon( - modifier: Modifier = Modifier, - code: CurrencyCode, -) { - val ctx = LocalContext.current - Icon( - modifier = modifier, - painter = painterResource(id = IconUtils.iconForCurrCode(ctx, code)), - contentDescription = code, - tint = Color.Unspecified, - ) -} - - -@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) -@Composable -fun QuickPairItemPreview() { - QuickPairItem( - quick = QuickPair( - id = 1, - from = "BTC", - amount = BigDecimal.valueOf(1.2), - to = listOf( - Amount("USD", BigDecimal.valueOf(12.0)), - Amount("EUR", BigDecimal.valueOf(12.0)) - ), - calculatedDate = OffsetDateTime.now(), - pinnedDate = null, - group = null - ) - ) -} - diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt new file mode 100644 index 000000000..e1c4162bd --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt @@ -0,0 +1,166 @@ +package dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables + +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.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.Card +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.domain.CurrUtils +import dev.arkbuilders.rate.core.domain.model.Amount +import dev.arkbuilders.rate.core.domain.model.CurrencyCode +import dev.arkbuilders.rate.core.presentation.theme.ArkColor +import dev.arkbuilders.rate.core.presentation.utils.IconUtils +import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair +import java.math.BigDecimal +import java.time.OffsetDateTime + +@Composable +fun QuickPairItem( + modifier: Modifier = Modifier, + quick: QuickPair, +) { + var isExpanded by remember { + mutableStateOf(true) + } + Card( + onClick = { /*TODO*/ }, + modifier = modifier + .padding(horizontal = 12.dp, vertical = 4.dp) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(modifier = modifier.align(Alignment.Start)) { + CurrIcon( + modifier = modifier.size(16.dp), + code = quick.from + ) + if (quick.to.size > 1) { + Box( + modifier = + modifier + .size(16.dp) + .background(ArkColor.BGTertiary, CircleShape), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "+ ${quick.to.size}", + fontWeight = FontWeight.SemiBold, + fontSize = 8.sp, + color = ArkColor.TextTertiary, + ) + } + } else { + CurrIcon( + modifier = modifier.size(16.dp), + code = quick.to.first().code + ) + } + + + Text( + text = "2 mins ago", + modifier = modifier.fillMaxWidth(), + textAlign = TextAlign.End + ) + } + if (isExpanded) { + Text( + modifier = modifier.fillMaxWidth(), + text = "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = ", + ) + quick.to.forEach { + Row( + modifier = Modifier.padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CurrIcon( + modifier = Modifier.size(20.dp), + code = it.code + ) + Text( + modifier = modifier + .fillMaxWidth() + .padding(start = 8.dp), + text = "${CurrUtils.prepareToDisplay(it.value)} ${it.code}", + ) + } + } + } else { + Text( + modifier = modifier.fillMaxWidth(), + text = "${quick.from} to ${ + quick.to.joinToString( + separator = ", ", + ) { it.code } + }", + ) + Text( + modifier = modifier.fillMaxWidth(), + text = + "${CurrUtils.prepareToDisplay(quick.amount)} ${quick.from} = " + + "${CurrUtils.prepareToDisplay(quick.to.first().value)} ${quick.to.first().code}", + ) + } + } + } +} + +@Composable +fun CurrIcon( + modifier: Modifier = Modifier, + code: CurrencyCode, +) { + val ctx = LocalContext.current + Icon( + modifier = modifier, + painter = painterResource(id = IconUtils.iconForCurrCode(ctx, code)), + contentDescription = code, + tint = Color.Unspecified, + ) +} + + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun QuickPairItemPreview() { + QuickPairItem( + quick = QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ) + ) +} + From 67d32154a47274ac2213382edeffbc6c37a41c03 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Wed, 25 Dec 2024 22:44:54 +0700 Subject: [PATCH 07/32] Display empty and quick pairs list --- .../quickpairs/QuickPairsScreen.kt | 26 ++++++++++++++++++- .../quickpairs/composables/QuickPairsEmpty.kt | 10 +++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index cbb571f80..43759171c 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -1,8 +1,32 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairItem +import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairsEmpty @Composable -fun QuickPairsScreen(modifier: Modifier = Modifier ) { +fun QuickPairsScreen( + modifier: Modifier = Modifier, + viewModel: QuickPairsViewModel = QuickPairsViewModel() +) { + val quickPairsList = viewModel.quickPairs.collectAsState().value + + if (quickPairsList.isEmpty()) { + QuickPairsEmpty(modifier = modifier.fillMaxSize()) + } else { + LazyColumn(modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp) + ) { + items(quickPairsList) { quickPair -> + QuickPairItem(quick = quickPair) + } + } + } } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt index db2cca03a..941b829e3 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt @@ -1,6 +1,5 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -19,16 +18,13 @@ import androidx.compose.ui.unit.sp import androidx.wear.compose.material.Icon import androidx.wear.compose.material.Text import dev.arkbuilders.rate.core.presentation.CoreRDrawable -import dev.arkbuilders.rate.core.presentation.theme.ArkColor @Composable fun QuickPairsEmpty( modifier: Modifier = Modifier ) { Column( - modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center ) { Icon( painter = painterResource(id = CoreRDrawable.ic_empty_quick), @@ -45,10 +41,10 @@ fun QuickPairsEmpty( textAlign = TextAlign.Center, ) Text( - modifier = modifier.padding(start = 24.dp, end = 24.dp), + modifier = modifier.fillMaxWidth() + .padding(horizontal = 8.dp), text = "Calculate currency from Rate App", fontSize = 12.sp, - color = ArkColor.TextTertiary, textAlign = TextAlign.Center, ) @@ -57,6 +53,6 @@ fun QuickPairsEmpty( @Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) @Composable -fun QuickPairItemPreview() { +fun QuickPairEmptyPreview() { QuickPairsEmpty() } From 7726f4eb54accf9d34685fc17c1e65a8c3634d1d Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Wed, 25 Dec 2024 22:45:07 +0700 Subject: [PATCH 08/32] Display empty and quick pairs list --- .../watchapp/presentation/MainActivity.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 02bc6090a..11bf93fa4 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -1,9 +1,3 @@ -/* While this template provides a good starting point for using Wear Compose, you can always - * take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter and - * https://github.com/android/wear-os-samples/tree/main/ComposeAdvanced to find the most up to date - * changes to the libraries and their usages. - */ - package dev.arkbuilders.rate.watchapp.presentation import android.os.Bundle @@ -22,9 +16,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Scaffold import androidx.wear.compose.material.Text import androidx.wear.compose.material.TimeText +import androidx.wear.compose.material.Vignette +import androidx.wear.compose.material.VignettePosition import dev.arkbuilders.rate.watchapp.R +import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme class MainActivity : ComponentActivity() { @@ -36,7 +34,14 @@ class MainActivity : ComponentActivity() { setTheme(android.R.style.Theme_DeviceDefault) setContent { - WearApp("Android") + Scaffold( + vignette = { + Vignette(vignettePosition = VignettePosition.TopAndBottom) + } + ) { + QuickPairsScreen() + } + } } } @@ -70,4 +75,4 @@ fun Greeting(greetingName: String) { @Composable fun DefaultPreview() { WearApp("Preview Android") -} \ No newline at end of file +} From 1bbef9acf6e7008df10a130bc58aa2aa46bbb60c Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Wed, 25 Dec 2024 22:45:20 +0700 Subject: [PATCH 09/32] Create viewmodel with fake data --- .../quickpairs/QuickPairsViewModel.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt new file mode 100644 index 000000000..2dd8f0152 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt @@ -0,0 +1,97 @@ +package dev.arkbuilders.rate.watchapp.presentation.quickpairs + +import androidx.lifecycle.ViewModel +import dev.arkbuilders.rate.core.domain.model.Amount +import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import java.math.BigDecimal +import java.time.OffsetDateTime + +class QuickPairsViewModel : ViewModel() { + + private val _quickPairs: MutableStateFlow> = MutableStateFlow(listOf()) + val quickPairs: StateFlow> = _quickPairs + + init { + val a = listOf( + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ), + + QuickPair( + id = 1, + from = "BTC", + amount = BigDecimal.valueOf(1.2), + to = listOf( + Amount("USD", BigDecimal.valueOf(12.0)), + Amount("EUR", BigDecimal.valueOf(12.0)) + ), + calculatedDate = OffsetDateTime.now(), + pinnedDate = null, + group = null + ) + ) + _quickPairs.value = a + } + +} From 52a9aafd6414a6d549384498e0ade81e6e1e911d Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Thu, 26 Dec 2024 23:13:49 +0700 Subject: [PATCH 10/32] Create option item --- .../addquickpairs/composables/OptionItem.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt new file mode 100644 index 000000000..28ef67c80 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt @@ -0,0 +1,76 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +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.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.OutlinedButton +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.CoreRDrawable +import dev.arkbuilders.rate.core.presentation.CoreRString +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun OptionItem( + modifier: Modifier = Modifier, + icon: Painter, + text: String, + onClick: () -> Unit, +) { + OutlinedButton( + modifier = modifier + .fillMaxWidth() + .padding(8.dp), + onClick = onClick, + shape = RoundedCornerShape(20), + border = ButtonDefaults.buttonBorder(BorderStroke(1.dp, ArkColor.Border)), + colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White), + ) { + Row( + modifier = Modifier.fillMaxWidth().align(Alignment.Center), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = icon, + contentDescription = "", + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "text", + fontWeight = FontWeight.SemiBold, + ) + } + } +} + + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun OptionItemPreview(modifier: Modifier = Modifier) { + OptionItem( + modifier = modifier.fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_download), + text = stringResource(id = CoreRString.edit), + onClick = {}, + ) +} From c63aa8e14b7fe71531a45f9b1900b8b2b0bc8d07 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Thu, 26 Dec 2024 23:14:02 +0700 Subject: [PATCH 11/32] Implement add quick pair screen --- .../addquickpairs/AddQuickPairs.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt new file mode 100644 index 000000000..67ecdbdc7 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt @@ -0,0 +1,48 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.CoreRDrawable +import dev.arkbuilders.rate.core.presentation.CoreRString +import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.OptionItem + +@Composable +fun AddQuickPairs(modifier: Modifier = Modifier) { + Scaffold(modifier = modifier.fillMaxSize()) { + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center + ) { + Text( + modifier = modifier.fillMaxWidth(), + text = "Options", + textAlign = TextAlign.Center + ) + OptionItem( + modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally), + icon = painterResource(id = CoreRDrawable.ic_download), + text = stringResource(id = CoreRString.edit), + onClick = {}, + ) + } + + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun AddQuickPairsPreview() { + AddQuickPairs() +} From 63fbd61850195e597b5470f8abcf68e990ce803b Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Thu, 26 Dec 2024 23:14:09 +0700 Subject: [PATCH 12/32] Create viewmodel --- .../presentation/addquickpairs/AddQuickPairsViewModel.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsViewModel.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsViewModel.kt new file mode 100644 index 000000000..1b95d3563 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsViewModel.kt @@ -0,0 +1,7 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs + +import androidx.lifecycle.ViewModel + +class AddQuickPairsViewModel : ViewModel() { + +} From eb9dd008233e14bff16eac8f6e842f15d3dd1b0e Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 28 Dec 2024 22:28:50 +0700 Subject: [PATCH 13/32] Implement options screen --- .../src/main/res/drawable/ic_update.xml | 13 ++++ .../src/main/res/values/strings.xml | 2 + .../watchapp/presentation/MainActivity.kt | 4 +- .../addquickpairs/AddQuickPairs.kt | 62 ++++++++++++++++--- .../addquickpairs/composables/OptionItem.kt | 16 +++-- 5 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 core/presentation/src/main/res/drawable/ic_update.xml diff --git a/core/presentation/src/main/res/drawable/ic_update.xml b/core/presentation/src/main/res/drawable/ic_update.xml new file mode 100644 index 000000000..b8df7f914 --- /dev/null +++ b/core/presentation/src/main/res/drawable/ic_update.xml @@ -0,0 +1,13 @@ + + + diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index 36cb9383f..8e09573f8 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -146,6 +146,8 @@ Delete Re-use Edit + Update + Options Oops, request time out! Please check your connection and refresh the page to try again. diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 11bf93fa4..a92422201 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -22,6 +22,7 @@ import androidx.wear.compose.material.TimeText import androidx.wear.compose.material.Vignette import androidx.wear.compose.material.VignettePosition import dev.arkbuilders.rate.watchapp.R +import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairs import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme @@ -39,7 +40,8 @@ class MainActivity : ComponentActivity() { Vignette(vignettePosition = VignettePosition.TopAndBottom) } ) { - QuickPairsScreen() + AddQuickPairs() +// QuickPairsScreen() } } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt index 67ecdbdc7..b6a26353a 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -20,24 +21,71 @@ import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.Opti @Composable fun AddQuickPairs(modifier: Modifier = Modifier) { - Scaffold(modifier = modifier.fillMaxSize()) { - Column( - modifier = modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center - ) { + LazyColumn( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + + Text( modifier = modifier.fillMaxWidth(), text = "Options", textAlign = TextAlign.Center ) + } + item { + + OptionItem( + modifier = Modifier + .fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_update), + text = stringResource(id = CoreRString.update), + onClick = {}, + ) + } + item { + OptionItem( - modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally), - icon = painterResource(id = CoreRDrawable.ic_download), + modifier = Modifier + .fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_pin), + text = stringResource(id = CoreRString.pin), + onClick = {}, + ) + } + + item { + + OptionItem( + modifier = Modifier + .fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_edit), text = stringResource(id = CoreRString.edit), onClick = {}, ) } + item { + OptionItem( + modifier = Modifier + .fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_reuse), + text = stringResource(id = CoreRString.re_use), + onClick = {}, + ) + } + item { + OptionItem( + modifier = Modifier + .fillMaxWidth(), + icon = painterResource(id = CoreRDrawable.ic_delete), + text = stringResource(id = CoreRString.delete), + onClick = {}, + isDeleteButton = true + ) + } } } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt index 28ef67c80..714df88e7 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt @@ -35,6 +35,7 @@ fun OptionItem( icon: Painter, text: String, onClick: () -> Unit, + isDeleteButton: Boolean = false, ) { OutlinedButton( modifier = modifier @@ -42,11 +43,18 @@ fun OptionItem( .padding(8.dp), onClick = onClick, shape = RoundedCornerShape(20), - border = ButtonDefaults.buttonBorder(BorderStroke(1.dp, ArkColor.Border)), - colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White), + border = ButtonDefaults.buttonBorder( + borderStroke = BorderStroke( + width = 1.dp, + color = if (isDeleteButton) Color.Red else ArkColor.Border + ), + ), + colors = ButtonDefaults.outlinedButtonColors(contentColor = if (isDeleteButton) Color.Red else Color.White), ) { Row( - modifier = Modifier.fillMaxWidth().align(Alignment.Center), + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { @@ -56,7 +64,7 @@ fun OptionItem( ) Spacer(modifier = Modifier.width(8.dp)) Text( - text = "text", + text = text, fontWeight = FontWeight.SemiBold, ) } From e0996f4e11ac9f2c81867a64b8c0cfc8a24fe38d Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Tue, 7 Jan 2025 21:48:19 +0700 Subject: [PATCH 14/32] Improve list effect --- gradle/libs.versions.toml | 2 ++ watchapp/build.gradle.kts | 1 + .../presentation/addquickpairs/AddQuickPairs.kt | 11 ++--------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aef266a5d..55c5dc007 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +composeMaterialVersion = "1.4.0" kotlin = "1.9.22" agp = "8.1.3" coreKtx = "1.13.1" @@ -44,6 +45,7 @@ composeFoundation = "1.2.1" coreSplashscreen = "1.0.1" [libraries] +androidx-compose-material-v140 = { module = "androidx.wear.compose:compose-material", version.ref = "composeMaterialVersion" } ark-about = { module = "dev.arkbuilders.components:about", version.ref = "arkAbout" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts index 5bc638c8c..5552ee37f 100644 --- a/watchapp/build.gradle.kts +++ b/watchapp/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { implementation(project(":feature:quick")) implementation(project(":core:domain")) implementation(project(":core:presentation")) + implementation(libs.androidx.compose.material.v140) implementation(libs.play.services.wearable) implementation(platform(libs.androidx.compose.bom)) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt index b6a26353a..a68e3e203 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt @@ -1,10 +1,8 @@ package dev.arkbuilders.rate.watchapp.presentation.addquickpairs import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -13,7 +11,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text import dev.arkbuilders.rate.core.presentation.CoreRDrawable import dev.arkbuilders.rate.core.presentation.CoreRString @@ -21,14 +19,12 @@ import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.Opti @Composable fun AddQuickPairs(modifier: Modifier = Modifier) { - LazyColumn( + ScalingLazyColumn( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { item { - - Text( modifier = modifier.fillMaxWidth(), text = "Options", @@ -36,7 +32,6 @@ fun AddQuickPairs(modifier: Modifier = Modifier) { ) } item { - OptionItem( modifier = Modifier .fillMaxWidth(), @@ -46,7 +41,6 @@ fun AddQuickPairs(modifier: Modifier = Modifier) { ) } item { - OptionItem( modifier = Modifier .fillMaxWidth(), @@ -57,7 +51,6 @@ fun AddQuickPairs(modifier: Modifier = Modifier) { } item { - OptionItem( modifier = Modifier .fillMaxWidth(), From ae36e647bab2cb4e22595a94ed429adc725cab33 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Tue, 7 Jan 2025 21:49:42 +0700 Subject: [PATCH 15/32] Extract composable and improve imports --- .../arkbuilders/rate/watchapp/presentation/MainActivity.kt | 5 ++--- .../presentation/addquickpairs/composables/OptionItem.kt | 2 -- .../{AddQuickPairs.kt => composables/OptionsMenu.kt} | 7 +++---- 3 files changed, 5 insertions(+), 9 deletions(-) rename watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/{AddQuickPairs.kt => composables/OptionsMenu.kt} (94%) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index a92422201..81c882eb7 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -22,8 +22,7 @@ import androidx.wear.compose.material.TimeText import androidx.wear.compose.material.Vignette import androidx.wear.compose.material.VignettePosition import dev.arkbuilders.rate.watchapp.R -import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairs -import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen +import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.OptionsMenu import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme class MainActivity : ComponentActivity() { @@ -40,7 +39,7 @@ class MainActivity : ComponentActivity() { Vignette(vignettePosition = VignettePosition.TopAndBottom) } ) { - AddQuickPairs() + OptionsMenu() // QuickPairsScreen() } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt index 714df88e7..93997adac 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -20,7 +19,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.wear.compose.material.ButtonDefaults import androidx.wear.compose.material.Icon import androidx.wear.compose.material.OutlinedButton diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt similarity index 94% rename from watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt rename to watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt index a68e3e203..4c20abdd9 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairs.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt @@ -1,4 +1,4 @@ -package dev.arkbuilders.rate.watchapp.presentation.addquickpairs +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize @@ -15,10 +15,9 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text import dev.arkbuilders.rate.core.presentation.CoreRDrawable import dev.arkbuilders.rate.core.presentation.CoreRString -import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.OptionItem @Composable -fun AddQuickPairs(modifier: Modifier = Modifier) { +fun OptionsMenu(modifier: Modifier = Modifier) { ScalingLazyColumn( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, @@ -85,5 +84,5 @@ fun AddQuickPairs(modifier: Modifier = Modifier) { @Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) @Composable fun AddQuickPairsPreview() { - AddQuickPairs() + OptionsMenu() } From 0c5a42a72ad72748b7f52e4d2c622619414bd261 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Tue, 7 Jan 2025 21:52:12 +0700 Subject: [PATCH 16/32] Improve list effect --- .../presentation/quickpairs/QuickPairsScreen.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index 43759171c..9d4c59030 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -2,12 +2,11 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairItem import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairsEmpty @@ -21,11 +20,12 @@ fun QuickPairsScreen( if (quickPairsList.isEmpty()) { QuickPairsEmpty(modifier = modifier.fillMaxSize()) } else { - LazyColumn(modifier = modifier.fillMaxSize(), + ScalingLazyColumn( + modifier = modifier.fillMaxSize(), contentPadding = PaddingValues(4.dp) ) { - items(quickPairsList) { quickPair -> - QuickPairItem(quick = quickPair) + items(quickPairsList.size, key = null) { idx -> + QuickPairItem(quick = quickPairsList[idx]) } } } From 3f29554d9ad91b42b53b93b2533a185d97734eca Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Tue, 7 Jan 2025 22:22:03 +0700 Subject: [PATCH 17/32] Set up navigation --- gradle/libs.versions.toml | 4 + watchapp/build.gradle.kts | 2 + .../watchapp/presentation/MainActivity.kt | 82 +++++++----------- .../addquickpairs/AddQuickPairsScreen.kt | 84 +++++++++++++++++++ .../quickpairs/QuickPairsScreen.kt | 5 +- .../quickpairs/composables/QuickPairItem.kt | 6 +- 6 files changed, 126 insertions(+), 57 deletions(-) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 55c5dc007..49a3bc24b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,8 @@ composeBom = "2023.08.00" composeMaterial = "1.2.1" composeFoundation = "1.2.1" coreSplashscreen = "1.0.1" +material3Android = "1.3.1" +composeNavigation = "1.4.0" [libraries] androidx-compose-material-v140 = { module = "androidx.wear.compose:compose-material", version.ref = "composeMaterialVersion" } @@ -97,6 +99,8 @@ androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", versi androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } androidx-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "composeFoundation" } androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } +androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } +androidx-compose-navigation = { group = "androidx.wear.compose", name = "compose-navigation", version.ref = "composeNavigation" } [plugins] android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts index 5552ee37f..cecaf38f9 100644 --- a/watchapp/build.gradle.kts +++ b/watchapp/build.gradle.kts @@ -62,6 +62,8 @@ dependencies { implementation(libs.androidx.compose.foundation) implementation(libs.androidx.activity.compose) implementation(libs.androidx.core.splashscreen) + implementation(libs.androidx.material3.android) + implementation(libs.androidx.compose.navigation) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 81c882eb7..096c34b65 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -4,25 +4,14 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Scaffold -import androidx.wear.compose.material.Text -import androidx.wear.compose.material.TimeText import androidx.wear.compose.material.Vignette import androidx.wear.compose.material.VignettePosition -import dev.arkbuilders.rate.watchapp.R -import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.OptionsMenu +import androidx.wear.compose.navigation.SwipeDismissableNavHost +import androidx.wear.compose.navigation.composable +import androidx.wear.compose.navigation.rememberSwipeDismissableNavController +import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairsScreen +import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme class MainActivity : ComponentActivity() { @@ -34,46 +23,33 @@ class MainActivity : ComponentActivity() { setTheme(android.R.style.Theme_DeviceDefault) setContent { - Scaffold( - vignette = { - Vignette(vignettePosition = VignettePosition.TopAndBottom) + ArkrateTheme { + val navController = rememberSwipeDismissableNavController() + Scaffold( + vignette = { + Vignette(vignettePosition = VignettePosition.TopAndBottom) + } + ) { + AddQuickPairsScreen() + SwipeDismissableNavHost( + navController = navController, + startDestination = "list" + ) { + composable("list") { + QuickPairsScreen( + onNavigateToAdd = { + navController.navigate("addquickpairs") + } + ) + } + composable("addquickpairs") { + AddQuickPairsScreen( + ) + } + } } - ) { - OptionsMenu() -// QuickPairsScreen() } } } } - -@Composable -fun WearApp(greetingName: String) { - ArkrateTheme { - Box( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background), - contentAlignment = Alignment.Center - ) { - TimeText() - Greeting(greetingName = greetingName) - } - } -} - -@Composable -fun Greeting(greetingName: String) { - Text( - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - color = MaterialTheme.colors.primary, - text = stringResource(R.string.hello_world, greetingName) - ) -} - -@Preview(device = Devices.WEAR_OS_SMALL_ROUND, showSystemUi = true) -@Composable -fun DefaultPreview() { - WearApp("Preview Android") -} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt new file mode 100644 index 000000000..e5aec70da --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt @@ -0,0 +1,84 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MenuItemColors +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.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.material.Text + +@Composable +fun AddQuickPairsScreen(modifier: Modifier = Modifier) { + var expanded by remember { mutableStateOf(false) } + val scrollState = rememberScrollState() + + ScalingLazyColumn( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Text( + modifier = modifier.fillMaxWidth(), + text = "Add", + textAlign = TextAlign.Center + ) + } + + item { + Text( + modifier = modifier.fillMaxWidth(), + text = "From", + ) + } + + item { + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + scrollState = scrollState + ) { + repeat(8) { + DropdownMenuItem( + text = { Text("Item ${it + 1}") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) } + ) + } + } + } + item { + BasicTextField( + modifier = modifier.fillMaxWidth(), + value = "", + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + onValueChange = { text -> + }, + ) + } + + } +} + +@Composable +fun AddQuickPairsScreenPreview() { + AddQuickPairsScreen() +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index 9d4c59030..b03aa98d7 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -13,7 +13,8 @@ import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPa @Composable fun QuickPairsScreen( modifier: Modifier = Modifier, - viewModel: QuickPairsViewModel = QuickPairsViewModel() + viewModel: QuickPairsViewModel = QuickPairsViewModel(), + onNavigateToAdd: () -> Unit ) { val quickPairsList = viewModel.quickPairs.collectAsState().value @@ -25,7 +26,7 @@ fun QuickPairsScreen( contentPadding = PaddingValues(4.dp) ) { items(quickPairsList.size, key = null) { idx -> - QuickPairItem(quick = quickPairsList[idx]) + QuickPairItem(quick = quickPairsList[idx], onClick = onNavigateToAdd) } } } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt index e1c4162bd..45302f60b 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt @@ -41,12 +41,13 @@ import java.time.OffsetDateTime fun QuickPairItem( modifier: Modifier = Modifier, quick: QuickPair, + onClick: () -> Unit, ) { var isExpanded by remember { mutableStateOf(true) } Card( - onClick = { /*TODO*/ }, + onClick = onClick, modifier = modifier .padding(horizontal = 12.dp, vertical = 4.dp) ) { @@ -160,7 +161,8 @@ fun QuickPairItemPreview() { calculatedDate = OffsetDateTime.now(), pinnedDate = null, group = null - ) + ), + onClick = {} ) } From 03e816fa2109634637b26be72b52cb695397b451 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Tue, 7 Jan 2025 22:22:59 +0700 Subject: [PATCH 18/32] Tweak UI --- .../presentation/quickpairs/QuickPairsScreen.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index b03aa98d7..bacc6374d 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -2,11 +2,14 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable 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.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.material.Text import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairItem import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairsEmpty @@ -25,6 +28,13 @@ fun QuickPairsScreen( modifier = modifier.fillMaxSize(), contentPadding = PaddingValues(4.dp) ) { + item { + Text( + modifier = modifier.fillMaxWidth(), + text = "Options", + textAlign = TextAlign.Center + ) + } items(quickPairsList.size, key = null) { idx -> QuickPairItem(quick = quickPairsList[idx], onClick = onNavigateToAdd) } From 2531ec5cb8a604fd01f5671ab11452fc683d0a8f Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 29 Mar 2025 22:31:58 +0700 Subject: [PATCH 19/32] Create UI --- .../addquickpairs/AddQuickPairsScreen.kt | 56 ++++++++++++++----- .../quickpairs/QuickPairsScreen.kt | 2 +- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt index e5aec70da..0994de524 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt @@ -1,16 +1,25 @@ package dev.arkbuilders.rate.watchapp.presentation.addquickpairs import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Email +import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MenuDefaults import androidx.compose.material3.MenuItemColors import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -22,6 +31,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text @@ -51,21 +62,7 @@ fun AddQuickPairsScreen(modifier: Modifier = Modifier) { } item { - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - scrollState = scrollState - ) { - repeat(8) { - DropdownMenuItem( - text = { Text("Item ${it + 1}") }, - onClick = { /* TODO */ }, - leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) } - ) - } - } - } - item { + BasicTextField( modifier = modifier.fillMaxWidth(), value = "", @@ -75,10 +72,39 @@ fun AddQuickPairsScreen(modifier: Modifier = Modifier) { ) } + item { + Box( + modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart) + ) { + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "Localized description") + } + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + DropdownMenuItem( + text = { Text("Edit") }, + onClick = { /* Handle edit! */ }, + leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Settings") }, + onClick = { /* Handle settings! */ }, + leadingIcon = { Icon(Icons.Outlined.Settings, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Send Feedback") }, + onClick = { /* Handle send feedback! */ }, + leadingIcon = { Icon(Icons.Outlined.Email, contentDescription = null) }, + trailingIcon = { Text("F11", textAlign = TextAlign.Center) } + ) + } + } + } + } } @Composable +@Preview fun AddQuickPairsScreenPreview() { AddQuickPairsScreen() } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index bacc6374d..8002c451e 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -31,7 +31,7 @@ fun QuickPairsScreen( item { Text( modifier = modifier.fillMaxWidth(), - text = "Options", + text = "Quick", textAlign = TextAlign.Center ) } From 58290f381889a8c2ac1c8d89a217653d1a90dd03 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 09:58:36 +0700 Subject: [PATCH 20/32] Create options screen --- build.gradle.kts | 2 +- core/db/build.gradle.kts | 2 +- gradle/libs.versions.toml | 23 +++- settings.gradle | 0 settings.gradle.kts | 4 +- watchapp/build.gradle.kts | 12 +- .../watchapp/presentation/MainActivity.kt | 14 +- .../presentation/options/OptionsScreen.kt | 110 +++++++++++++++ .../presentation/options/WearOptionButton.kt | 129 ++++++++++++++++++ .../options/WearOptionsHomeScreen.kt | 124 +++++++++++++++++ .../presentation/options/WearPageIndicator.kt | 64 +++++++++ .../quickpairs/QuickPairsScreen.kt | 3 +- .../quickpairs/QuickPairsViewModel.kt | 13 +- .../quickpairs/composables/QuickPairItem.kt | 3 +- 14 files changed, 478 insertions(+), 25 deletions(-) delete mode 100644 settings.gradle create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/OptionsScreen.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionButton.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionsHomeScreen.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearPageIndicator.kt diff --git a/build.gradle.kts b/build.gradle.kts index 68c93594d..ca411f2f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.9.1" apply false + alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.compose.compiler) apply false diff --git a/core/db/build.gradle.kts b/core/db/build.gradle.kts index c54cf41b4..30ef0a54e 100644 --- a/core/db/build.gradle.kts +++ b/core/db/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { ksp(libs.androidx.room.compiler) testImplementation(libs.junit) - androidTestImplementation(libs.androidx.room.testing) +// androidTestImplementation(libs.androidx.room.testing) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ceed63c0..c63a9a2d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,13 @@ [versions] +composeNavigation = "1.3.0" kotlin = "2.1.20" agp = "8.1.4" +playServicesWearable = "18.2.0" coreKtx = "1.15.0" minSdk = "26" compileSdk = "35" ksp = "2.1.20-1.0.32" - +androidApp = "8.9.1" arkAbout = "0.2.0" activityCompose = "1.10.1" composeDestinationsVersion = "2.1.0" @@ -35,8 +37,15 @@ workRuntimeKtx = "2.8.1" appcompat = "1.7.0" material = "1.12.0" gson = "2.11.0" +reorderable = "2.4.3" +playReview = "2.0.2" +composeBom = "2023.08.00" +composeMaterial = "1.2.1" +composeFoundation = "1.2.1" +coreSplashscreen = "1.0.1" [libraries] +androidx-compose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "composeNavigation" } ark-about = { module = "dev.arkbuilders.components:about", version.ref = "arkAbout" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } @@ -55,7 +64,7 @@ androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "composeUi" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } -compose-destinations-animations = { module = "io.github.raamcosta.compose-destinations:animations-core", version.ref = "composeDestinationsVersion" } +compose-destinations-core = { module = "io.github.raamcosta.compose-destinations:core", version.ref = "composeDestinationsVersion" } compose-destinations-compiler = { module = "io.github.raamcosta.compose-destinations:ksp", version.ref = "composeDestinationsVersion" } arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrowFxCoroutines" } @@ -66,6 +75,9 @@ dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +play-review = { group = "com.google.android.play", name = "review", version.ref = "playReview" } +play-review-ktx = { group = "com.google.android.play", name = "review-ktx", version.ref = "playReview" } + firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx", version = "" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx", version = "" } @@ -84,8 +96,15 @@ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } reorderable = { group = "sh.calvin.reorderable", name = "reorderable", version.ref = "reorderable" } +play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" } +androidx-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "composeFoundation" } +androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } + [plugins] android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"} +android-application = { id = "com.android.application", version.ref = "androidApp"} diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index e69de29bb..000000000 diff --git a/settings.gradle.kts b/settings.gradle.kts index 5090e0e26..975c7bc98 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,9 +2,9 @@ import java.util.Properties pluginManagement { repositories { - gradlePluginPortal() google() mavenCentral() + gradlePluginPortal() maven { setUrl("https://jitpack.io") } @@ -54,6 +54,8 @@ include(":feature:quickwidget") include(":feature:search") include(":feature:settings") include(":feature:onboarding") +include(":watchapp") + fun getLocalProps(): Properties { val props = Properties() diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts index cecaf38f9..da43078b8 100644 --- a/watchapp/build.gradle.kts +++ b/watchapp/build.gradle.kts @@ -1,6 +1,7 @@ plugins { - id("com.android.application") version "8.1.3" + id("com.android.application") id("org.jetbrains.kotlin.android") + alias(libs.plugins.compose.compiler) } android { namespace = "dev.arkbuilders.rate.watchapp" @@ -52,18 +53,19 @@ dependencies { implementation(project(":feature:quick")) implementation(project(":core:domain")) implementation(project(":core:presentation")) - implementation(libs.androidx.compose.material.v140) implementation(libs.play.services.wearable) - implementation(platform(libs.androidx.compose.bom)) +// implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) + implementation (libs.androidx.compose.navigation )// Or the latest version + implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.foundation) implementation(libs.androidx.activity.compose) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.material3.android) - implementation(libs.androidx.compose.navigation) + implementation(libs.material3) + implementation(libs.navigation.compose) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 096c34b65..80c4a5d04 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -11,7 +11,7 @@ import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairsScreen -import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen +import dev.arkbuilders.rate.watchapp.presentation.options.OptionsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme class MainActivity : ComponentActivity() { @@ -30,17 +30,17 @@ class MainActivity : ComponentActivity() { Vignette(vignettePosition = VignettePosition.TopAndBottom) } ) { - AddQuickPairsScreen() SwipeDismissableNavHost( navController = navController, startDestination = "list" ) { composable("list") { - QuickPairsScreen( - onNavigateToAdd = { - navController.navigate("addquickpairs") - } - ) + OptionsScreen() +// QuickPairsScreen( +// onNavigateToAdd = { +//// navController.navigate("addquickpairs") +// } +// ) } composable("addquickpairs") { AddQuickPairsScreen( diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/OptionsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/OptionsScreen.kt new file mode 100644 index 000000000..e287e3d63 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/OptionsScreen.kt @@ -0,0 +1,110 @@ +package dev.arkbuilders.rate.watchapp.presentation.options + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Text +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun OptionsScreen( + modifier: Modifier = Modifier, + onUpdateClick: () -> Unit = {}, + onPinClick: () -> Unit = {}, + onEditClick: () -> Unit = {}, + onReuseClick: () -> Unit = {}, + onDeleteClick: () -> Unit = {} +) { + val listState = rememberScalingLazyListState() + + Scaffold( + positionIndicator = { + PositionIndicator(scalingLazyListState = listState) + } + ) { + ScalingLazyColumn( + modifier = modifier.fillMaxSize() + .background(ArkColor.BGSecondaryAlt), + state = listState, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + text = "Options", + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextPrimary + ) + } + + item { + WearOptionButton( + text = "Update", + icon = WearOptionButtonIcon.Refresh, + onClick = onUpdateClick + ) + } + + item { + WearOptionButton( + text = "Pin", + icon = WearOptionButtonIcon.Pin, + onClick = onPinClick + ) + } + + item { + WearOptionButton( + text = "Edit", + icon = WearOptionButtonIcon.Edit, + onClick = onEditClick + ) + } + + item { + WearOptionButton( + text = "Re-Use", + icon = WearOptionButtonIcon.Reuse, + onClick = onReuseClick + ) + } + + item { + WearOptionButton( + text = "Delete", + icon = WearOptionButtonIcon.Delete, + buttonType = WearOptionButtonType.Destructive, + onClick = onDeleteClick + ) + } + } + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun OptionsScreenPreview() { + OptionsScreen() +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionButton.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionButton.kt new file mode 100644 index 000000000..efd37852d --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionButton.kt @@ -0,0 +1,129 @@ +package dev.arkbuilders.rate.watchapp.presentation.options + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.Star +import androidx.compose.material.icons.outlined.Share +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.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +enum class WearOptionButtonType { + Default, + Destructive +} + +enum class WearOptionButtonIcon(val imageVector: ImageVector) { + Refresh(Icons.Outlined.Refresh), + Pin(Icons.Outlined.Star), + Edit(Icons.Outlined.Edit), + Reuse(Icons.Outlined.Share), + Delete(Icons.Outlined.Delete) +} + +@Composable +fun WearOptionButton( + text: String, + icon: WearOptionButtonIcon, + onClick: () -> Unit, + modifier: Modifier = Modifier, + buttonType: WearOptionButtonType = WearOptionButtonType.Default +) { + val colors = when (buttonType) { + WearOptionButtonType.Default -> ButtonDefaults.buttonColors( + backgroundColor = Color.White, + contentColor = ArkColor.TextSecondary + ) + + WearOptionButtonType.Destructive -> ButtonDefaults.buttonColors( + backgroundColor = Color.White, + contentColor = ArkColor.FGErrorPrimary + ) + } + + val borderStroke = when (buttonType) { + WearOptionButtonType.Default -> ButtonDefaults.buttonBorder( + borderStroke = androidx.compose.foundation.BorderStroke(1.dp, ArkColor.BorderSecondary) + ) + WearOptionButtonType.Destructive -> ButtonDefaults.buttonBorder( + borderStroke = androidx.compose.foundation.BorderStroke(1.dp, ArkColor.BorderError) + ) + } + + Button( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = colors, + border = borderStroke + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 6.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon.imageVector, + contentDescription = text, + modifier = Modifier.size(20.dp), + tint = when (buttonType) { + WearOptionButtonType.Default -> ArkColor.TextSecondary + WearOptionButtonType.Destructive -> ArkColor.FGErrorPrimary + } + ) + Text( + text = text, + modifier = Modifier.padding(start = 6.dp), + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + textAlign = TextAlign.Start, + color = when (buttonType) { + WearOptionButtonType.Default -> ArkColor.TextSecondary + WearOptionButtonType.Destructive -> ArkColor.FGErrorPrimary + } + ) + } + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearOptionButtonPreview() { + WearOptionButton( + text = "Update", + icon = WearOptionButtonIcon.Refresh, + onClick = {} + ) +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearOptionButtonDestructivePreview() { + WearOptionButton( + text = "Delete", + icon = WearOptionButtonIcon.Delete, + buttonType = WearOptionButtonType.Destructive, + onClick = {} + ) +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionsHomeScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionsHomeScreen.kt new file mode 100644 index 000000000..b433a0f02 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearOptionsHomeScreen.kt @@ -0,0 +1,124 @@ +package dev.arkbuilders.rate.watchapp.presentation.options + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun WearOptionsHomeScreen( + modifier: Modifier = Modifier, + currentPage: Int = 1, + totalPages: Int = 3, + onUpdateClick: () -> Unit = {}, + onPinClick: () -> Unit = {}, + onEditClick: () -> Unit = {}, + onReuseClick: () -> Unit = {}, + onDeleteClick: () -> Unit = {} +) { + val listState = rememberScalingLazyListState() + + Scaffold( + modifier = modifier + .fillMaxSize() + .clip(CircleShape), + positionIndicator = { + PositionIndicator(scalingLazyListState = listState) + } + ) { + ScalingLazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Title section + item { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + text = "Options", + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextPrimary + ) + } + + // Page indicator + item { + WearPageIndicator( + totalPages = totalPages, + currentPage = currentPage, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + + // Main slot - Option buttons + item { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + WearOptionButton( + text = "Update", + icon = WearOptionButtonIcon.Refresh, + onClick = onUpdateClick + ) + + WearOptionButton( + text = "Pin", + icon = WearOptionButtonIcon.Pin, + onClick = onPinClick + ) + + WearOptionButton( + text = "Edit", + icon = WearOptionButtonIcon.Edit, + onClick = onEditClick + ) + + WearOptionButton( + text = "Re-Use", + icon = WearOptionButtonIcon.Reuse, + onClick = onReuseClick + ) + + WearOptionButton( + text = "Delete", + icon = WearOptionButtonIcon.Delete, + buttonType = WearOptionButtonType.Destructive, + onClick = onDeleteClick + ) + } + } + } + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearOptionsHomeScreenPreview() { + WearOptionsHomeScreen() +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearPageIndicator.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearPageIndicator.kt new file mode 100644 index 000000000..e257c4446 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/options/WearPageIndicator.kt @@ -0,0 +1,64 @@ +package dev.arkbuilders.rate.watchapp.presentation.options + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun WearPageIndicator( + totalPages: Int, + currentPage: Int, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier.padding(6.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + repeat(totalPages) { index -> + PageIndicatorDot( + isSelected = index == currentPage, + modifier = Modifier.size(6.dp) + ) + } + } +} + +@Composable +private fun PageIndicatorDot( + isSelected: Boolean, + modifier: Modifier = Modifier +) { + val backgroundColor = if (isSelected) { + ArkColor.TextSecondary + } else { + ArkColor.TextSecondary.copy(alpha = 0.3f) + } + + Box( + modifier = modifier + .clip(CircleShape) + .background(backgroundColor) + ) +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearPageIndicatorPreview() { + WearPageIndicator( + totalPages = 3, + currentPage = 1 + ) +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index 8002c451e..20e9b4b50 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -8,6 +8,7 @@ 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.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairItem @@ -19,7 +20,7 @@ fun QuickPairsScreen( viewModel: QuickPairsViewModel = QuickPairsViewModel(), onNavigateToAdd: () -> Unit ) { - val quickPairsList = viewModel.quickPairs.collectAsState().value + val quickPairsList = viewModel.quickPairs.collectAsStateWithLifecycle().value if (quickPairsList.isEmpty()) { QuickPairsEmpty(modifier = modifier.fillMaxSize()) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt index 2dd8f0152..00a91c3a7 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt @@ -2,6 +2,7 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs import androidx.lifecycle.ViewModel import dev.arkbuilders.rate.core.domain.model.Amount +import dev.arkbuilders.rate.core.domain.model.Group import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -25,7 +26,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), QuickPair( id = 1, @@ -37,7 +38,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), QuickPair( @@ -50,7 +51,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), QuickPair( @@ -63,7 +64,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), QuickPair( id = 1, @@ -75,7 +76,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), QuickPair( @@ -88,7 +89,7 @@ class QuickPairsViewModel : ViewModel() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ) ) _quickPairs.value = a diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt index 45302f60b..84e6a8d5a 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt @@ -31,6 +31,7 @@ import androidx.wear.compose.material.Text import dev.arkbuilders.rate.core.domain.CurrUtils import dev.arkbuilders.rate.core.domain.model.Amount import dev.arkbuilders.rate.core.domain.model.CurrencyCode +import dev.arkbuilders.rate.core.domain.model.Group import dev.arkbuilders.rate.core.presentation.theme.ArkColor import dev.arkbuilders.rate.core.presentation.utils.IconUtils import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair @@ -160,7 +161,7 @@ fun QuickPairItemPreview() { ), calculatedDate = OffsetDateTime.now(), pinnedDate = null, - group = null + group = Group.empty() ), onClick = {} ) From eacf30961539ee6c1abd6febd65db5d4195bf1c8 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 14:06:11 +0700 Subject: [PATCH 21/32] Add new pair screens --- .../watchapp/presentation/MainActivity.kt | 13 +- .../addquickpairs/AddQuickPairsScreen.kt | 38 ++-- .../composables/CurrencyInputField.kt | 200 ++++++++++++++++++ .../addquickpairs/composables/SwapButton.kt | 84 ++++++++ 4 files changed, 317 insertions(+), 18 deletions(-) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/CurrencyInputField.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/SwapButton.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 80c4a5d04..fcbbef159 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -12,6 +12,7 @@ import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.options.OptionsScreen +import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme class MainActivity : ComponentActivity() { @@ -35,12 +36,12 @@ class MainActivity : ComponentActivity() { startDestination = "list" ) { composable("list") { - OptionsScreen() -// QuickPairsScreen( -// onNavigateToAdd = { -//// navController.navigate("addquickpairs") -// } -// ) +// OptionsScreen() + QuickPairsScreen( + onNavigateToAdd = { + navController.navigate("addquickpairs") + } + ) } composable("addquickpairs") { AddQuickPairsScreen( diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt index 0994de524..cb30dd7ef 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt @@ -1,5 +1,6 @@ package dev.arkbuilders.rate.watchapp.presentation.addquickpairs +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -35,6 +36,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor +import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables.CurrencyInputField @Composable fun AddQuickPairsScreen(modifier: Modifier = Modifier) { @@ -42,7 +45,7 @@ fun AddQuickPairsScreen(modifier: Modifier = Modifier) { val scrollState = rememberScrollState() ScalingLazyColumn( - modifier = modifier.fillMaxSize(), + modifier = modifier.fillMaxSize().background(ArkColor.BGSecondaryAlt), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { @@ -50,25 +53,36 @@ fun AddQuickPairsScreen(modifier: Modifier = Modifier) { Text( modifier = modifier.fillMaxWidth(), text = "Add", - textAlign = TextAlign.Center + textAlign = TextAlign.Center, + color = ArkColor.TextPrimary ) } item { - Text( - modifier = modifier.fillMaxWidth(), - text = "From", + CurrencyInputField( + label = "To", + currencyCode = "EUR", + value = "0.92", + onValueChange = { }, + onCurrencyClick = { + + }, + showLabel = true, + showDeleteButton = false, + onDeleteClick = {} ) } item { - - BasicTextField( - modifier = modifier.fillMaxWidth(), - value = "", - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - onValueChange = { text -> - }, + CurrencyInputField( + label = "To", + currencyCode = "EUR", + value = "0.92", + onValueChange = { }, + onCurrencyClick = {}, + showLabel = false, + showDeleteButton = false, + onDeleteClick = {} ) } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/CurrencyInputField.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/CurrencyInputField.kt new file mode 100644 index 000000000..7dc6eb924 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/CurrencyInputField.kt @@ -0,0 +1,200 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.KeyboardArrowDown +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.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun CurrencyInputField( + label: String, + currencyCode: String, + value: String, + onValueChange: (String) -> Unit, + onCurrencyClick: () -> Unit, + modifier: Modifier = Modifier, + showDeleteButton: Boolean = false, + onDeleteClick: () -> Unit = {}, + showLabel: Boolean = true, + hintText: String = "This is a hint text to help user." +) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.Bottom + ) { + // Main input field + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + if (showLabel) { + Text( + text = label, + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + color = ArkColor.TextSecondary + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .background( + color = Color.White, + shape = RoundedCornerShape(8.dp) + ) + .border( + width = 1.dp, + color = ArkColor.BorderSecondary, + shape = RoundedCornerShape(8.dp) + ) + ) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + // Currency dropdown + Box( + modifier = Modifier + .width(80.dp) + .clickable { onCurrencyClick() } + .padding(horizontal = 14.dp, vertical = 10.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = currencyCode, + fontSize = 14.sp, + color = ArkColor.TextSecondary + ) + Icon( + imageVector = Icons.Outlined.KeyboardArrowDown, + contentDescription = "Select currency", + modifier = Modifier.size(16.dp), + tint = ArkColor.FGQuinary + ) + } + } + + // Value input + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = Modifier + .weight(1f) + .padding(horizontal = 12.dp, vertical = 10.dp), + textStyle = TextStyle( + fontSize = 14.sp, + color = ArkColor.TextPrimary, + textAlign = TextAlign.Start + ), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true + ) + } + } + + if (showLabel) { + Text( + text = hintText, + fontSize = 14.sp, + color = ArkColor.TextTertiary + ) + } + } + + // Delete button + if (showDeleteButton) { + Button( + onClick = onDeleteClick, + modifier = Modifier.size(44.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.White, + contentColor = ArkColor.FGErrorPrimary + ), + border = ButtonDefaults.buttonBorder( + borderStroke = BorderStroke(1.dp, ArkColor.BorderError) + ), + shape = RoundedCornerShape(8.dp) + ) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = "Delete", + modifier = Modifier.size(20.dp), + tint = ArkColor.FGErrorPrimary + ) + } + } + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun CurrencyInputFieldPreview() { + var value by remember { mutableStateOf("1") } + + CurrencyInputField( + label = "From", + currencyCode = "USD", + value = value, + onValueChange = { value = it }, + onCurrencyClick = {}, + showDeleteButton = true, + onDeleteClick = {} + ) +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun CurrencyInputFieldNoLabelPreview() { + var value by remember { mutableStateOf("0.92") } + + CurrencyInputField( + label = "To", + currencyCode = "EUR", + value = value, + onValueChange = { value = it }, + onCurrencyClick = {}, + showLabel = false, + showDeleteButton = true, + onDeleteClick = {} + ) +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/SwapButton.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/SwapButton.kt new file mode 100644 index 000000000..e74406a7f --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/SwapButton.kt @@ -0,0 +1,84 @@ +package dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AccountBox +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.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.Icon +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun SwapButton( + onSwapClick: () -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Left divider + Box( + modifier = Modifier + .weight(1f) + .height(1.dp) + .background(ArkColor.BorderSecondary) + ) + + // Swap button + Button( + onClick = onSwapClick, + modifier = Modifier.size(40.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.White, + contentColor = ArkColor.TextPrimary + ), + border = ButtonDefaults.buttonBorder( + borderStroke = BorderStroke(1.dp, ArkColor.BorderSecondary) + ), + shape = CircleShape + ) { + Icon( + imageVector = Icons.Outlined.AccountBox, + contentDescription = "Swap currencies", + modifier = Modifier.size(20.dp), + tint = ArkColor.TextPrimary + ) + } + + // Right divider + Box( + modifier = Modifier + .weight(1f) + .height(1.dp) + .background(ArkColor.BorderSecondary) + ) + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun SwapButtonPreview() { + SwapButton( + onSwapClick = {} + ) +} From dc5aaaa29ca6b1f3a15b2537e600e5516ca03aaa Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 14:10:14 +0700 Subject: [PATCH 22/32] Add composables --- .../quickpairs/QuickPairsScreen.kt | 12 ++ .../theme/WearComponentExamples.kt | 165 ++++++++++++++++ .../presentation/theme/WearComponents.kt | 178 ++++++++++++++++++ .../watchapp/presentation/theme/WearDialog.kt | 162 ++++++++++++++++ 4 files changed, 517 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponentExamples.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponents.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearDialog.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index 20e9b4b50..f641614bd 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -3,6 +3,8 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier @@ -13,6 +15,8 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairItem import dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables.QuickPairsEmpty +import dev.arkbuilders.rate.watchapp.presentation.theme.WearButton +import dev.arkbuilders.rate.watchapp.presentation.theme.WearButtonStyle @Composable fun QuickPairsScreen( @@ -36,6 +40,14 @@ fun QuickPairsScreen( textAlign = TextAlign.Center ) } + item { + WearButton( + text = "Add", + onClick = onNavigateToAdd, + style = WearButtonStyle.Primary, + leadingIcon = Icons.Outlined.Add + ) + } items(quickPairsList.size, key = null) { idx -> QuickPairItem(quick = quickPairsList[idx], onClick = onNavigateToAdd) } diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponentExamples.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponentExamples.kt new file mode 100644 index 000000000..4bd0ebd71 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponentExamples.kt @@ -0,0 +1,165 @@ +package dev.arkbuilders.rate.watchapp.presentation.theme + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Refresh +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.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.Scaffold +import dev.arkbuilders.rate.watchapp.presentation.options.WearOptionsHomeScreen +import dev.arkbuilders.rate.watchapp.presentation.options.WearPageIndicator + +/** + * Examples of how to use the WearOS components created for the ARK Rate app. + * These components follow the Figma design system and WearOS best practices. + */ + +@Composable +fun WearButtonExamples(modifier: Modifier = Modifier) { + val listState = rememberScalingLazyListState() + + Scaffold( + positionIndicator = { + PositionIndicator(scalingLazyListState = listState) + } + ) { + ScalingLazyColumn( + modifier = modifier.fillMaxSize(), + state = listState, + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + WearButton( + text = "Primary Button", + onClick = {}, + style = WearButtonStyle.Primary, + leadingIcon = Icons.Outlined.Add + ) + } + + item { + WearButton( + text = "Secondary Button", + onClick = {}, + style = WearButtonStyle.Secondary, + leadingIcon = Icons.Outlined.Edit + ) + } + + item { + WearButton( + text = "Outlined Button", + onClick = {}, + style = WearButtonStyle.Outlined, + leadingIcon = Icons.Outlined.Refresh + ) + } + + item { + WearButton( + text = "Destructive Button", + onClick = {}, + style = WearButtonStyle.Destructive + ) + } + + item { + WearPageIndicator( + totalPages = 5, + currentPage = 2 + ) + } + } + } +} + +@Composable +fun WearDialogExamples(modifier: Modifier = Modifier) { + var showConfirmDialog by remember { mutableStateOf(false) } + var showInfoDialog by remember { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + WearButton( + text = "Show Confirmation Dialog", + onClick = { showConfirmDialog = true } + ) + + WearButton( + text = "Show Info Dialog", + onClick = { showInfoDialog = true } + ) + + if (showConfirmDialog) { + WearConfirmationDialog( + title = "Delete Item", + message = "Are you sure you want to delete this item?", + onConfirm = { + showConfirmDialog = false + // Handle confirmation + }, + onDismiss = { showConfirmDialog = false }, + isDestructive = true + ) + } + + if (showInfoDialog) { + WearInfoDialog( + title = "Success", + message = "Operation completed successfully!", + onDismiss = { showInfoDialog = false } + ) + } + } +} + +// Preview showing the main Options screen from Figma +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearOptionsHomeScreenExample() { + WearOptionsHomeScreen( + currentPage = 1, + totalPages = 3, + onUpdateClick = { /* Handle update */ }, + onPinClick = { /* Handle pin */ }, + onEditClick = { /* Handle edit */ }, + onReuseClick = { /* Handle reuse */ }, + onDeleteClick = { /* Handle delete */ } + ) +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearButtonExamplesPreview() { + WearButtonExamples() +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearDialogExamplesPreview() { + WearDialogExamples() +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponents.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponents.kt new file mode 100644 index 000000000..dcceb57d5 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearComponents.kt @@ -0,0 +1,178 @@ +package dev.arkbuilders.rate.watchapp.presentation.theme + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.OutlinedButton +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +enum class WearButtonStyle { + Primary, + Secondary, + Outlined, + Destructive +} + +@Composable +fun WearButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: WearButtonStyle = WearButtonStyle.Primary, + leadingIcon: ImageVector? = null, + enabled: Boolean = true +) { + when (style) { + WearButtonStyle.Primary -> { + Button( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = ArkColor.Primary, + contentColor = Color.White + ), + enabled = enabled + ) { + ButtonContent(text = text, leadingIcon = leadingIcon) + } + } + + WearButtonStyle.Secondary -> { + Button( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = ArkColor.BGSecondaryAlt, + contentColor = ArkColor.TextSecondary + ), + enabled = enabled + ) { + ButtonContent(text = text, leadingIcon = leadingIcon) + } + } + + WearButtonStyle.Outlined -> { + OutlinedButton( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = ArkColor.TextSecondary + ), + border = ButtonDefaults.buttonBorder( + borderStroke = BorderStroke(1.dp, ArkColor.BorderSecondary) + ), + enabled = enabled + ) { + ButtonContent(text = text, leadingIcon = leadingIcon) + } + } + + WearButtonStyle.Destructive -> { + Button( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = ArkColor.UtilityError500, + contentColor = Color.White + ), + enabled = enabled + ) { + ButtonContent(text = text, leadingIcon = leadingIcon) + } + } + } +} + +@Composable +private fun ButtonContent( + text: String, + leadingIcon: ImageVector?, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = if (leadingIcon != null) Arrangement.Start else Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + leadingIcon?.let { icon -> + Icon( + imageVector = icon, + contentDescription = text, + modifier = Modifier.size(18.dp) + ) + } + Text( + text = text, + modifier = Modifier.padding( + start = if (leadingIcon != null) 8.dp else 0.dp + ), + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + textAlign = if (leadingIcon != null) TextAlign.Start else TextAlign.Center + ) + } +} + +@Composable +fun WearCompactButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: WearButtonStyle = WearButtonStyle.Outlined +) { + val colors = when (style) { + WearButtonStyle.Primary -> ButtonDefaults.buttonColors( + backgroundColor = ArkColor.Primary, + contentColor = Color.White + ) + + WearButtonStyle.Destructive -> ButtonDefaults.buttonColors( + backgroundColor = ArkColor.UtilityError500, + contentColor = Color.White + ) + + else -> ButtonDefaults.buttonColors( + backgroundColor = Color.White, + contentColor = ArkColor.TextSecondary + ) + } + + val border = if (style == WearButtonStyle.Outlined) { + ButtonDefaults.buttonBorder( + borderStroke = BorderStroke(1.dp, ArkColor.BorderSecondary) + ) + } else { + ButtonDefaults.buttonBorder() + } + + Button( + onClick = onClick, + modifier = modifier, + colors = colors, + border = border, + shape = RoundedCornerShape(20.dp) + ) { + Text( + text = text, + fontWeight = FontWeight.Medium, + fontSize = 14.sp + ) + } +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearDialog.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearDialog.kt new file mode 100644 index 000000000..58714fd9d --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/theme/WearDialog.kt @@ -0,0 +1,162 @@ +package dev.arkbuilders.rate.watchapp.presentation.theme + +import androidx.compose.foundation.background +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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.Close +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.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.wear.compose.material.Text +import dev.arkbuilders.rate.core.presentation.theme.ArkColor + +@Composable +fun WearConfirmationDialog( + title: String, + message: String, + onConfirm: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + confirmText: String = "Yes", + dismissText: String = "No", + confirmIcon: ImageVector = Icons.Outlined.Check, + dismissIcon: ImageVector = Icons.Outlined.Close, + isDestructive: Boolean = false +) { + Dialog(onDismissRequest = onDismiss) { + Column( + modifier = modifier + .fillMaxWidth() + .background( + color = Color.White, + shape = RoundedCornerShape(16.dp) + ) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextPrimary + ) + + Text( + text = message, + fontSize = 14.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextSecondary + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + WearCompactButton( + text = dismissText, + onClick = onDismiss, + modifier = Modifier.weight(1f), + style = WearButtonStyle.Outlined + ) + + WearCompactButton( + text = confirmText, + onClick = onConfirm, + modifier = Modifier.weight(1f), + style = if (isDestructive) WearButtonStyle.Destructive else WearButtonStyle.Primary + ) + } + } + } +} + +@Composable +fun WearInfoDialog( + title: String, + message: String, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + dismissText: String = "OK" +) { + Dialog(onDismissRequest = onDismiss) { + Column( + modifier = modifier + .fillMaxWidth() + .background( + color = Color.White, + shape = RoundedCornerShape(16.dp) + ) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = title, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextPrimary + ) + + Text( + text = message, + fontSize = 14.sp, + textAlign = TextAlign.Center, + color = ArkColor.TextSecondary + ) + + Spacer(modifier = Modifier.height(8.dp)) + + WearCompactButton( + text = dismissText, + onClick = onDismiss, + modifier = Modifier.fillMaxWidth(), + style = WearButtonStyle.Primary + ) + } + } +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearConfirmationDialogPreview() { + WearConfirmationDialog( + title = "Delete Item", + message = "Are you sure you want to delete this item? This action cannot be undone.", + onConfirm = {}, + onDismiss = {}, + isDestructive = true + ) +} + +@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true) +@Composable +fun WearInfoDialogPreview() { + WearInfoDialog( + title = "Success", + message = "Your changes have been saved successfully.", + onDismiss = {} + ) +} From 29bddf601bc812c7bd079c4f04eac6d045ac7527 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:01:51 +0700 Subject: [PATCH 23/32] Refactor so that class be free from di code --- .../core/data/network/OkHttpClientBuilder.kt | 2 -- .../rate/core/data/repo/TimestampRepoImpl.kt | 2 -- .../repo/currency/CryptoCurrencyDataSource.kt | 2 -- .../repo/currency/CurrencyInfoDataSource.kt | 2 -- .../data/repo/currency/CurrencyRepoImpl.kt | 2 -- .../repo/currency/FallbackRatesProvider.kt | 2 -- .../repo/currency/FiatCurrencyDataSource.kt | 2 -- .../rate/core/di/modules/RepoModule.kt | 30 +++++++++++++++++++ 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/network/OkHttpClientBuilder.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/network/OkHttpClientBuilder.kt index 54e654e89..419ef1fad 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/network/OkHttpClientBuilder.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/network/OkHttpClientBuilder.kt @@ -4,9 +4,7 @@ import android.content.Context import android.webkit.WebSettings import okhttp3.OkHttpClient import javax.inject.Inject -import javax.inject.Singleton -@Singleton class OkHttpClientBuilder @Inject constructor(val context: Context) { fun build(): OkHttpClient { val agent = WebSettings.getDefaultUserAgent(context) diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/TimestampRepoImpl.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/TimestampRepoImpl.kt index 943137507..7eb7b15bc 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/TimestampRepoImpl.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/TimestampRepoImpl.kt @@ -8,9 +8,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import java.time.OffsetDateTime import javax.inject.Inject -import javax.inject.Singleton -@Singleton class TimestampRepoImpl @Inject constructor(private val dao: TimestampDao) : TimestampRepo { override suspend fun rememberTimestamp(type: TimestampType) = dao.insert( diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CryptoCurrencyDataSource.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CryptoCurrencyDataSource.kt index ceb9856a9..ba0179db5 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CryptoCurrencyDataSource.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CryptoCurrencyDataSource.kt @@ -8,9 +8,7 @@ import dev.arkbuilders.rate.core.data.network.api.CryptoAPI import dev.arkbuilders.rate.core.domain.model.CurrencyRate import dev.arkbuilders.rate.core.domain.model.CurrencyType import javax.inject.Inject -import javax.inject.Singleton -@Singleton class CryptoCurrencyDataSource @Inject constructor( private val cryptoAPI: CryptoAPI, private val cryptoRateResponseMapper: CryptoRateResponseMapper, diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyInfoDataSource.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyInfoDataSource.kt index ca8ef3948..9c995d064 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyInfoDataSource.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyInfoDataSource.kt @@ -11,9 +11,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext import javax.inject.Inject -import javax.inject.Singleton -@Singleton class CurrencyInfoDataSource @Inject constructor( private val ctx: Context, ) { diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyRepoImpl.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyRepoImpl.kt index 2f79c7e89..f25fafdee 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyRepoImpl.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/CurrencyRepoImpl.kt @@ -20,9 +20,7 @@ import kotlinx.coroutines.withContext import java.time.Duration import java.time.OffsetDateTime import javax.inject.Inject -import javax.inject.Singleton -@Singleton class CurrencyRepoImpl @Inject constructor( private val fiatDataSource: FiatCurrencyDataSource, private val cryptoDataSource: CryptoCurrencyDataSource, diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FallbackRatesProvider.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FallbackRatesProvider.kt index d3638c0fc..5e3a84fdc 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FallbackRatesProvider.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FallbackRatesProvider.kt @@ -12,9 +12,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.time.OffsetDateTime import javax.inject.Inject -import javax.inject.Singleton -@Singleton class FallbackRatesProvider @Inject constructor( private val ctx: Context, private val fiatRateResponseMapper: FiatRateResponseMapper, diff --git a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FiatCurrencyDataSource.kt b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FiatCurrencyDataSource.kt index 742e1f464..132874c71 100644 --- a/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FiatCurrencyDataSource.kt +++ b/core/data/src/main/java/dev/arkbuilders/rate/core/data/repo/currency/FiatCurrencyDataSource.kt @@ -8,9 +8,7 @@ import dev.arkbuilders.rate.core.data.network.api.FiatAPI import dev.arkbuilders.rate.core.domain.model.CurrencyRate import dev.arkbuilders.rate.core.domain.model.CurrencyType import javax.inject.Inject -import javax.inject.Singleton -@Singleton class FiatCurrencyDataSource @Inject constructor( private val fiatAPI: FiatAPI, private val fiatRateResponseMapper: FiatRateResponseMapper, diff --git a/core/di/src/main/java/dev/arkbuilders/rate/core/di/modules/RepoModule.kt b/core/di/src/main/java/dev/arkbuilders/rate/core/di/modules/RepoModule.kt index c14a324db..75f7277f6 100644 --- a/core/di/src/main/java/dev/arkbuilders/rate/core/di/modules/RepoModule.kt +++ b/core/di/src/main/java/dev/arkbuilders/rate/core/di/modules/RepoModule.kt @@ -3,6 +3,8 @@ package dev.arkbuilders.rate.core.di.modules import android.content.Context import dagger.Module import dagger.Provides +import dev.arkbuilders.rate.core.data.mapper.CryptoRateResponseMapper +import dev.arkbuilders.rate.core.data.mapper.FiatRateResponseMapper import dev.arkbuilders.rate.core.data.network.NetworkStatusImpl import dev.arkbuilders.rate.core.data.preferences.PrefsImpl import dev.arkbuilders.rate.core.data.repo.AnalyticsManagerImpl @@ -35,6 +37,34 @@ import javax.inject.Singleton @Module class RepoModule { + @Singleton + @Provides + fun provideFCryptoRateResponseMapper(): CryptoRateResponseMapper { + return CryptoRateResponseMapper() + } + + @Singleton + @Provides + fun provideFiatRateResponseMapper(): FiatRateResponseMapper { + return FiatRateResponseMapper() + } + + @Singleton + @Provides + fun provideFallbackRatesProvider( + context: Context, + fiatRateResponseMapper: FiatRateResponseMapper, + cryptoRateResponseMapper: CryptoRateResponseMapper, + buildConfigFieldsProvider: BuildConfigFieldsProvider, + ): FallbackRatesProvider { + return FallbackRatesProvider( + context, + fiatRateResponseMapper, + cryptoRateResponseMapper, + buildConfigFieldsProvider, + ) + } + @Singleton @Provides fun currencyRepo( From c7020b193bf4d2e1a40be1ef6aac32709b658f7f Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:02:13 +0700 Subject: [PATCH 24/32] Setup hilt for di --- build.gradle.kts | 1 + gradle/libs.versions.toml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index ca411f2f5..507df665f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,7 @@ plugins { id("com.google.gms.google-services") version "4.4.2" apply false id("com.google.firebase.crashlytics") version "3.0.1" apply false id("org.jlleitschuh.gradle.ktlint") version "12.1.1" + id("com.google.dagger.hilt.android") version "2.56.2" apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c63a9a2d4..4b3c6b5cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] composeNavigation = "1.3.0" +hilt = "2.56.2" kotlin = "2.1.20" agp = "8.1.4" playServicesWearable = "18.2.0" @@ -75,6 +76,8 @@ dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } +hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } play-review = { group = "com.google.android.play", name = "review", version.ref = "playReview" } play-review-ktx = { group = "com.google.android.play", name = "review-ktx", version.ref = "playReview" } From 9fbff91371832f13dc19218d0dad5f670bf7e002 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:02:55 +0700 Subject: [PATCH 25/32] Set up di and db --- watchapp/build.gradle.kts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/watchapp/build.gradle.kts b/watchapp/build.gradle.kts index da43078b8..d1d11ffa4 100644 --- a/watchapp/build.gradle.kts +++ b/watchapp/build.gradle.kts @@ -2,6 +2,8 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") alias(libs.plugins.compose.compiler) + alias(libs.plugins.ksp) + id("com.google.dagger.hilt.android") } android { namespace = "dev.arkbuilders.rate.watchapp" @@ -48,12 +50,26 @@ android { } dependencies { + implementation(project(":core:db")) + implementation(project(":core:data")) + implementation(project(":cryptoicons")) implementation(project(":fiaticons")) implementation(project(":feature:quick")) implementation(project(":core:domain")) implementation(project(":core:presentation")) + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") + + + implementation(libs.retrofit) + implementation(libs.converter.gson) + implementation(libs.logging.interceptor) + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) + implementation(libs.hilt.android) + ksp(libs.hilt.android.compiler) implementation(libs.play.services.wearable) // implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) From 5f6cde75cc2c649c15645612f55a4ac07eff3393 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:02:59 +0700 Subject: [PATCH 26/32] Set up di and db --- watchapp/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/watchapp/src/main/AndroidManifest.xml b/watchapp/src/main/AndroidManifest.xml index 8547e121e..eff222470 100644 --- a/watchapp/src/main/AndroidManifest.xml +++ b/watchapp/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ Date: Sat, 6 Sep 2025 21:03:08 +0700 Subject: [PATCH 27/32] Clone di graph --- .../arkbuilders/rate/watchapp/di/ApiModule.kt | 67 +++++++ .../arkbuilders/rate/watchapp/di/DBModule.kt | 40 +++++ .../rate/watchapp/di/RepoModule.kt | 165 ++++++++++++++++++ .../rate/watchapp/di/UseCaseModule.kt | 66 +++++++ 4 files changed, 338 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/ApiModule.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/DBModule.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/RepoModule.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/UseCaseModule.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/ApiModule.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/ApiModule.kt new file mode 100644 index 000000000..dba841a91 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/ApiModule.kt @@ -0,0 +1,67 @@ +package dev.arkbuilders.rate.watchapp.di + +import android.content.Context +import android.webkit.WebSettings +import com.google.gson.GsonBuilder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dev.arkbuilders.rate.core.data.network.OkHttpClientBuilder +import dev.arkbuilders.rate.core.data.network.api.CryptoAPI +import dev.arkbuilders.rate.core.data.network.api.FiatAPI +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class ApiModule { + + @Singleton + @Provides + fun clientBuilder(@ApplicationContext context: Context): OkHttpClient { + val client = + OkHttpClient.Builder() + .addNetworkInterceptor { chain -> + chain.proceed( + chain.request() + .newBuilder() + .build(), + ) + } + .build() + + return client + } + + @Singleton + @Provides + fun cryptoAPI(clientBuilder: OkHttpClient): CryptoAPI { + val httpClient = clientBuilder + val gson = GsonBuilder().create() + + return Retrofit.Builder() + .baseUrl("https://raw.githubusercontent.com") + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(httpClient) + .build() + .create(CryptoAPI::class.java) + } + + @Singleton + @Provides + fun fiatAPI(clientBuilder: OkHttpClient): FiatAPI { + val httpClient = clientBuilder + val gson = GsonBuilder().create() + + return Retrofit.Builder() + .baseUrl("https://raw.githubusercontent.com") + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(httpClient) + .build() + .create(FiatAPI::class.java) + } +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/DBModule.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/DBModule.kt new file mode 100644 index 000000000..57d1743aa --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/DBModule.kt @@ -0,0 +1,40 @@ +package dev.arkbuilders.rate.watchapp.di + +import android.app.Application +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dev.arkbuilders.rate.core.db.Database +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class DBModule { + @Singleton + @Provides + fun database(app: Application): Database { + return Database.build(app) + } + + @Provides + fun assetsDao(db: Database) = db.assetsDao() + + @Provides + fun quickDao(db: Database) = db.quickDao() + + @Provides + fun rateDao(db: Database) = db.rateDao() + + @Provides + fun pairAlertDao(db: Database) = db.pairAlertDao() + + @Provides + fun fetchTimestampDao(db: Database) = db.fetchTimestampDao() + + @Provides + fun codeUseStatDao(db: Database) = db.codeUseStatDao() + + @Provides + fun groupDao(db: Database) = db.groupDao() +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/RepoModule.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/RepoModule.kt new file mode 100644 index 000000000..d906ea87f --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/RepoModule.kt @@ -0,0 +1,165 @@ +package dev.arkbuilders.rate.watchapp.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dev.arkbuilders.rate.core.data.mapper.CryptoRateResponseMapper +import dev.arkbuilders.rate.core.data.mapper.FiatRateResponseMapper +import dev.arkbuilders.rate.core.data.network.NetworkStatusImpl +import dev.arkbuilders.rate.core.data.network.OkHttpClientBuilder +import dev.arkbuilders.rate.core.data.network.api.CryptoAPI +import dev.arkbuilders.rate.core.data.preferences.PrefsImpl +import dev.arkbuilders.rate.core.data.repo.AnalyticsManagerImpl +import dev.arkbuilders.rate.core.data.repo.BuildConfigFieldsProviderImpl +import dev.arkbuilders.rate.core.data.repo.CodeUseStatRepoImpl +import dev.arkbuilders.rate.core.data.repo.GooglePlayInAppReviewManagerImpl +import dev.arkbuilders.rate.core.data.repo.GroupRepoImpl +import dev.arkbuilders.rate.core.data.repo.TimestampRepoImpl +import dev.arkbuilders.rate.core.data.repo.currency.CryptoCurrencyDataSource +import dev.arkbuilders.rate.core.data.repo.currency.CurrencyInfoDataSource +import dev.arkbuilders.rate.core.data.repo.currency.CurrencyRepoImpl +import dev.arkbuilders.rate.core.data.repo.currency.FallbackRatesProvider +import dev.arkbuilders.rate.core.data.repo.currency.FiatCurrencyDataSource +import dev.arkbuilders.rate.core.data.repo.currency.LocalCurrencyDataSource +import dev.arkbuilders.rate.core.db.dao.CodeUseStatDao +import dev.arkbuilders.rate.core.db.dao.CurrencyRateDao +import dev.arkbuilders.rate.core.db.dao.GroupDao +import dev.arkbuilders.rate.core.db.dao.TimestampDao +import dev.arkbuilders.rate.core.domain.BuildConfigFieldsProvider +import dev.arkbuilders.rate.core.domain.repo.AnalyticsManager +import dev.arkbuilders.rate.core.domain.repo.CodeUseStatRepo +import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo +import dev.arkbuilders.rate.core.domain.repo.GroupRepo +import dev.arkbuilders.rate.core.domain.repo.InAppReviewManager +import dev.arkbuilders.rate.core.domain.repo.NetworkStatus +import dev.arkbuilders.rate.core.domain.repo.Prefs +import dev.arkbuilders.rate.core.domain.repo.TimestampRepo +import dev.arkbuilders.rate.core.domain.usecase.DefaultGroupNameProvider +import dev.arkbuilders.rate.core.presentation.utils.DefaultGroupNameProviderImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class RepoModule { + + @Singleton + @Provides + fun buildConfigFieldsProvider(): BuildConfigFieldsProvider = BuildConfigFieldsProviderImpl() + + @Singleton + @Provides + fun provideFCryptoRateResponseMapper(): CryptoRateResponseMapper{ + return CryptoRateResponseMapper() + } + + @Singleton + @Provides + fun provideFiatRateResponseMapper(): FiatRateResponseMapper{ + return FiatRateResponseMapper() + } + + @Singleton + @Provides + fun provideFallbackRatesProvider( + @ApplicationContext context: Context, + fiatRateResponseMapper: FiatRateResponseMapper, + cryptoRateResponseMapper: CryptoRateResponseMapper, + buildConfigFieldsProvider: BuildConfigFieldsProvider, + ): FallbackRatesProvider { + return FallbackRatesProvider( + context, + fiatRateResponseMapper, + cryptoRateResponseMapper, + buildConfigFieldsProvider, + ) + } + + @Singleton + @Provides + fun provideCurrencyInfoDataSource( + @ApplicationContext context: Context, + ): CurrencyInfoDataSource { + return CurrencyInfoDataSource(context) + } + + @Singleton + @Provides + fun provideCryptoCurrencyDataSource( + cryptoAPI: CryptoAPI, + cryptoRateResponseMapper: CryptoRateResponseMapper, + ): CryptoCurrencyDataSource { + return CryptoCurrencyDataSource(cryptoAPI, cryptoRateResponseMapper) + } + + @Singleton + @Provides + fun provideLocalCurrencyDataSource(dao: CurrencyRateDao):LocalCurrencyDataSource { + return LocalCurrencyDataSource(dao) + } + + @Singleton + @Provides + fun currencyRepo( + fiatCurrencyDataSource: FiatCurrencyDataSource, + cryptoCurrencyDataSource: CryptoCurrencyDataSource, + localCurrencyDataSource: LocalCurrencyDataSource, + currencyInfoDataSource: CurrencyInfoDataSource, + timestampRepo: TimestampRepo, + networkStatus: NetworkStatus, + fallbackRatesProvider: FallbackRatesProvider, + ): CurrencyRepo = + CurrencyRepoImpl( + fiatCurrencyDataSource, + cryptoCurrencyDataSource, + localCurrencyDataSource, + fallbackRatesProvider, + currencyInfoDataSource, + timestampRepo, + networkStatus, + ) + + @Singleton + @Provides + fun groupRepo(groupDao: GroupDao): GroupRepo = GroupRepoImpl(groupDao) + + @Singleton + @Provides + fun prefs(@ApplicationContext context: Context): Prefs = PrefsImpl(context) + + @Singleton + @Provides + fun codeUseStatRepo(codeUseStatDao: CodeUseStatDao): CodeUseStatRepo = + CodeUseStatRepoImpl(codeUseStatDao) + + @Singleton + @Provides + fun analyticsManager(prefs: Prefs): AnalyticsManager = AnalyticsManagerImpl(prefs) + + @Singleton + @Provides + fun timestampRepo(timestampDao: TimestampDao): TimestampRepo = TimestampRepoImpl(timestampDao) + + @Singleton + @Provides + fun networkStatus(@ApplicationContext context: Context): NetworkStatus = + NetworkStatusImpl(context) + + @Singleton + @Provides + fun defaultGroupNameProvider(@ApplicationContext context: Context): DefaultGroupNameProvider = + DefaultGroupNameProviderImpl(context) + + @Singleton + @Provides + fun inAppReviewManager( + analyticsManager: AnalyticsManager, + buildConfigFieldsProvider: BuildConfigFieldsProvider, + ): InAppReviewManager = + GooglePlayInAppReviewManagerImpl( + analyticsManager, + buildConfigFieldsProvider.provide(), + ) +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/UseCaseModule.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/UseCaseModule.kt new file mode 100644 index 000000000..cec60593c --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/di/UseCaseModule.kt @@ -0,0 +1,66 @@ +package dev.arkbuilders.rate.watchapp.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dev.arkbuilders.rate.core.domain.BuildConfigFieldsProvider +import dev.arkbuilders.rate.core.domain.repo.CodeUseStatRepo +import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo +import dev.arkbuilders.rate.core.domain.repo.GroupRepo +import dev.arkbuilders.rate.core.domain.repo.InAppReviewManager +import dev.arkbuilders.rate.core.domain.repo.Prefs +import dev.arkbuilders.rate.core.domain.usecase.CalcFrequentCurrUseCase +import dev.arkbuilders.rate.core.domain.usecase.ConvertWithRateUseCase +import dev.arkbuilders.rate.core.domain.usecase.DefaultGroupNameProvider +import dev.arkbuilders.rate.core.domain.usecase.GetGroupByIdOrCreateDefaultUseCase +import dev.arkbuilders.rate.core.domain.usecase.GroupReorderSwapUseCase +import dev.arkbuilders.rate.core.domain.usecase.SearchUseCase +import dev.arkbuilders.rate.core.domain.usecase.ValidateGroupNameUseCase +import dev.arkbuilders.rate.feature.quick.domain.usecase.LaunchInAppReviewUseCase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class UseCaseModule { + @Singleton + @Provides + fun calcFrequentCurrUseCase(codeUseStatRepo: CodeUseStatRepo) = + CalcFrequentCurrUseCase(codeUseStatRepo) + + @Singleton + @Provides + fun convertWithRateUseCase(currencyRepo: CurrencyRepo) = ConvertWithRateUseCase(currencyRepo) + + @Singleton + @Provides + fun prepopulateDefaultGroupUseCase( + groupRepo: GroupRepo, + defaultGroupNameProvider: DefaultGroupNameProvider, + ) = GetGroupByIdOrCreateDefaultUseCase(groupRepo, defaultGroupNameProvider) + + @Singleton + @Provides + fun groupReorderSwapUseCase(groupRepo: GroupRepo) = GroupReorderSwapUseCase(groupRepo) + + @Singleton + @Provides + fun validateGroupNameUseCase() = ValidateGroupNameUseCase() + + @Singleton + @Provides + fun searchUseCase(buildConfigFieldsProvider: BuildConfigFieldsProvider) = + SearchUseCase(buildConfigFieldsProvider.provide()) + + + @Singleton + @Provides + fun provideLaunchInAppReviewUseCase( + + inAppReviewManager: InAppReviewManager, + prefs: Prefs, + buildConfigFieldsProvider: BuildConfigFieldsProvider + + ) = + LaunchInAppReviewUseCase(inAppReviewManager, prefs, buildConfigFieldsProvider) +} From e8255bd583e08bce1f9c0ec1802f5fce4d8fb92d Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:03:30 +0700 Subject: [PATCH 28/32] Set up hilt --- .../rate/watchapp/presentation/RateWatchApplication.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/RateWatchApplication.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/RateWatchApplication.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/RateWatchApplication.kt new file mode 100644 index 000000000..b3da758e1 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/RateWatchApplication.kt @@ -0,0 +1,9 @@ +package dev.arkbuilders.rate.watchapp.presentation + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class RateWatchApplication: Application() { + +} From d595fb6ab633ce5ae25fe8efeb879d6ade92538e Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:03:41 +0700 Subject: [PATCH 29/32] Init data --- .../watchapp/presentation/MainActivity.kt | 4 +++- .../presentation/main/MainViewModel.kt | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index fcbbef159..12d35348c 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -10,12 +10,14 @@ import androidx.wear.compose.material.VignettePosition import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController +import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.rate.watchapp.presentation.addquickpairs.AddQuickPairsScreen -import dev.arkbuilders.rate.watchapp.presentation.options.OptionsScreen import dev.arkbuilders.rate.watchapp.presentation.quickpairs.QuickPairsScreen import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme +@AndroidEntryPoint class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt new file mode 100644 index 000000000..0b1c34e81 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt @@ -0,0 +1,23 @@ +package dev.arkbuilders.rate.watchapp.presentation.main + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo +import jakarta.inject.Inject +import kotlinx.coroutines.launch + +@HiltViewModel +class MainViewModel @Inject constructor( + private val currencyRepo: CurrencyRepo, + ): ViewModel() { + + init { + viewModelScope.launch { + currencyRepo.initialize() + launch { + currencyRepo.getCurrencyRates() + } + } + } +} From 80b9a52ec572fe4bcd5177007c9805fe427c70a6 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:03:57 +0700 Subject: [PATCH 30/32] Set up compose viewmodel --- .../watchapp/presentation/quickpairs/QuickPairsScreen.kt | 4 ++-- .../presentation/quickpairs/QuickPairsViewModel.kt | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt index f641614bd..67029153e 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt @@ -6,10 +6,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable -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.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.material.Text @@ -21,7 +21,7 @@ import dev.arkbuilders.rate.watchapp.presentation.theme.WearButtonStyle @Composable fun QuickPairsScreen( modifier: Modifier = Modifier, - viewModel: QuickPairsViewModel = QuickPairsViewModel(), + viewModel: QuickPairsViewModel = hiltViewModel(), onNavigateToAdd: () -> Unit ) { val quickPairsList = viewModel.quickPairs.collectAsStateWithLifecycle().value diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt index 00a91c3a7..c7e4cc1ca 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt @@ -1,15 +1,22 @@ package dev.arkbuilders.rate.watchapp.presentation.quickpairs import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel import dev.arkbuilders.rate.core.domain.model.Amount import dev.arkbuilders.rate.core.domain.model.Group +import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo import dev.arkbuilders.rate.feature.quick.domain.model.QuickPair +import jakarta.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import java.math.BigDecimal import java.time.OffsetDateTime -class QuickPairsViewModel : ViewModel() { +@HiltViewModel +class QuickPairsViewModel @Inject constructor( + private val currencyRepo: CurrencyRepo, +) : ViewModel() { + private val _quickPairs: MutableStateFlow> = MutableStateFlow(listOf()) val quickPairs: StateFlow> = _quickPairs From 778d74e0f71c30f3c3e13cdb04da8f0e380291f2 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:21:43 +0700 Subject: [PATCH 31/32] Implement search screen --- .../presentation/search/SearchScreen.kt | 31 +++++++++++++++++++ .../presentation/search/SearchViewModel.kt | 17 ++++++++++ 2 files changed, 48 insertions(+) create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchScreen.kt create mode 100644 watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchViewModel.kt diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchScreen.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchScreen.kt new file mode 100644 index 000000000..442246777 --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchScreen.kt @@ -0,0 +1,31 @@ +package dev.arkbuilders.rate.watchapp.presentation.search + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import dev.arkbuilders.rate.core.presentation.theme.ArkColor +import dev.arkbuilders.rate.core.presentation.ui.SearchTextField + +@Composable +fun SearchScreen( + modifier: Modifier = Modifier, + viewModel: SearchViewModel = hiltViewModel(), +) { + ScalingLazyColumn( + modifier = modifier.fillMaxSize().background(ArkColor.BGSecondaryAlt), + contentPadding = PaddingValues(4.dp) + ) { + item { + SearchTextField(modifier = Modifier + .fillMaxWidth() + .padding(16.dp)) + } + } +} diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchViewModel.kt new file mode 100644 index 000000000..67b7de3bc --- /dev/null +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/search/SearchViewModel.kt @@ -0,0 +1,17 @@ +package dev.arkbuilders.rate.watchapp.presentation.search + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import dev.arkbuilders.rate.core.domain.usecase.SearchUseCase +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( +// private val searchUseCase: SearchUseCase, +): ViewModel() { + + init { + + } + +} From 57be820e9409cb7abab47dafb301794131fc09f2 Mon Sep 17 00:00:00 2001 From: Hieu Vu Date: Sat, 6 Sep 2025 21:21:56 +0700 Subject: [PATCH 32/32] Fix wrong annotation --- .../dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt | 1 + .../rate/watchapp/presentation/main/MainViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt index 12d35348c..35b6253c9 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt @@ -39,6 +39,7 @@ class MainActivity : ComponentActivity() { ) { composable("list") { // OptionsScreen() +// SearchScreen() QuickPairsScreen( onNavigateToAdd = { navController.navigate("addquickpairs") diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt index 0b1c34e81..744915d44 100644 --- a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt +++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/main/MainViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo -import jakarta.inject.Inject import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor(