diff --git a/build.gradle.kts b/build.gradle.kts
index 68c93594d..507df665f 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
@@ -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/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/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/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(
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 bd94ec9d6..2e7a19c23 100644
--- a/core/presentation/src/main/res/values/strings.xml
+++ b/core/presentation/src/main/res/values/strings.xml
@@ -144,6 +144,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/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 36b017002..4b3c6b5cf 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,11 +1,14 @@
[versions]
+composeNavigation = "1.3.0"
+hilt = "2.56.2"
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"
@@ -37,8 +40,13 @@ 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" }
@@ -49,7 +57,6 @@ androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtim
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
-androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "roomRuntime" }
androidx-ui = { module = "androidx.compose.ui:ui", version.ref = "composeUi" }
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeUi" }
@@ -69,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" }
@@ -90,8 +99,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.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/.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..d1d11ffa4
--- /dev/null
+++ b/watchapp/build.gradle.kts
@@ -0,0 +1,89 @@
+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"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "dev.arkbuilders.rate.watchapp"
+ minSdk = libs.versions.minSdk.get().toInt()
+ 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(":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)
+ 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.material3)
+ implementation(libs.navigation.compose)
+ 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..eff222470
--- /dev/null
+++ b/watchapp/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
+}
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..35b6253c9
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/MainActivity.kt
@@ -0,0 +1,59 @@
+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.wear.compose.material.Scaffold
+import androidx.wear.compose.material.Vignette
+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.quickpairs.QuickPairsScreen
+import dev.arkbuilders.rate.watchapp.presentation.theme.ArkrateTheme
+
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
+
+ super.onCreate(savedInstanceState)
+
+ setTheme(android.R.style.Theme_DeviceDefault)
+
+ setContent {
+ ArkrateTheme {
+ val navController = rememberSwipeDismissableNavController()
+ Scaffold(
+ vignette = {
+ Vignette(vignettePosition = VignettePosition.TopAndBottom)
+ }
+ ) {
+ SwipeDismissableNavHost(
+ navController = navController,
+ startDestination = "list"
+ ) {
+ composable("list") {
+// OptionsScreen()
+// SearchScreen()
+ QuickPairsScreen(
+ onNavigateToAdd = {
+ navController.navigate("addquickpairs")
+ }
+ )
+ }
+ composable("addquickpairs") {
+ AddQuickPairsScreen(
+ )
+ }
+ }
+ }
+ }
+
+ }
+ }
+}
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() {
+
+}
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..cb30dd7ef
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/AddQuickPairsScreen.kt
@@ -0,0 +1,124 @@
+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
+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
+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.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) {
+ var expanded by remember { mutableStateOf(false) }
+ val scrollState = rememberScrollState()
+
+ ScalingLazyColumn(
+ modifier = modifier.fillMaxSize().background(ArkColor.BGSecondaryAlt),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ item {
+ Text(
+ modifier = modifier.fillMaxWidth(),
+ text = "Add",
+ textAlign = TextAlign.Center,
+ color = ArkColor.TextPrimary
+ )
+ }
+
+ item {
+ CurrencyInputField(
+ label = "To",
+ currencyCode = "EUR",
+ value = "0.92",
+ onValueChange = { },
+ onCurrencyClick = {
+
+ },
+ showLabel = true,
+ showDeleteButton = false,
+ onDeleteClick = {}
+ )
+ }
+
+ item {
+ CurrencyInputField(
+ label = "To",
+ currencyCode = "EUR",
+ value = "0.92",
+ onValueChange = { },
+ onCurrencyClick = {},
+ showLabel = false,
+ showDeleteButton = false,
+ onDeleteClick = {}
+ )
+ }
+
+ 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/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() {
+
+}
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/OptionItem.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt
new file mode 100644
index 000000000..93997adac
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionItem.kt
@@ -0,0 +1,82 @@
+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.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.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,
+ isDeleteButton: Boolean = false,
+) {
+ OutlinedButton(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ onClick = onClick,
+ shape = RoundedCornerShape(20),
+ 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),
+ 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 = {},
+ )
+}
diff --git a/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt
new file mode 100644
index 000000000..4c20abdd9
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/addquickpairs/composables/OptionsMenu.kt
@@ -0,0 +1,88 @@
+package dev.arkbuilders.rate.watchapp.presentation.addquickpairs.composables
+
+import androidx.compose.foundation.layout.Arrangement
+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.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.material.Text
+import dev.arkbuilders.rate.core.presentation.CoreRDrawable
+import dev.arkbuilders.rate.core.presentation.CoreRString
+
+@Composable
+fun OptionsMenu(modifier: Modifier = Modifier) {
+ ScalingLazyColumn(
+ 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(),
+ 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
+ )
+ }
+ }
+}
+
+@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true)
+@Composable
+fun AddQuickPairsPreview() {
+ OptionsMenu()
+}
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 = {}
+ )
+}
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..744915d44
--- /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 kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class MainViewModel @Inject constructor(
+ private val currencyRepo: CurrencyRepo,
+ ): ViewModel() {
+
+ init {
+ viewModelScope.launch {
+ currencyRepo.initialize()
+ launch {
+ currencyRepo.getCurrencyRates()
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000..67029153e
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsScreen.kt
@@ -0,0 +1,56 @@
+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.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
+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(
+ modifier: Modifier = Modifier,
+ viewModel: QuickPairsViewModel = hiltViewModel(),
+ onNavigateToAdd: () -> Unit
+) {
+ val quickPairsList = viewModel.quickPairs.collectAsStateWithLifecycle().value
+
+ if (quickPairsList.isEmpty()) {
+ QuickPairsEmpty(modifier = modifier.fillMaxSize())
+ } else {
+ ScalingLazyColumn(
+ modifier = modifier.fillMaxSize(),
+ contentPadding = PaddingValues(4.dp)
+ ) {
+ item {
+ Text(
+ modifier = modifier.fillMaxWidth(),
+ text = "Quick",
+ 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/quickpairs/QuickPairsViewModel.kt b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt
new file mode 100644
index 000000000..c7e4cc1ca
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/QuickPairsViewModel.kt
@@ -0,0 +1,105 @@
+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
+
+@HiltViewModel
+class QuickPairsViewModel @Inject constructor(
+ private val currencyRepo: CurrencyRepo,
+) : 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 = Group.empty()
+ ),
+ 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 = Group.empty()
+ ),
+
+ 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 = Group.empty()
+ ),
+
+ 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 = Group.empty()
+ ),
+ 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 = Group.empty()
+ ),
+
+ 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 = 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
new file mode 100644
index 000000000..84e6a8d5a
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairItem.kt
@@ -0,0 +1,169 @@
+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.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
+import java.math.BigDecimal
+import java.time.OffsetDateTime
+
+@Composable
+fun QuickPairItem(
+ modifier: Modifier = Modifier,
+ quick: QuickPair,
+ onClick: () -> Unit,
+) {
+ var isExpanded by remember {
+ mutableStateOf(true)
+ }
+ Card(
+ onClick = onClick,
+ 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 = Group.empty()
+ ),
+ onClick = {}
+ )
+}
+
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..941b829e3
--- /dev/null
+++ b/watchapp/src/main/java/dev/arkbuilders/rate/watchapp/presentation/quickpairs/composables/QuickPairsEmpty.kt
@@ -0,0 +1,58 @@
+package dev.arkbuilders.rate.watchapp.presentation.quickpairs.composables
+
+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
+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
+
+@Composable
+fun QuickPairsEmpty(
+ modifier: Modifier = Modifier
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ painter = painterResource(id = CoreRDrawable.ic_empty_quick),
+ contentDescription = "",
+ tint = Color.Unspecified,
+ )
+ Text(
+ modifier = modifier
+ .padding(horizontal = 8.dp)
+ .fillMaxWidth(),
+ text = "Empty Here, But Full of Possibilities!",
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 16.sp,
+ textAlign = TextAlign.Center,
+ )
+ Text(
+ modifier = modifier.fillMaxWidth()
+ .padding(horizontal = 8.dp),
+ text = "Calculate currency from Rate App",
+ fontSize = 12.sp,
+ textAlign = TextAlign.Center,
+ )
+
+ }
+}
+
+@Preview(device = Devices.WEAR_OS_LARGE_ROUND, showSystemUi = true)
+@Composable
+fun QuickPairEmptyPreview() {
+ QuickPairsEmpty()
+}
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 {
+
+ }
+
+}
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/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 = {}
+ )
+}
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 000000000..c209e78ec
Binary files /dev/null and b/watchapp/src/main/res/mipmap-hdpi/ic_launcher.webp differ
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 000000000..4f0f1d64e
Binary files /dev/null and b/watchapp/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/watchapp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/watchapp/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/watchapp/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
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 000000000..28d4b77f9
Binary files /dev/null and b/watchapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/watchapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/watchapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/watchapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/watchapp/src/main/res/values-round/strings.xml b/watchapp/src/main/res/values-round/strings.xml
new file mode 100644
index 000000000..42f12297f
--- /dev/null
+++ b/watchapp/src/main/res/values-round/strings.xml
@@ -0,0 +1,3 @@
+
+ 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 @@
+
+
+
+