diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..6a250899 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,105 @@ +name: Krail App CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + android: + runs-on: ubuntu-latest + environment: Firebase + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + - name: Setup environment variables + run: | + echo "NSW_TRANSPORT_API_KEY=${{ secrets.NSW_TRANSPORT_API_KEY }}" >> $GITHUB_ENV + + - name: set up JDK + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + + - name: Cache Gradle and wrapper + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Make Gradle executable + run: chmod +x ./gradlew + + - name: Firebase (Release) - Google Services.json file + env: + DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_RELEASE }} + run: echo $DATA | base64 -di > composeApp/src/androidMain/google-services.json + + - name: Firebase (Debug) - Google Services.json file + env: + DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} + run: echo $DATA | base64 -di > composeApp/src/androidDebug/google-services.json + +# - name: Detekt Checks +# run: ./gradlew detekt + + - name: Build Debug + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} + run: ./gradlew :composeApp:assembleDebug test + + - name: Build Release + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} + run: ./gradlew :composeApp:assembleRelease test + + iOS: + runs-on: macos-14 + environment: Firebase + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + - name: Setup environment variables + run: | + echo "NSW_TRANSPORT_API_KEY=${{ secrets.NSW_TRANSPORT_API_KEY }}" >> $GITHUB_ENV + + - name: Checkout code + uses: actions/checkout@v4 + + - name: set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - run: brew install swiftlint + + - uses: gradle/actions/setup-gradle@v4 + with: + cache-disabled: true + + - name: Build iOS App - Debug (Without Code Signing) + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} + run: | + xcodebuild -project iosApp/iosApp.xcodeproj \ + -scheme iosApp \ + -configuration Debug \ + OBJROOT=$GITHUB_WORKSPACE/build/ios \ + SYMROOT=$GITHUB_WORKSPACE/build/ios \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9e8f882d..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Android CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - environment: Firebase - steps: - - uses: actions/checkout@v4 - name: Setup environment variables - env: - NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} - run: | - echo "::set-env name=NSW_TRANSPORT_API_KEY::${{ secrets.NSW_TRANSPORT_API_KEY }}" - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: 21 - - - name: Cache Gradle and wrapper - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Make Gradle executable - run: chmod +x ./gradlew - - - name: Firebase (Release) - Google Services.json file - env: - DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_RELEASE }} - run: echo $DATA | base64 -di > app/google-services.json - - - name: Firebase (Debug) - Google Services.json file - env: - DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} - run: echo $DATA | base64 -di > app/src/debug/google-services.json - - - name: Detekt Checks - run: ./gradlew detekt - - - name: Build Debug - run: ./gradlew assembleDebug test - - - name: Build Release - run: ./gradlew assembleRelease test diff --git a/.github/workflows/firebase-app-distribution.yml b/.github/workflows/firebase-app-distribution.yml deleted file mode 100644 index 9ffcf9af..00000000 --- a/.github/workflows/firebase-app-distribution.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Firebase App Distribution - -on: - pull_request: - branches: - - debug-alpha - -jobs: - distribute: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - name: Setup environment variables - env: - NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} - run: | - echo "::set-env name=NSW_TRANSPORT_API_KEY::${{ secrets.NSW_TRANSPORT_API_KEY }}" - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: 21 - - - name: Cache Gradle and wrapper - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Firebase (Debug) - Google Services.json file - env: - DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} - run: echo $DATA | base64 -di > app/src/debug/google-services.json - - - name: Make Gradle executable - run: chmod +x ./gradlew - - - name: Build APK - run: ./gradlew assembleDebug --no-daemon - - - name: Upload to Firebase App Distribution - env: - FIREBASE_TOKEN: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} - run: | - firebase appdistribution:distribute app/build/outputs/apk/debug/app-debug.apk \ - --app ${{ secrets.FIREBASE_APP_ID_DEBUG }} \ - --groups "Friends" \ - --release-notes "Automated release" diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..a0891f56 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.4 diff --git a/.xcode-version b/.xcode-version new file mode 100644 index 00000000..dddffdec --- /dev/null +++ b/.xcode-version @@ -0,0 +1 @@ +15.3 diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index 9c81af2b..00000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.application) - alias(libs.plugins.krail.android.hilt) - alias(libs.plugins.kotlin.serialization) -} - -android { - namespace = "xyz.ksharma.krail" - - defaultConfig { - applicationId = "xyz.ksharma.krail" - versionCode = 12 - versionName = "1.0-alpha03" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - - debug { - applicationIdSuffix = ".debug" - isDebuggable = true - ndk { - isDebuggable = true - debugSymbolLevel = "FULL" - } - } - - release { - isMinifyEnabled = true - isDebuggable = false - isShrinkResources = true - ndk { - isDebuggable = false - debugSymbolLevel = "FULL" - } - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - firebaseCrashlytics { - nativeSymbolUploadEnabled = true - } - } - } - - hilt { - enableAggregatingTask = true - } -} - -dependencies { - - // Projects - implementation(projects.core.designSystem) - implementation(projects.core.network) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real) - - implementation(libs.activity.compose) - implementation(libs.compose.foundation) - implementation(libs.compose.navigation) - implementation(libs.core.ktx) - implementation(libs.kotlinx.serialization.json) - implementation(libs.lifecycle.runtime.ktx) - implementation(libs.timber) - implementation(libs.hilt.navigation.compose) - - // Firebase - implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.analytics) - implementation(libs.firebase.crashlytics) - implementation(libs.firebase.perf) - - // Test - androidTestImplementation(libs.test.androidxTestExtJunit) - testImplementation(libs.test.composeUiTestJunit4) - testImplementation(libs.test.paparazzi) -} diff --git a/app/src/.DS_Store b/app/src/.DS_Store deleted file mode 100644 index ede8a9ca..00000000 Binary files a/app/src/.DS_Store and /dev/null differ diff --git a/app/src/debug/.gitkeep b/app/src/debug/.gitkeep deleted file mode 100644 index f2a53523..00000000 --- a/app/src/debug/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -keep for git - diff --git a/app/src/debug/res/drawable/ic_launcher_foreground.xml b/app/src/debug/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 9ea9b179..00000000 --- a/app/src/debug/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/kotlin/xyz/ksharma/krail/KrailApp.kt b/app/src/main/kotlin/xyz/ksharma/krail/KrailApp.kt deleted file mode 100644 index 059819d5..00000000 --- a/app/src/main/kotlin/xyz/ksharma/krail/KrailApp.kt +++ /dev/null @@ -1,9 +0,0 @@ -package xyz.ksharma.krail - -import androidx.compose.runtime.Composable -import xyz.ksharma.krail.navigation.KrailNavHost - -@Composable -internal fun KrailApp() { - KrailNavHost() -} diff --git a/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt deleted file mode 100644 index 814848fa..00000000 --- a/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.ksharma.krail - -import android.app.Application -import dagger.hilt.android.HiltAndroidApp -import timber.log.Timber - -@HiltAndroidApp -class KrailApplication : Application() { - - override fun onCreate() { - super.onCreate() - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - } - } -} diff --git a/app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt deleted file mode 100644 index 12ff4c52..00000000 --- a/app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package xyz.ksharma.krail - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import dagger.hilt.android.AndroidEntryPoint -import xyz.ksharma.krail.design.system.theme.KrailTheme - -@AndroidEntryPoint -class MainActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - KrailTheme { - KrailApp() - } - } - } -} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 532196b3..00000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml deleted file mode 100644 index 5c71db05..00000000 --- a/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt b/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt deleted file mode 100644 index d7eaf556..00000000 --- a/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.ksharma.krail - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts deleted file mode 100644 index 0b55affa..00000000 --- a/build-logic/convention/build.gradle.kts +++ /dev/null @@ -1,51 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - `kotlin-dsl` -} - -group = "xyz.ksharma.buildlogic" - -val javaVersion = libs.versions.java.get().toInt() - -java { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] -} - -tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } -} - -dependencies { - compileOnly(libs.kotlin.gradlePlugin) - compileOnly(libs.android.gradlePlugin) -} - -gradlePlugin { - plugins { - register("application") { - id = "krail.android.application" - implementationClass = "ApplicationConventionPlugin" - } - register("androidHilt") { - id = "krail.android.hilt" - implementationClass = "AndroidHiltConventionPlugin" - } - register("androidLibrary") { - id = "krail.android.library" - implementationClass = "AndroidLibraryConventionPlugin" - } - register("androidLibraryCompose") { - id = "krail.android.library.compose" - implementationClass = "AndroidLibraryComposeConventionPlugin" - } - register("jvmLibrary") { - id = "krail.jvm.library" - implementationClass = "JvmLibraryConventionPlugin" - } - } -} diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt deleted file mode 100644 index 460f4982..00000000 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt +++ /dev/null @@ -1,23 +0,0 @@ -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType - -class AndroidHiltConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - - with(pluginManager) { - apply("com.google.dagger.hilt.android") - apply("com.google.devtools.ksp") - } - - dependencies { - "implementation"(libs.findLibrary("hilt.android").get()) - "ksp"(libs.findLibrary("hilt.compiler").get()) - } - } - } -} diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt deleted file mode 100644 index 38fecea1..00000000 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt +++ /dev/null @@ -1,62 +0,0 @@ -import com.android.build.gradle.LibraryExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class AndroidLibraryComposeConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - - with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") - } - - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - } - - buildTypes { - release { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - compose = true - buildConfig = true - } - } - } - } -} diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt deleted file mode 100644 index 61072bb4..00000000 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt +++ /dev/null @@ -1,65 +0,0 @@ -import com.android.build.gradle.LibraryExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class AndroidLibraryConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - - with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - } - - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - } - - buildTypes { - release { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - buildConfig = true - } - } - - dependencies { - "implementation"(libs.findLibrary("timber").get()) - } - } - } -} diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt deleted file mode 100644 index 91c488af..00000000 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt +++ /dev/null @@ -1,81 +0,0 @@ -import com.android.build.api.dsl.ApplicationExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class ApplicationConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val targetSdkVersion = libs.findVersion("targetSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - val kotlinVersion = libs.findVersion("kotlin").get().toString() - - with(pluginManager) { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") - apply("com.google.gms.google-services") - apply("com.google.firebase.crashlytics") - apply("com.google.firebase.firebase-perf") - } - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - targetSdk = targetSdkVersion - vectorDrawables { - useSupportLibrary = true - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - compose = true - buildConfig = true - } - - composeOptions { - // Kotlin and Compose compiler version is same after K2 is released. - kotlinCompilerExtensionVersion = kotlinVersion - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - dependencies { - "implementation"(libs.findLibrary("timber").get()) - } - } - } - } -} diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt deleted file mode 100644 index 863735c9..00000000 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt +++ /dev/null @@ -1,44 +0,0 @@ -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.provideDelegate -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -class JvmLibraryConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - - with(pluginManager) { - apply("org.jetbrains.kotlin.jvm") - } - - extensions.configure { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - - compilerOptions { - val warningsAsErrors: String? by project - allWarningsAsErrors.set(warningsAsErrors.toBoolean()) - - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - } - } -} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts deleted file mode 100644 index 62257853..00000000 --- a/build-logic/settings.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -dependencyResolutionManagement { - repositories { - google() - mavenCentral() - } - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} - -rootProject.name = "build-logic" -include(":convention") \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2b049a2e..f6ab05be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,92 +1,13 @@ -import io.gitlab.arturbosch.detekt.Detekt // Top-level build file where you can add configuration options common to all sub-projects/modules. @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.kotlinAndroid) apply false - alias(libs.plugins.hilt) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.serialization) apply false - alias(libs.plugins.detekt) alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.google.services) apply false - alias(libs.plugins.firebase.crashlyticsPlugin) apply false - alias(libs.plugins.firebase.performancePlugin) apply false -} - -subprojects { - plugins.withId("app.cash.paparazzi") { - // Defer until afterEvaluate so that testImplementation is created by Android plugin. - afterEvaluate { - dependencies.constraints { - add("testImplementation", "com.google.guava:guava") { - attributes { - attribute( - TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, - objects.named( - TargetJvmEnvironment::class.java, - TargetJvmEnvironment.STANDARD_JVM - ) - ) - } - because( - "LayoutLib and sdk-common depend on Guava's -jre published variant." + - "See https://github.com/cashapp/paparazzi/issues/906." - ) - } - } - } - } -} -true // Needed to make the Suppress annotation work for the plugins block - -val excludeFilesDetekt = listOf( - "**/.gradle/**", - "**/.idea/**", - "**/build/**", - ".github/**", - "gradle/**", -) - -tasks { - withType { - config.setFrom(files("$rootDir/config/detekt/detekt.yaml")) - baseline.set(file("$rootDir/config/detekt/detekt-baseline.xml")) - - autoCorrect = true - buildUponDefaultConfig = true - parallel = true - debug = true - - source = files(subprojects.flatMap { subproject -> - listOf( - File(subproject.projectDir, "/src/main/kotlin"), - File(subproject.projectDir, "/src/main/java"), - File(subproject.projectDir, "/src/debug/kotlin"), - File(subproject.projectDir, "/src/debug/java"), - ) - }).asFileTree - - excludes.addAll(excludeFilesDetekt) - - reports { - html.required = true - html.outputLocation.set(file("build/reports/detekt.html")) - txt.required = true - md.required = true - xml.required = false - sarif.required = false - } - } - project.detekt { - basePath = rootProject.projectDir.absolutePath - toolVersion = libs.versions.detekt.get() - autoCorrect = true - } -} - -dependencies { - detektPlugins(libs.detektFormat) - detektPlugins(libs.detektCompose) + alias(libs.plugins.composeMultiplatform) apply false + alias(libs.plugins.kotlinMultiplatform) apply false } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 00000000..eed11ebd --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,113 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +android { + namespace = "xyz.ksharma.krail" + + defaultConfig { + applicationId = "xyz.ksharma.krail" + versionCode = 12 + versionName = "1.0-alpha03" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + + debug { + applicationIdSuffix = ".debug" + isDebuggable = true + ndk { + isDebuggable = true + debugSymbolLevel = "FULL" + } + } + + release { + isMinifyEnabled = true + isDebuggable = false + isShrinkResources = true + ndk { + isDebuggable = false + debugSymbolLevel = "FULL" + } + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } +} + +plugins { + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.krail.android.application) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) +} + +kotlin { + applyDefaultHierarchyTemplate() + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } + + listOf( + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "KrailApp" + isStatic = true + } + } + + sourceSets { + androidMain { + dependencies { + implementation(compose.preview) + implementation(libs.activity.compose) + implementation(compose.foundation) + implementation(libs.core.ktx) + implementation(libs.kotlinx.serialization.json) + implementation(libs.lifecycle.runtime.ktx) + api(libs.di.koinAndroid) + } + } + + commonMain.dependencies { + implementation(projects.taj) + implementation(projects.sandook) + implementation(projects.feature.tripPlanner.network) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.feature.tripPlanner.state) + + implementation(libs.navigation.compose) + + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.runtime.compose) + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + + api(libs.di.koinComposeViewmodel) + } + } +} + +dependencies { + implementation(projects.sandook) +} diff --git a/composeApp/src/androidDebug/kotlin/.gitkeep b/composeApp/src/androidDebug/kotlin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/app/src/main/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to composeApp/src/androidMain/AndroidManifest.xml diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt new file mode 100644 index 00000000..20378d13 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail + +import android.app.Application + +class KrailApplication : Application() { + + override fun onCreate() { + super.onCreate() + instance = this + } + + companion object { + var instance : Application? = null + } +} diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt new file mode 100644 index 00000000..6c3fafc2 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -0,0 +1,38 @@ +package xyz.ksharma.krail + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import xyz.ksharma.krail.KrailApp + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + + enableEdgeToEdge() + super.onCreate(savedInstanceState) + +/* + val applicationComponent = AndroidApplicationComponent.from(this) +*/ + + setContent { + KrailApp() + } + } + + /* + private val koinConfig1 = koinConfiguration { + androidContext(androidContext = this@MainActivity.applicationContext) + modules(androidDbModule) + includes(koinConfig) + } + */ +} + +/* +private fun AndroidApplicationComponent.Companion.from(context: Context): AndroidApplicationComponent { + return (context.applicationContext as KrailApplication).component +} +*/ diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt new file mode 100644 index 00000000..0129e774 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt @@ -0,0 +1,9 @@ +package xyz.ksharma.krail + +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +actual fun getPlatform(): Platform = AndroidPlatform() diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt new file mode 100644 index 00000000..758abdee --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.di + +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.dsl.koinConfiguration +import xyz.ksharma.krail.KrailApplication + +actual fun nativeConfig() = koinConfiguration { + androidLogger() + androidContext(KrailApplication.instance ?: error("No Android application context set")) +} diff --git a/app/proguard-rules.pro b/composeApp/src/androidMain/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to composeApp/src/androidMain/proguard-rules.pro diff --git a/app/src/main/res/color/gradient_background.xml b/composeApp/src/androidMain/res/color/gradient_background.xml similarity index 100% rename from app/src/main/res/color/gradient_background.xml rename to composeApp/src/androidMain/res/color/gradient_background.xml diff --git a/app/src/debug/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml similarity index 100% rename from app/src/debug/res/drawable/ic_launcher_background.xml rename to composeApp/src/androidMain/res/drawable/ic_launcher_background.xml diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_foreground.xml rename to composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app/src/main/res/values-night/colors.xml b/composeApp/src/androidMain/res/values-night/colors.xml similarity index 100% rename from app/src/main/res/values-night/colors.xml rename to composeApp/src/androidMain/res/values-night/colors.xml diff --git a/app/src/main/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to composeApp/src/androidMain/res/values/colors.xml diff --git a/app/src/main/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml similarity index 100% rename from app/src/main/res/values/strings.xml rename to composeApp/src/androidMain/res/values/strings.xml diff --git a/composeApp/src/androidMain/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml new file mode 100644 index 00000000..931e639d --- /dev/null +++ b/composeApp/src/androidMain/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/xml/backup_rules.xml b/composeApp/src/androidMain/res/xml/backup_rules.xml similarity index 100% rename from app/src/main/res/xml/backup_rules.xml rename to composeApp/src/androidMain/res/xml/backup_rules.xml diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/composeApp/src/androidMain/res/xml/data_extraction_rules.xml similarity index 100% rename from app/src/main/res/xml/data_extraction_rules.xml rename to composeApp/src/androidMain/res/xml/data_extraction_rules.xml diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000..d171a450 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,4 @@ + + + Krail App + diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt new file mode 100644 index 00000000..1b30bfea --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail + +import kotlinx.coroutines.CoroutineDispatcher + +data class AppCoroutineDispatchers( + val io: CoroutineDispatcher, + val databaseWrite: CoroutineDispatcher, + val databaseRead: CoroutineDispatcher, + val computation: CoroutineDispatcher, + val main: CoroutineDispatcher, +) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt new file mode 100644 index 00000000..802d1692 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail + +import androidx.compose.runtime.Composable +import org.koin.compose.KoinApplication +import xyz.ksharma.krail.di.koinConfig +import xyz.ksharma.krail.taj.theme.KrailTheme + +@Composable +fun KrailApp() { + KoinApplication(application = koinConfig) { + KrailTheme { + KrailNavHost() + } + } +} diff --git a/app/src/main/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt similarity index 90% rename from app/src/main/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt index bc1e9d92..c5e43cdc 100644 --- a/app/src/main/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt @@ -1,4 +1,5 @@ -package xyz.ksharma.krail.navigation + +package xyz.ksharma.krail import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -8,20 +9,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.serialization.Serializable -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.getForegroundColor -import xyz.ksharma.krail.design.system.unspecifiedColor +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.splash.SplashScreen import xyz.ksharma.krail.splash.SplashViewModel +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.getForegroundColor +import xyz.ksharma.krail.taj.unspecifiedColor import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.toHex import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute @@ -62,7 +63,7 @@ fun KrailNavHost(modifier: Modifier = Modifier) { tripPlannerDestinations(navController = navController) composable { - val viewModel = hiltViewModel() + val viewModel: SplashViewModel = koinViewModel() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() val mode by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt new file mode 100644 index 00000000..17fe15b1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail + +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt new file mode 100644 index 00000000..48d8d9bb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt @@ -0,0 +1,22 @@ +package xyz.ksharma.krail.di + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.includes +import org.koin.dsl.koinConfiguration +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.di.sandookModule +import xyz.ksharma.krail.splash.SplashViewModel +import xyz.ksharma.krail.trip.planner.network.api.di.networkModule +import xyz.ksharma.krail.trip.planner.ui.di.viewModelsModule + +val koinConfig = koinConfiguration { + includes(nativeConfig()) + modules(networkModule + viewModelsModule + sandookModule + splashModule) +} + +val splashModule = module { + viewModelOf(::SplashViewModel) +} + +expect fun nativeConfig() : KoinAppDeclaration diff --git a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt similarity index 96% rename from app/src/main/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt index aed7fc57..e10082d7 100644 --- a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt @@ -29,15 +29,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.delay -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import org.jetbrains.compose.ui.tooling.preview.Preview +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun SplashScreen( @@ -177,7 +176,6 @@ private fun AnimatedLetter( ) } -@PreviewLightDark @Preview @Composable private fun PreviewLogo() { diff --git a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt similarity index 75% rename from app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt index 05748b94..b6621612 100644 --- a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt @@ -2,8 +2,8 @@ package xyz.ksharma.krail.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -11,17 +11,12 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -import javax.inject.Inject -@HiltViewModel -class SplashViewModel @Inject constructor( - sandookFactory: SandookFactory, +class SplashViewModel( + private val sandook: Sandook, ) : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.THEME) - private val _uiState: MutableStateFlow = MutableStateFlow(null) val uiState: MutableStateFlow = _uiState @@ -33,7 +28,7 @@ class SplashViewModel @Inject constructor( private fun getThemeTransportMode() { viewModelScope.launch(Dispatchers.IO) { - val productClass = sandook.getInt("selectedMode") + val productClass = sandook.getProductClass()?.toInt() ?: 0 val mode = TransportMode.toTransportModeType(productClass) _uiState.value = mode } diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt new file mode 100644 index 00000000..607b0294 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt @@ -0,0 +1,5 @@ +package xyz.ksharma.krail + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { KrailApp() } diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt new file mode 100644 index 00000000..77c96dc5 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail + +import platform.UIKit.UIDevice + +class IOSPlatform : Platform { + override val name: String = + UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt new file mode 100644 index 00000000..7d51e1d7 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.di + +import org.koin.dsl.koinConfiguration + +actual fun nativeConfig() = koinConfiguration { + printLogger() +} diff --git a/config/detekt/detekt-baseline.xml b/config/detekt/detekt-baseline.xml deleted file mode 100644 index 64a56649..00000000 --- a/config/detekt/detekt-baseline.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/config/detekt/detekt.yaml b/config/detekt/detekt.yaml deleted file mode 100644 index a562a979..00000000 --- a/config/detekt/detekt.yaml +++ /dev/null @@ -1,408 +0,0 @@ -Compose: - ComposableAnnotationNaming: - active: true - ComposableNaming: - active: true - # -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters) - # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter - ComposableParamOrder: - active: true - # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsLambda: MyLambdaType - CompositionLocalAllowlist: - active: true - # -- You can optionally define a list of CompositionLocals that are allowed here - allowedCompositionLocals: LocalTextColor,LocalContentAlpha, LocalTextStyle,LocalContentColor,LocalOnContentColor,LocalKrailTypography,LocalKrailColors,LocalThemeColor,LocalThemeContentColor - CompositionLocalNaming: - active: true - ContentEmitterReturningValues: - active: true - # -- You can optionally add your own composables here - # contentEmitters: MyComposable,MyOtherComposable - ContentTrailingLambda: - active: true - # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsComposableLambda: MyComposableLambdaType - DefaultsVisibility: - active: true - LambdaParameterInRestartableEffect: - active: false - # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsLambda: MyLambdaType - Material2: - active: true # Opt-in, disabled by default. Turn on if you want to disallow Material 2 usages. - # -- You can optionally allow parts of it, if you are in the middle of a migration. - # allowedFromM2: icons.Icons,TopAppBar - ModifierClickableOrder: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierComposable: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierComposed: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierMissing: - active: true - # -- You can optionally control the visibility of which composables to check for here - # -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`) - # checkModifiersForVisibility: only_public - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierNaming: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierNotUsedAtRoot: - active: true - # -- You can optionally add your own composables here - # contentEmitters: MyComposable,MyOtherComposable - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierReused: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierWithoutDefault: - active: true - MultipleEmitters: - active: true - # -- You can optionally add your own composables here that will count as content emitters - # contentEmitters: MyComposable,MyOtherComposable - # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals) - # contentEmittersDenylist: MyNonEmitterComposable - MutableParams: - active: true - MutableStateAutoboxing: - active: true - MutableStateParam: - active: true - ParameterNaming: - active: true - # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsComposableLambda: MyComposableLambdaType - PreviewAnnotationNaming: - active: true - PreviewPublic: - active: true - RememberMissing: - active: true - RememberContentMissing: - active: true - UnstableCollections: - active: false # Opt-in, disabled by default. Turn on if you want to enforce this (e.g. you have strong skipping disabled) - ViewModelForwarding: - active: true - # -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here: - # allowedStateHolderNames: .*ViewModel,.*Presenter - # -- You can optionally add an allowlist for Composable names that won't be affected by this rule - # allowedForwarding: .*Content,.*FancyStuff - # -- You can optionally add an allowlist for ViewModel/StateHolder names that won't be affected by this rule - # allowedForwardingOfTypes: PotatoViewModel,(Apple|Banana)ViewModel,.*FancyViewModel - ViewModelInjection: - active: true - # -- You can optionally add your own ViewModel factories here - # viewModelFactories: hiltViewModel,potatoViewModel - -comments: - CommentOverPrivateProperty: - active: true - UndocumentedPublicClass: - active: true - excludes: ['**/*.kt'] - includes: ['**/detekt-api/src/main/**/api/*.kt'] - UndocumentedPublicFunction: - active: true - excludes: ['**/*.kt'] - includes: ['**/detekt-api/src/main/**/api/*.kt'] - -complexity: - StringLiteralDuplication: - active: true - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - threshold: 5 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - ignoreAnnotated: ['Preview', 'PreviewLightDark', 'PreviewComponent'] - ComplexInterface: - active: false - threshold: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - CyclomaticComplexMethod: - active: true - ignoreSingleWhenExpression: true - LargeClass: - active: true - excludes: ['**/test/**', '**/*.Test.kt', '**/*.Spec.kt'] - MethodOverloading: - active: true - TooManyFunctions: - active: false - ignoreAnnotatedFunctions: ['Composable'] - excludes: ['**/test/**', '**/functionalTest/**'] - LongMethod: - active: true - threshold: 60 - ignoreAnnotated: ['Composable'] - LongParameterList: - active: false - ignoreAnnotated: ['Composable', 'Test', 'GET', 'POST'] - -coroutines: - active: true - GlobalCoroutineUsage: - active: true - RedundantSuspendModifier: - active: true - SleepInsteadOfDelay: - active: true - SuspendFunWithFlowReturnType: - active: true - -exceptions: - InstanceOfCheckForException: - active: true - NotImplementedDeclaration: - active: true - ObjectExtendsThrowable: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - ThrowingNewInstanceOfSameException: - active: true - -formatting: - active: true - android: false - autoCorrect: true - ContextReceiverMapping: - active: true - Filename: - active: false - MaximumLineLength: - active: false - ParameterListSpacing: - active: true - TypeParameterListSpacing: - active: true - Indentation: - active: true - indentSize: 4 - TrailingCommaOnCallSite: - active: true - TrailingCommaOnDeclarationSite: - active: true - -naming: - ClassNaming: - ignoreAnnotated: ['org.junit.jupiter.api.Nested'] - FunctionNaming: - active: true - excludes: [] - ignoreAnnotated: ['Test', 'ParameterizedTest', 'RepeatedTest', 'TestFactory', 'Composable'] - TopLevelPropertyNaming: - constantPattern: '[a-z][_A-Za-z0-9]*|[A-Z][_A-Z0-9]*' - InvalidPackageDeclaration: - active: true - excludes: ['**/build-logic/**/*.kt', '**/*.kts'] - NoNameShadowing: - active: true - NonBooleanPropertyPrefixedWithIs: - active: true - VariableMaxLength: - active: true - VariableMinLength: - active: true - -performance: - SpreadOperator: - excludes: ['**/test/**', '**/functionalTest/**'] - -potential-bugs: - AvoidReferentialEquality: - active: true - DontDowncastCollectionTypes: - active: true - ElseCaseInsteadOfExhaustiveWhen: - active: true - ExitOutsideMain: - active: false - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - ImplicitUnitReturnType: - active: true - MapGetWithNotNullAssertionOperator: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnreachableCatchBlock: - active: true - UnsafeCast: - active: true - excludes: ['**/test/**', '**/*.Test.kt', '**/*.Spec.kt'] - UselessPostfixExpression: - active: true - -style: - BracesOnIfStatements: - active: true - singleLine: 'consistent' - multiLine: 'consistent' - CanBeNonNullable: - active: true - CascadingCallWrapping: - active: false - ClassOrdering: - active: true - CollapsibleIfStatements: - active: true - DestructuringDeclarationWithTooManyEntries: - active: true - EqualsOnSignatureLine: - active: true - ExplicitCollectionElementAccessMethod: - active: true - ExplicitItLambdaParameter: - active: true - ForbiddenComment: - active: true - comments: - - value: 'FIXME:' - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' - - value: 'STOPSHIP:' - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' - - value: 'TODO:' - reason: 'Forbidden TODO todo marker in comment, please do the changes.' - - value: '@author' - reason: 'Authors are not recorded in KDoc.' - - value: '@requiresTypeResolution' - reason: 'Use @RequiresTypeResolution annotation on the class instead.' - excludes: ['**/detekt-rules-style/**/ForbiddenComment.kt'] - ForbiddenImport: - active: true - imports: - - value: 'org.assertj.core.api.Assertions' - reason: 'Import Assertions.assertThat instead.' - - value: 'org.junit.jupiter.api.Assertions*' - reason: 'Use AssertJ assertions instead.' - - value: 'org.junit.jupiter.api.assertAll' - reason: 'Use AssertJ assertSoftly instead.' - - value: 'org.junit.jupiter.api.assertThrows' - reason: 'Use AssertJ assertThatCode { }.isInstanceOf() or assertThatExceptionOfType().isThrownBy { } instead.' - - value: 'org.junit.jupiter.api.assertDoesNotThrow' - reason: 'Use AssertJ assertThatCode { }.doesNotThrowAnyException() instead.' - # These don't have AssertJ alternatives, so just allow them: - #- value: 'org.junit.jupiter.api.fail' - #- value: 'org.junit.jupiter.api.assertTimeout' - #- value: 'org.junit.jupiter.api.assertTimeoutPreemptively' - - value: 'java.util.stream.*' - reason: "Use Kotlin's sequences instead." - ForbiddenMethodCall: - active: true - methods: - - 'kotlin.io.print' - - 'kotlin.io.println' - - 'java.net.URL.openStream' - - 'java.lang.Class.getResourceAsStream' - - 'java.lang.ClassLoader.getResourceAsStream' - - 'org.jetbrains.kotlin.diagnostics.DiagnosticUtils.getLineAndColumnInPsiFile' - ForbiddenVoid: - active: true - MagicNumber: - active: false - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - ignorePropertyDeclaration: true - ignoreAnnotation: true - ignoreEnums: true - ignoreNumbers: - - '-1' - - '0' - - '1' - - '2' - - '100' - - '1000' - MandatoryBracesLoops: - active: true - MaxLineLength: - active: true - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - excludeCommentStatements: true - autoCorrect: true - NestedClassesVisibility: - active: true - ObjectLiteralToLambda: - active: true - PreferToOverPairSyntax: - active: true - RedundantExplicitType: - active: true - RedundantHigherOrderMapUsage: - active: true - RedundantVisibilityModifierRule: - active: true - ReturnCount: - active: true - excludeGuardClauses: true - SpacingBetweenPackageAndImports: - active: true - TrimMultilineRawString: - active: true - UnderscoresInNumericLiterals: - active: true - UnnecessaryAnnotationUseSiteTarget: - active: true - UnnecessaryBackticks: - active: true - UnnecessaryFilter: - active: true - UnnecessaryLet: - active: true - UnnecessaryInnerClass: - active: true - ignoreAnnotated: ['Nested'] - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: false # formatting already have this rule enabled - UnusedPrivateMember: - active: false - allowedNames: '(_|ignored|expected)' - UseAnyOrNoneInsteadOfFind: - active: true - UseCheckOrError: - active: true - UseEmptyCounterpart: - active: true - UseIfEmptyOrIfBlank: - active: true - UseIsNullOrEmpty: - active: true - UseLet: - active: true - UseOrEmpty: - active: true - UseRequire: - active: true - UseRequireNotNull: - active: true - VarCouldBeVal: - active: true - ignoreAnnotated: ['Parameter'] - WildcardImport: - active: true - excludeImports: [] diff --git a/core/coroutines-ext/build.gradle.kts b/core/coroutines-ext/build.gradle.kts index fb5ba3e0..bc9ade18 100644 --- a/core/coroutines-ext/build.gradle.kts +++ b/core/coroutines-ext/build.gradle.kts @@ -1,11 +1,24 @@ plugins { alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) } android { namespace = "xyz.ksharma.krail.coroutines.ext" } -dependencies { - implementation(libs.test.androidxCoreKtx) +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.coroutines.core) + } + } + } } diff --git a/core/coroutines-ext/src/main/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt b/core/coroutines-ext/src/commonMain/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt similarity index 100% rename from core/coroutines-ext/src/main/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt rename to core/coroutines-ext/src/commonMain/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt diff --git a/core/date-time/build.gradle.kts b/core/date-time/build.gradle.kts index 34359049..59230f24 100644 --- a/core/date-time/build.gradle.kts +++ b/core/date-time/build.gradle.kts @@ -1,12 +1,33 @@ plugins { alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) } android { namespace = "xyz.ksharma.krail.core.datetime" } -dependencies { - testImplementation(libs.test.kotlin) - testImplementation(libs.test.googleTruth) - implementation(libs.kotlinx.datetime) + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.datetime) + implementation(compose.runtime) + } + } + + commonTest { + dependencies { + implementation(libs.test.kotlin) + } + } + } } diff --git a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt new file mode 100644 index 00000000..3af36886 --- /dev/null +++ b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt @@ -0,0 +1,95 @@ +package xyz.ksharma.krail.core.datetime + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.math.absoluteValue +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.DurationUnit + +object DateTimeHelper { + + fun String.formatTo12HourTime(): String { + val localDateTime = Instant.parse(this).toLocalDateTime(TimeZone.UTC) + val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format + val minute = localDateTime.minute.toString().padStart(2, '0') + val amPm = if (localDateTime.hour < 12) "am" else "pm" + return "$hour:$minute $amPm" + } + + fun String.utcToAEST(): String { + val instant = Instant.parse(this) + val aestZone = TimeZone.of("Australia/Sydney") + val localDateTime = instant.toLocalDateTime(aestZone) + return localDateTime.toString() + } + + fun String.utcToLocalDateTimeAEST(): LocalDateTime { + val instant = Instant.parse(this) + val aestZone = TimeZone.of("Australia/Sydney") + val localDateTime = instant.toLocalDateTime(aestZone) + return localDateTime + } + + fun LocalDateTime.toHHMM(): String { + val hour = if (this.hour % 12 == 0) 12 else this.hour % 12 // Ensure 12-hour format + val minute = this.minute.toString().padStart(2, '0') + val amPm = if (this.hour < 12) "AM" else "PM" + return "$hour:$minute $amPm" + } + + /* fun String.aestToHHMM(): String { + val dateTimeString = if (this.length == 16) "$this:00" else this + val localDateTime = Instant.parse(dateTimeString).toLocalDateTime(TimeZone.of("Australia/Sydney")) + val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format + val minute = localDateTime.minute.toString().padStart(2, '0') + val amPm = if (localDateTime.hour < 12) "AM" else "PM" + return "$hour:$minute $amPm" + }*/ + + fun calculateTimeDifferenceFromNow( + utcDateString: String, + now: Instant = Clock.System.now(), + ): Duration { + val instant = Instant.parse(utcDateString) + return instant - now + } + + fun Duration.toGenericFormattedTimeString(): String { + val totalMinutes = this.toLong(DurationUnit.MINUTES) + val hours = this.toLong(DurationUnit.HOURS) + val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) + + return when { + totalMinutes < 0 -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"} ago" + totalMinutes == 0L -> "Now" + hours == 1L -> "in ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" + hours >= 2 -> "in ${hours.absoluteValue}h" + else -> "in ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" + } + } + + fun Duration.toFormattedDurationTimeString(): String { + val totalMinutes = this.toLong(DurationUnit.MINUTES) + val hours = this.toLong(DurationUnit.HOURS) + val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) + + return when { + hours >= 1 && partialMinutes == 0L -> "${hours.absoluteValue}h" + hours >= 1 -> "${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" + else -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" + } + } + + fun calculateTimeDifference( + utcDateString1: String, + utcDateString2: String, + ): Duration { + val instant1 = Instant.parse(utcDateString1) + val instant2 = Instant.parse(utcDateString2) + return (instant1 - instant2).absoluteValue + } +} diff --git a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt new file mode 100644 index 00000000..d5622640 --- /dev/null +++ b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt @@ -0,0 +1,91 @@ +package xyz.ksharma.krail.core.datetime + +import kotlinx.datetime.Instant +import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.minutes + +class DateTimeHelperTest { + + @Test + fun testCalculateTimeDifferenceFromNow() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-07T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(40L, difference.inWholeMinutes) + } + + @Test + fun testCalculateTimeDifferenceNextDay() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-08T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(1480L, difference.inWholeMinutes) + } + + @Test + fun testCalculateTimeDifferencePreviousDay() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-06T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(-1400L, difference.inWholeMinutes) + } + + @Test + fun testFormatTo12HourTime() { + assertEquals("12:00 am", "2024-10-07T00:00:00Z".formatTo12HourTime()) + assertEquals("1:00 am", "2024-10-07T01:00:00Z".formatTo12HourTime()) + assertEquals("12:00 pm", "2024-10-07T12:00:00Z".formatTo12HourTime()) + assertEquals("1:00 pm", "2024-10-07T13:00:00Z".formatTo12HourTime()) + } + + @Test + fun testUtcToAEST() { + assertEquals("2024-10-07T11:00:10", "2024-10-07T00:00:10Z".utcToAEST()) + assertEquals("2024-10-07T12:00", "2024-10-07T01:00:00Z".utcToAEST()) + assertEquals("2024-10-07T12:00:23", "2024-10-07T01:00:23Z".utcToAEST()) + } + + @Test + fun testToGenericFormattedTimeString() { + assertEquals("40 mins ago", (-40).minutes.toGenericFormattedTimeString()) + assertEquals("Now", 0.minutes.toGenericFormattedTimeString()) + assertEquals("in 1h 20m", 80.minutes.toGenericFormattedTimeString()) + assertEquals("in 2h", 120.minutes.toGenericFormattedTimeString()) + } + + @Test + fun testToFormattedDurationTimeString() { + assertEquals("1h 20m", 80.minutes.toFormattedDurationTimeString()) + assertEquals("2h", 120.minutes.toFormattedDurationTimeString()) + assertEquals("40 mins", 40.minutes.toFormattedDurationTimeString()) + } + + @Test + fun testCalculateTimeDifference() { + val difference1 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-07T08:20:00Z", + ) + assertEquals(40L, difference1.inWholeMinutes) + + val difference2 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-06T09:00:00Z", + ) + assertEquals(1440L, difference2.inWholeMinutes) + + val difference3 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-07T09:00:00Z", + ) + assertEquals(0L, difference3.inWholeMinutes) + } +} diff --git a/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt deleted file mode 100644 index b917be9a..00000000 --- a/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt +++ /dev/null @@ -1,113 +0,0 @@ -package xyz.ksharma.krail.core.datetime - -import kotlinx.datetime.Instant -import timber.log.Timber -import java.time.Duration -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import kotlin.math.absoluteValue -import kotlin.time.Duration.Companion.minutes -import kotlin.time.DurationUnit -import kotlin.time.toKotlinDuration - -object DateTimeHelper { - - fun String.formatTo12HourTime(): String { - // Parse the string as ZonedDateTime - val zonedDateTime = ZonedDateTime.parse(this) - - // Define the formatter for 12-hour time with AM/PM - val timeFormatter = DateTimeFormatter.ofPattern("h:mm a") - - // Format the ZonedDateTime to 12-hour format - return zonedDateTime.format(timeFormatter) - } - - /** - * Convert a date-time string in UTC to AEST time zone. - * E.g. "2021-08-01T12:00:00Z" -> "2021-08-01T22:00:00+10:00[Australia/Sydney]" - */ - fun String.utcToAEST(): String { - // Parse the string as a ZonedDateTime in UTC - val utcDateTime = ZonedDateTime.parse(this, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Convert to AEST time zone (UTC+10) - val aestZoneId = ZoneId.of("Australia/Sydney") - val aestDateTime = utcDateTime.withZoneSameInstant(aestZoneId) - return aestDateTime.format(DateTimeFormatter.ISO_DATE_TIME) - } - - /** - * Converts a date-time string in Australian Eastern Standard Time (AEST) - * to a string representing the time in hh:mm a format (12-hour format). - * - * The input string is expected to be in ISO 8601 format (e.g., "2024-09-24T19:00:00+10:00"). - * - * @return The time in hh:mm a format (e.g., "07:00 am"). - */ - fun String.aestToHHMM(): String { - val aestDateTime = ZonedDateTime.parse(this, DateTimeFormatter.ISO_DATE_TIME) - val timeFormatter = DateTimeFormatter.ofPattern("hh:mm a") - return aestDateTime.format(timeFormatter) - } - - /** - * Calculates the time difference between two dates in the UTC format - * ("2024-09-24T09:00:00Z") - * - * @return The time difference between the two dates as a kotlin.time.Duration object. - */ - fun calculateTimeDifferenceFromNow( - utcDateString: String, - now: Instant = kotlinx.datetime.Clock.System.now(), // Get current Instant in UTC - ): kotlin.time.Duration { - val instant = Instant.parse(utcDateString) // Parse UTC string to Instant - return instant - now - } - - fun kotlin.time.Duration.toGenericFormattedTimeString(): String { - val totalMinutes = this.toLong(DurationUnit.MINUTES) - val hours = this.toLong(DurationUnit.HOURS) - val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) - - val formattedDifference = when { - totalMinutes < 0 -> - "${totalMinutes.absoluteValue} " + - "${if (totalMinutes.absoluteValue == 1L) "min" else "mins"} ago" - - totalMinutes == 0L -> "Now" - hours == 1L -> "in ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" - hours >= 2 -> "in ${hours.absoluteValue}h" - else -> "in ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" - } - Timber.d("\t minutes: $partialMinutes, hours: $hours, formattedDifference: $formattedDifference -> originTime") - return formattedDifference - } - - fun kotlin.time.Duration.toFormattedDurationTimeString(): String { - val totalMinutes = this.toLong(DurationUnit.MINUTES) - val hours = this.toLong(DurationUnit.HOURS) - val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) - - val formattedDifference = when { - hours >= 1 -> "${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" - else -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" - } - return formattedDifference - } - - fun calculateTimeDifference( - utcDateString1: String, - utcDateString2: String, - ): kotlin.time.Duration { - // Parse the first UTC date string to a ZonedDateTime - val dateTime1 = ZonedDateTime.parse(utcDateString1, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Parse the second UTC date string to a ZonedDateTime - val dateTime2 = ZonedDateTime.parse(utcDateString2, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Calculate the duration between the two ZonedDateTime instances - return Duration.between(dateTime1, dateTime2).toKotlinDuration() - } -} diff --git a/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt deleted file mode 100644 index 4b35d437..00000000 --- a/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package xyz.ksharma.krail.core.datetime - -import com.google.common.truth.Truth.assertThat -import kotlinx.datetime.Instant -import kotlin.test.Test - -class DateTimeHelperTest { - - @Test - fun testCalculateTimeDifference() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-07T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(40L) - } - - @Test - fun testCalculateTimeDifferenceNextDay() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-08T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(1480L) - } - - @Test - fun testCalculateTimeDifferencePreviousDay() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-06T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(-1400L) - } -} diff --git a/core/design-system/README.md b/core/design-system/README.md deleted file mode 100644 index 9b32f5fa..00000000 --- a/core/design-system/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Important links for documentation - -Compose Foundation https://developer.android.com/jetpack/androidx/releases/compose-foundation -Material 3 Compose Code - https://github.com/androidx/androidx/tree/androidx-main/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3 -Compose Foundation Examples - https://composables.com/foundation/ -Compose Foundation Official Samples - https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ diff --git a/core/design-system/build.gradle.kts b/core/design-system/build.gradle.kts deleted file mode 100644 index 3181d038..00000000 --- a/core/design-system/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library.compose) - alias(libs.plugins.cash.paparazzi) -} - -android { - namespace = "xyz.ksharma.krail.design.system" -} - -dependencies { - api(platform(libs.compose.bom)) - implementation(libs.compose.foundation) - implementation(libs.compose.ui) - implementation(libs.compose.ui.graphics) - api(libs.compose.ui.tooling.preview) - implementation(libs.compose.material3) // adding for reading code inspiration. - - androidTestImplementation(platform(libs.compose.bom)) - debugApi(libs.compose.ui.tooling) - debugApi(libs.test.composeUiTestManifest) -} diff --git a/core/design-system/src/main/kotlin/xyz/.DS_Store b/core/design-system/src/main/kotlin/xyz/.DS_Store deleted file mode 100644 index 3bebff83..00000000 Binary files a/core/design-system/src/main/kotlin/xyz/.DS_Store and /dev/null differ diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store b/core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store deleted file mode 100644 index aeb58a8f..00000000 Binary files a/core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store and /dev/null differ diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store b/core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store deleted file mode 100644 index 5d56e9d7..00000000 Binary files a/core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store and /dev/null differ diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt deleted file mode 100644 index 266b0960..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt +++ /dev/null @@ -1,85 +0,0 @@ -package xyz.ksharma.krail.design.system.components - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.theme.KrailTheme - -/** - * FAB is a floating action button that represents the primary action of a screen. - * - * @param containerColor The background color of the FAB. - * @param contentColor The color of the content (e.g., icon) inside the FAB. - * @param onClick The callback to be invoked when the FAB is clicked. - * @param modifier The modifier to be applied to the FAB. - * @param content The composable content to be displayed inside the FAB. - */ -@Composable -fun Fab( - containerColor: Color, - contentColor: Color, - onClick: () -> Unit, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - Box( - modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .padding(24.dp) - .clickable(role = Role.Button, onClick = onClick), - contentAlignment = Alignment.BottomEnd, - ) { - Box( - modifier = Modifier - .size(56.dp) - .background(color = containerColor, shape = CircleShape), - contentAlignment = Alignment.Center, - ) { - CompositionLocalProvider(LocalContentColor provides contentColor) { - content() - } - } - } -} - -// region Preview - -@PreviewLightDark -@Composable -private fun PreviewFab() { - KrailTheme { - Fab( - containerColor = KrailTheme.colors.surface, - contentColor = KrailTheme.colors.onSurface, - onClick = {}, - ) { - Image( - painter = painterResource(R.drawable.star_outline), - contentDescription = null, - colorFilter = ColorFilter.tint(color = LocalContentColor.current), - modifier = Modifier.size(32.dp), - ) - } - } -} - -// endregion diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt deleted file mode 100644 index d582ec47..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt +++ /dev/null @@ -1,154 +0,0 @@ -package xyz.ksharma.krail.design.system.components - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme - -@Composable -fun TitleBar( - title: @Composable () -> Unit, - modifier: Modifier = Modifier, - navAction: @Composable (() -> Unit)? = null, - actions: @Composable (() -> Unit)? = null, -) { - Row( - modifier = modifier - .statusBarsPadding() - .fillMaxWidth() - .heightIn(min = 56.dp) - .padding(end = 16.dp, start = 8.dp) - .padding(vertical = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - navAction?.let { - navAction() - } - Row( - modifier = Modifier - .weight(1f) - .padding(start = 10.dp), - ) { - CompositionLocalProvider( - LocalTextColor provides KrailTheme.colors.onSurface, - LocalTextStyle provides KrailTheme.typography.headlineMedium, - ) { - title() - } - } - actions?.let { - Row( - modifier = Modifier.padding(start = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - CompositionLocalProvider( - LocalContentColor provides KrailTheme.colors.onSurface, - ) { - actions() - } - } - } - } -} - -// region Previews - -@PreviewComponent -@Composable -private fun TitleBarPreview() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - actions = { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -@Preview -@Composable -private fun TitleBarPreviewMultipleActions() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - actions = { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -@PreviewComponent -@Composable -private fun TitleBarPreviewNoActions() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -// endregion diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt deleted file mode 100644 index 69329eb2..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.ksharma.krail.design.system.preview - -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark - -/** - * A MultiPreview annotation for displaying a component using light and dark themes along with - * large font size. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) -@PreviewLightDark -@Preview(name = "Large Font", fontScale = 2f) -annotation class PreviewComponent diff --git a/core/design-system/src/main/res/drawable/star_filled.xml b/core/design-system/src/main/res/drawable/star_filled.xml deleted file mode 100644 index 0b4fb8b6..00000000 --- a/core/design-system/src/main/res/drawable/star_filled.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/core/design-system/src/main/res/drawable/star_outline.xml b/core/design-system/src/main/res/drawable/star_outline.xml deleted file mode 100644 index 8e80be47..00000000 --- a/core/design-system/src/main/res/drawable/star_outline.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/core/design-system/src/test/kotlin/xyz/.DS_Store b/core/design-system/src/test/kotlin/xyz/.DS_Store deleted file mode 100644 index 80a3d64a..00000000 Binary files a/core/design-system/src/test/kotlin/xyz/.DS_Store and /dev/null differ diff --git a/core/design-system/src/test/kotlin/xyz/ksharma/.DS_Store b/core/design-system/src/test/kotlin/xyz/ksharma/.DS_Store deleted file mode 100644 index 1677d621..00000000 Binary files a/core/design-system/src/test/kotlin/xyz/ksharma/.DS_Store and /dev/null differ diff --git a/core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store b/core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store deleted file mode 100644 index 122de71f..00000000 Binary files a/core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store and /dev/null differ diff --git a/core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store b/core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store deleted file mode 100644 index 5e966b58..00000000 Binary files a/core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store and /dev/null differ diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts deleted file mode 100644 index 9bd1f984..00000000 --- a/core/di/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) -} - -android { - namespace = "xyz.ksharma.core.di" -} - -dependencies { - -} diff --git a/core/di/src/main/kotlin/xyz/.DS_Store b/core/di/src/main/kotlin/xyz/.DS_Store deleted file mode 100644 index 3bebff83..00000000 Binary files a/core/di/src/main/kotlin/xyz/.DS_Store and /dev/null differ diff --git a/core/di/src/main/kotlin/xyz/ksharma/.DS_Store b/core/di/src/main/kotlin/xyz/ksharma/.DS_Store deleted file mode 100644 index 0597c575..00000000 Binary files a/core/di/src/main/kotlin/xyz/ksharma/.DS_Store and /dev/null differ diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt b/core/di/src/main/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt deleted file mode 100644 index 43736f21..00000000 --- a/core/di/src/main/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt +++ /dev/null @@ -1,12 +0,0 @@ -package xyz.ksharma.krail.di - -import javax.inject.Qualifier - -enum class AppDispatchers { - Default, - IO, -} - -@Qualifier -@Retention(AnnotationRetention.RUNTIME) -annotation class Dispatcher(val dispatch: AppDispatchers) diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt b/core/di/src/main/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt deleted file mode 100644 index a92580c1..00000000 --- a/core/di/src/main/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package xyz.ksharma.krail.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob - -@Module -@InstallIn(SingletonComponent::class) -object CoroutinesModule { - - @Provides - fun provideCoroutineScope(@Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher): CoroutineScope = - CoroutineScope(context = ioDispatcher + SupervisorJob()) -} diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt b/core/di/src/main/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt deleted file mode 100644 index 654eda4c..00000000 --- a/core/di/src/main/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.ksharma.krail.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -@Module -@InstallIn(SingletonComponent::class) -object DispatchersModule { - - @Provides - @Dispatcher(AppDispatchers.Default) - fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default - - @Provides - @Dispatcher(AppDispatchers.IO) - fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO -} diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts deleted file mode 100644 index 8c5e92b3..00000000 --- a/core/network/build.gradle.kts +++ /dev/null @@ -1,51 +0,0 @@ -import java.util.Properties - -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.wire) -} - -// Get local.properties values -val localProperties = Properties() -val localPropertiesFile = rootProject.file("local.properties") -if (localPropertiesFile.exists()) { - localProperties.load(localPropertiesFile.inputStream()) -} -val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") - -android { - namespace = "xyz.ksharma.krail.network" - - buildTypes { - debug { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } - - release { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } - } -} - -wire { - kotlin { - javaInterop = true - } - sourcePath { - srcDir(files("src/main/proto")) - } -} - -dependencies { - api(projects.core.di) - implementation(projects.core.coroutinesExt) - implementation(libs.test.androidxCoreKtx) - implementation(libs.kotlinx.serialization.json) - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.okhttp.logging.interceptor) - implementation(libs.retrofit) - implementation(libs.retrofit2.kotlinx.serialization.converter) -} diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/ResponseExt.kt b/core/network/src/main/kotlin/xyz/ksharma/krail/network/ResponseExt.kt deleted file mode 100644 index 6cd709ba..00000000 --- a/core/network/src/main/kotlin/xyz/ksharma/krail/network/ResponseExt.kt +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.ksharma.krail.network - -import retrofit2.Response - -/** - * Processes a Retrofit response and returns a [Result] object. - * - * If the response is successful, the body is extracted and wrapped in a [Result.success]. - * If the response is unsuccessful, an [Exception] is created with the error code and wrapped in a [Result.failure]. - * - * @return A [Result] object containing the response body or an error. - */ -fun Response.toSafeResult(): Result { - return if (this.isSuccessful) { - this.body()?.let { body -> - Result.success(body) - } ?: Result.failure(Exception("Response body is null")) - } else { - Result.failure(Exception("API call failed with error: ${this.code()}")) - } -} diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt b/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt deleted file mode 100644 index 039e874f..00000000 --- a/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt +++ /dev/null @@ -1,58 +0,0 @@ -package xyz.ksharma.krail.network.di - -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import xyz.ksharma.krail.network.BuildConfig -import xyz.ksharma.krail.network.interceptor.AuthInterceptor -import java.util.concurrent.TimeUnit - -@Module -@InstallIn(SingletonComponent::class) -object NetworkModule { - - const val BASE_URL = "https://api.transport.nsw.gov.au" - - @Provides - fun provideOkHttpClient(): OkHttpClient { - val okhttpBuilder = OkHttpClient.Builder() - - if (BuildConfig.DEBUG) { - okhttpBuilder.addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), - ) - } - okhttpBuilder.addInterceptor(AuthInterceptor()) - - // Add Timeouts - okhttpBuilder - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - - return okhttpBuilder.build() - } - - @Provides - fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { - val json = Json { ignoreUnknownKeys = true } - - val retrofit: Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(okHttpClient) - .addConverterFactory( - json.asConverterFactory( - "application/json; charset=UTF8".toMediaType(), - ), - ) - .build() - return retrofit - } -} diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt b/core/network/src/main/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt deleted file mode 100644 index 3363ebe2..00000000 --- a/core/network/src/main/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package xyz.ksharma.krail.network.interceptor - -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import xyz.ksharma.krail.network.BuildConfig.NSW_TRANSPORT_API_KEY -import javax.inject.Singleton - -@Singleton -class AuthInterceptor : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - return chain.proceed( - Request - .Builder() - .header("Authorization", "apikey $NSW_TRANSPORT_API_KEY") - .header("accept", "application/x-google-protobuf") - .url(chain.request().url) - .build(), - ) - } -} diff --git a/feature/trip-planner/network/api/build.gradle.kts b/feature/trip-planner/network/api/build.gradle.kts deleted file mode 100644 index 4d445b50..00000000 --- a/feature/trip-planner/network/api/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) - alias(libs.plugins.kotlin.serialization) -} - -android { - namespace = "xyz.ksharma.krail.trip.planner.network.api" -} - -dependencies { - api(projects.core.network) - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.retrofit) - implementation(libs.kotlinx.serialization.json) -} diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt b/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt deleted file mode 100644 index dc0f6ddc..00000000 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object ServiceModule { - - @Provides - @Singleton - fun provideTripPlanningService(retrofit: Retrofit): TripPlanningService { - return retrofit.create(TripPlanningService::class.java) - } -} diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt b/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt deleted file mode 100644 index e4cba9d7..00000000 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.repository - -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter - -interface TripPlanningRepository { - - suspend fun stopFinder( - stopType: StopType = StopType.STOP, - stopSearchQuery: String, - ): Result - - suspend fun trip( - originStopId: String, - destinationStopId: String, - journeyTime: String? = null, - ): Result -} - -/** - * Converts Instant to ITD Time in HHMM 24-hour format. - * // todo - move to another module for time related functions - */ -fun Instant.toItdTime(): String { - val formatter = DateTimeFormatter.ofPattern("HHMM").withZone(ZoneId.of("Australia/Sydney")) - return formatter.format(this) -} diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt deleted file mode 100644 index f71febd7..00000000 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt +++ /dev/null @@ -1,353 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.service - -import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Query -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse - -/** - * Swagger: https://opendata.transport.nsw.gov.au/dataset/trip-planner-apis/resource/917c66c3-8123-4a0f-b1b1-b4220f32585d - */ -interface TripPlanningService { - - /** - * This endpoint returns info about stops that match the search criteria. Matches can be - * sorted on matchQuality to determine the best matches for the given input, while the best - * match will be indicated by the isBest value. - * - * Provides capability to return all NSW public transport stop, station, wharf, - * points of interest and known addresses to be used for auto-suggest/auto-complete (to be - * used with the Trip planner and Departure board APIs). - */ - @GET("v1/tp/stop_finder") - suspend fun stopFinder( - /** - * Used to set the response data type. This documentation only covers responses that use the JSON format. - * Setting the outputFormat value to rapidJSON is required to enable JSON output. - * - * Available values : rapidJSON - */ - @Query("outputFormat") outputFormat: String = "rapidJSON", - - /** - * This specifies the type of results expected in the list of returned stops. - * By specifying `any`, locations of all types can be returned. - * If you specifically know that you're searching using a coord, specify `coord`. - * Likewise, if you're using a stop ID or global stop ID as an input, use `stop` - * for more accurate results. - * - * Available values : any, coord, poi, stop - */ - @Query("type_sf") typeSf: String = "stop", - - /** - * This is the search term that will be used to find locations. - * To lookup a coordinate, set type_sf to coord, and use the following format: - * LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). For example, 151.206290:-33.884080:EPSG:4326. - * To lookup a stop set type_sf to stop and enter the stop id or global stop ID. For example, 10101100 - * - * Default value : Circular Quay - */ - @Query("name_sf") nameSf: String, - - /** - * This specifies the format the coordinates are returned in. - * While other variations are available, the EPSG:4326 format will return the widely-used format. - * - * Available values : EPSG:4326 - */ - @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", - - /** - * Including this parameter enables a number of options that result in the stop finder - * operating in the same way as the Transport for NSW Trip Planner web site. - * - * Available values : true - * - * Default value : true - */ - @Query("TfNSWSF") tfNSWSF: String = "true", - - /** - * Indicates which version of the API the caller is expecting for both request and response - * data. Note that if this version differs - * from the version listed above then the returned data may not be as expected. - * - * Default value : 10.2.1.42 - */ - @Query("version") version: String? = null, - ): Response - - /** - * This endpoint is used to find a list of journeys between two locations at the specified - * date and time. For example, if the user is at the Airport and wants to get to Manly using - * public transport but isn't sure how exactly, this call will tell them exactly which train, - * bus, ferry or light rail to catch, and between which stops. It is extremely detailed, - * and includes the the specific path the vehicle(s) will take. - * - * Provides capability to provide NSW public transport trip plan options, - * including walking and driving legs, real-time and Opal fare information. - */ - @GET("v1/tp/trip") - suspend fun trip( - /** - * Used to set the response data type. This documentation only covers responses that use - * the JSON format. Setting the outputFormat value to rapidJSON is required to enable JSON output. - */ - @Query("outputFormat") outputFormat: String = "rapidJSON", - - /** - * This specifies the format the coordinates are returned in. While other variations are - * available, the EPSG:4326 format will return the widely-used format. - */ - @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", - - /** - * This value anchors the requested date time. If set to dep, then trips departing after - * the specified date/time at the specified location are included. - * - * If set to arr, then trips arriving before the specified time at its destination stop are included. - * Works in conjunctions with the [itdDate] and [itdTime] values. - * - * "dep" or "arr" - */ - @Query("depArrMacro") depArrMacro: String, - - /** - * The reference date used when searching trips, in YYYYMMDD format. - * For instance, 20160901 refers to 1 September 2016. Works in conjunction with the - * [itdTime] and [depArrMacro] values. If not specified, the current server date is used. - * - * Optional, default to null if not provided - */ - @Query("itdDate") itdDate: String? = null, - - /** - * The reference time used when searching trips, in HHMM 24-hour format. - * For instance, 2215 refers to 10:15 PM. - * Works in conjunction with the [itdDate] and [depArrMacro] values. - * - * If not specified, the current server time is used. - * - * Optional, default to null if not provided - */ - @Query("itdTime") itdTime: String? = null, - - /** - * This is the type of data specified in the name_origin field. The origin indicates the - * starting point when searching for journeys. - * The best way to use the trip planner is to use use any for this field then specify a - * valid location ID in type_origin, or to use coord - * in this field and a correctly formatted coordinate in type_origin. - * - * Available values : any, coord - * - * Default value : any - */ - @Query("type_origin") typeOrigin: String = "any", - - /** - * This value is used to indicate the starting point when searching for journeys. - * This value can be one of three things: - * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be - * determined using stop_finder). - * - * A valid global stop ID (for example, 200060 indicates Central Station - this can be - * determined using stop_finder) Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 - * (Note that longitude is first). - * - * Default value : 10101331 - */ - @Query("name_origin") nameOrigin: String, - - /** - * This is the type of data specified in the name_destination field. The origin indicates - * the finishing point when searching for journeys. The best way to use the trip planner - * is to use use any for this field then specify a valid location ID in type_destination, - * or to use coord in this field and a correctly formatted coordinate in type_destination. - * - * Available values : any, coord - * - * Default value : any - */ - @Query("type_destination") typeDestination: String = "any", - - /** - * his value is used to indicate the finishing point when searching for journeys. - * This value can be one of three things: - * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be - * determined using [stopFinder]). - * - * A valid global stop ID (for example, 200060 indicates Central Station - this can be - * determined using [stopFinder]) - * Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). - * - * Default value : 10102027 - */ - @Query("name_destination") nameDestination: String, // Destination location or coordinates - - /** - * This parameter indicates the maximum number of trips to returned. Fewer trips may be returned anyway, - * depending on the available public transport services. - * - * Default value : 6 - */ - @Query("calcNumberOfTrips") calcNumberOfTrips: Int = 6, - - /** - * Including this parameter (regardless of its value) ensures that only wheelchair-accessible - * options are returned. - * - * Available values : on - */ - @Query("wheelchair") wheelchair: String? = null, // Optional, used for wheelchair accessible trips - - /** - * This parameter which means of transport to exclude from the trip plan. - * To exclude one means, - * select one of the following: 1 = train, 2 = metro, 4 = light rail, 5 = bus, 7 = coach, - * 9 = ferry, 11 = school bus. - * `checkbox` allows you to exclude more than one means of transport when used in conjunction - * with the exclMOT_ parameters. - * - * Available values : checkbox, 1, 2, 4, 5, 7, 9, 11 - */ - @Query("excludedMeans") excludedMeans: String? = null, // Optional, to exclude specific transport modes - - /** - * Excludes train services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_1") exclMOT1: String? = null, // Optional, to exclude trains - - /** - * Excludes metro services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_2") exclMOT2: String? = null, // Optional, to exclude metro - - /** - * Excludes light rail services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_4") exclMOT4: String? = null, // Optional, to exclude light rail - - /** - * Excludes bus services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_5") exclMOT5: String? = null, // Optional, to exclude bus - - /** - * Excludes coach services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_7") exclMOT7: String? = null, // Optional, to exclude bus - - /** - * Excludes ferry services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_9") exclMOT9: String? = null, // Optional, to exclude bus - - /** - * Excludes school bus services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_11") exclMOT11: String? = null, // Optional, to exclude bus - - /** - * Including this parameter enables a number of options that result in this API call - * operating in the same way as the Transport for NSW Trip Planner web site, - * including enabling real-time data. - * - * Available values : true - * - * Default value : true - */ - @Query("TfNSWTR") tfNSWTR: String = "true", // Enables real-time data - - /** - * Indicates which version of the API the caller is expecting for both request and response - * data. Note that if this version differs from the version listed above then the returned - * data may not be as expected. - * - * Default value : 10.2.1.42 - */ - @Query("version") version: String = "10.2.1.42", // API version - - /** - * This parameter activates the options for individual transport. If the parameter is - * disabled, the parameters concerning individual transport will not be taken into account. - * possible values are 0 and 1 - * - * Default value : 1 - */ - @Query("itOptionsActive") itOptionsActive: Int = 1, // Activates individual transport options - - /** - * Activates the calculation of a monomodal trip, i.e., a trip that takes place exclusively - * with the means of transport , e.g., with bicycle. - * - * Note 1: In order to use this parameter, the options for individual transport must be - * activated with itOptionsActive=1. - * - * Note 2: If no monomodal trip with the means of transport is calculated despite the - * parameter, the maximum time is often set too low. The parameter MaxITTime applies to - * all means of transport, the parameter MaxITTimeto the means of transport (e.g., MaxITTime107). - * These parameters are located in the [Parameters] section or are added to it. - * The configuration can be alternatively overridden by the [maxTime] parameter. - * - */ - @Query("computeMonomodalTripBicycle") computeMonomodalTripBicycle: Boolean = false, // Bike only trip - - @Query("cycleSpeed") cycleSpeed: Int = 16, // Cycling speed in km/h - - @Query("useElevationData") useElevationData: Int = 1, // Takes elevation data into account - - @Query("elevFac") elevFac: Int? = null, // Optional elevation factor - ): Response - - /** - * This endpoint returns a list of departures for a given location based on the date and time - * specified. This data can be used to display a "upcoming departures" board for a stop. - * - * Provides capability to provide NSW public transport departure information - * from a stop, station or wharf including real-time. - */ - @GET("v1/tp/departure_mon") - suspend fun departure() - - /** - * This endpoint returns a list of service alerts or additional information about travelling - * on the public transport network. This list can be filtered by date, route type, route, - * operator or stop. - * - * Provides capability to display all public transport service status and - * incident information (as published from the Incident Capture System). - */ - @GET("v1/tp/add_info") - suspend fun serviceAlert() - - /** - * When given a specific geographical location, this API finds public transport stops, - * stations, wharfs and points of interest around that location. - */ - @GET("v1/tp/coord") - suspend fun coordinateRequest() -} diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts new file mode 100644 index 00000000..93e709aa --- /dev/null +++ b/feature/trip-planner/network/build.gradle.kts @@ -0,0 +1,94 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +import com.codingfeline.buildkonfig.compiler.FieldSpec + +android { + namespace = "xyz.ksharma.krail.trip.planner.network" + + buildTypes { + debug {} + + release {} + } +} + +plugins { + alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.buildkonfig) +} + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + baseName = "network" + } + } + + sourceSets { + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + api(libs.di.koinAndroid) + } + + commonMain { + dependencies { + + implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.datetime) + implementation(compose.runtime) + implementation(libs.slf4j.simple) // Logging + + api(libs.di.koinComposeViewmodel) + } + } + + iosMain { + dependencies { + implementation(libs.ktor.client.darwin) + } + } + + commonTest { + dependencies { + implementation(libs.test.kotlin) + implementation(libs.test.turbine) + implementation(libs.test.kotlinxCoroutineTest) + } + } + } +} + +// READ API KEY +val localProperties = gradleLocalProperties(rootProject.rootDir, providers) +val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY") + ?: System.getenv("NSW_TRANSPORT_API_KEY") +require(nswTransportApiKey.isNotEmpty()) { + "Register API key and put in local.properties as `NSW_TRANSPORT_API_KEY`" +} +buildkonfig { + packageName = "xyz.ksharma.krail.trip.planner.network" + + require(nswTransportApiKey.isNotEmpty()) { + "Register API key and put in local.properties as `NSW_TRANSPORT_API_KEY`" + } + + defaultConfigs { + buildConfigField(FieldSpec.Type.STRING, "NSW_TRANSPORT_API_KEY", nswTransportApiKey) + } +} diff --git a/feature/trip-planner/network/real/build.gradle.kts b/feature/trip-planner/network/real/build.gradle.kts deleted file mode 100644 index e1ded5f3..00000000 --- a/feature/trip-planner/network/real/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) -} - -android { - namespace = "xyz.ksharma.krail.trip.planner.network.real" -} - -dependencies { - implementation(projects.core.coroutinesExt) - implementation(projects.feature.tripPlanner.network.api) - - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.retrofit) - - testImplementation(libs.test.junit) - testImplementation(libs.test.turbine) - testImplementation(libs.test.kotlin) - testImplementation(libs.test.kotlinxCoroutineTest) -} diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt b/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt deleted file mode 100644 index faa6bbe3..00000000 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.real.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository -import xyz.ksharma.krail.trip.planner.network.real.ratelimit.APIRateLimiter -import xyz.ksharma.krail.trip.planner.network.real.repository.RealTripPlanningRepository -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class TripPlanningModule { - - @Binds - @Singleton - abstract fun bindTripPlanningRepository(impl: RealTripPlanningRepository): TripPlanningRepository - - @Binds - // This clas should not be Singleton. It should be created per use-case. - abstract fun bindRateLimiter(impl: APIRateLimiter): RateLimiter -} diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt b/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt deleted file mode 100644 index fb05ec4b..00000000 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt +++ /dev/null @@ -1,50 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.real.repository - -import kotlinx.coroutines.CoroutineDispatcher -import xyz.ksharma.krail.coroutines.ext.suspendSafeResult -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher -import xyz.ksharma.krail.network.toSafeResult -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService -import javax.inject.Inject - -class RealTripPlanningRepository @Inject constructor( - private val tripPlanningService: TripPlanningService, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, -) : TripPlanningRepository { - - override suspend fun stopFinder( - stopType: StopType, - stopSearchQuery: String, - ): Result = suspendSafeResult(ioDispatcher) { - tripPlanningService.stopFinder( - typeSf = stopType.type, - nameSf = stopSearchQuery, - ).toSafeResult() - } - - override suspend fun trip( - originStopId: String, - destinationStopId: String, - journeyTime: String?, - ): Result = suspendSafeResult(ioDispatcher) { - tripPlanningService.trip( - depArrMacro = "dep", - nameOrigin = originStopId, - nameDestination = destinationStopId, - itdTime = journeyTime, - ).toSafeResult() - } -} - -/** Stop Ids - * Rockdale - 221620 - * Central - 200060 - * TownHall - 200070 - * SevenHills - 214710 - * NewTown - 204210 - */ diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt new file mode 100644 index 00000000..22ace500 --- /dev/null +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt @@ -0,0 +1,3 @@ +package xyz.ksharma.krail.trip.planner.network.api + +//actual fun createNetworkComponent(): NetworkComponent = NetworkComponent::class.create() diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..2d2bedb3 --- /dev/null +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,33 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import xyz.ksharma.krail.trip.planner.network.BuildKonfig + +actual fun httpClient(): HttpClient { + return HttpClient(OkHttp) { + expectSuccess = true + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + install(Logging) { +// if(debug) - TODO + level = LogLevel.BODY + } + + defaultRequest { + headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") + } + } +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt new file mode 100644 index 00000000..3e6ae788 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.trip.planner.network.api.di + +import io.ktor.client.HttpClient +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.service.RealTripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.httpClient + +val networkModule = module { + singleOf(::NetworkRateLimiter) { bind() } + single { httpClient() } + singleOf(::RealTripPlanningService) { bind() } +} diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt similarity index 81% rename from feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index b8403aa6..1c677f83 100644 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -1,5 +1,7 @@ -package xyz.ksharma.krail.trip.planner.network.real.ratelimit +package xyz.ksharma.krail.trip.planner.network.api.ratelimit +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -7,9 +9,6 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import timber.log.Timber -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter -import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -19,7 +18,7 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -class APIRateLimiter @Inject constructor() : RateLimiter { +class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) private val isFirstTime = MutableStateFlow(false) @@ -30,19 +29,20 @@ class APIRateLimiter @Inject constructor() : RateLimiter { * @param block A suspend function representing the API call to be rate-limited. * @return A Flow that emits the result of the API call. */ + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) override fun rateLimitFlow(block: suspend () -> T): Flow { return triggerFlow .debounce { // First time the block should be executed immediately and subsequent must be rate limited. val interval = if (isFirstTime.value) rateLimitInterval else 0.milliseconds - Timber.d("state: ${isFirstTime.value} and interval: $interval") + // Timber.d("state: ${isFirstTime.value} and interval: $interval") interval } .flatMapLatest { - Timber.d("flatmapLatest: Triggered") + //Timber.d("flatmapLatest: Triggered") isFirstTime.update { true } // Mark the first trigger flow { - Timber.d("Inside flow -emitting block") + // Timber.d("Inside flow -emitting block") emit(block()) } } diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt similarity index 92% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt index 17c172c5..b40d2302 100644 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.trip.planner.network.api +package xyz.ksharma.krail.trip.planner.network.api.ratelimit import kotlinx.coroutines.flow.Flow diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..8a2c4a0f --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,5 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient + +expect fun httpClient(): HttpClient diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt new file mode 100644 index 00000000..c10653ab --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt @@ -0,0 +1,60 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext +import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.StopFinderRequestParams +import xyz.ksharma.krail.trip.planner.network.api.service.trip.TripRequestParams + +class RealTripPlanningService(private val httpClient: HttpClient) : TripPlanningService { + + override suspend fun trip( + originStopId: String, + destinationStopId: String, + ): TripResponse = withContext(Dispatchers.IO) { + + httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { + url { + parameters.append(TripRequestParams.nameOrigin, originStopId) + parameters.append(TripRequestParams.nameDestination, destinationStopId) + + parameters.append(TripRequestParams.depArrMacro, "dep") + parameters.append(TripRequestParams.typeDestination, "any") + parameters.append(TripRequestParams.calcNumberOfTrips, "6") + parameters.append(TripRequestParams.typeOrigin, "any") + parameters.append(TripRequestParams.tfNSWTR, "true") + parameters.append(TripRequestParams.version, "10.2.1.42") + parameters.append(TripRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(TripRequestParams.itOptionsActive, "1") + parameters.append(TripRequestParams.computeMonomodalTripBicycle, "false") + parameters.append(TripRequestParams.cycleSpeed, "16") + parameters.append(TripRequestParams.useElevationData, "1") + parameters.append(TripRequestParams.outputFormat, "rapidJSON") + } + }.body() + } + + override suspend fun stopFinder( + stopSearchQuery: String, + stopType: StopType, + ): StopFinderResponse = withContext(Dispatchers.IO) { + httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { + url { + parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) + + parameters.append(StopFinderRequestParams.typeSf, stopType.type) + parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") +// parameters.append(StopFinderRequestParams.version, "10.2.1.42") + parameters.append(StopFinderRequestParams.tfNSWSF, "true") + } + }.body() + } + +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt new file mode 100644 index 00000000..ef0e037a --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt @@ -0,0 +1,49 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse + +/** + * Swagger: https://opendata.transport.nsw.gov.au/dataset/trip-planner-apis/resource/917c66c3-8123-4a0f-b1b1-b4220f32585d + */ +internal const val NSW_TRANSPORT_BASE_URL = "https://api.transport.nsw.gov.au" + +interface TripPlanningService { + + suspend fun trip(originStopId: String, destinationStopId: String): TripResponse + + suspend fun stopFinder( + stopSearchQuery: String, + stopType: StopType = StopType.STOP, + ): StopFinderResponse +} + + +/** + * This endpoint returns a list of departures for a given location based on the date and time + * specified. This data can be used to display a "upcoming departures" board for a stop. + * + * Provides capability to provide NSW public transport departure information + * from a stop, station or wharf including real-time. + */ +//("v1/tp/departure_mon") +//suspend fun departure() + +/** + * This endpoint returns a list of service alerts or additional information about travelling + * on the public transport network. This list can be filtered by date, route type, route, + * operator or stop. + * + * Provides capability to display all public transport service status and + * incident information (as published from the Incident Capture System). + */ +//"v1/tp/add_info") +//suspend fun serviceAlert() + +/** + * When given a specific geographical location, this API finds public transport stops, + * stations, wharfs and points of interest around that location. + */ +//"v1/tp/coord") +//suspend fun coordinateRequest() diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt new file mode 100644 index 00000000..9405efe3 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt @@ -0,0 +1,60 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.stop_finder + +internal object StopFinderRequestParams { + + /** + * Used to set the response data type. This documentation only covers responses that use the JSON format. + * Setting the outputFormat value to rapidJSON is required to enable JSON output. + * + * Available values : rapidJSON + */ + const val outputFormat: String = "outputFormat" + + /** + * This specifies the type of results expected in the list of returned stops. + * By specifying `any`, locations of all types can be returned. + * If you specifically know that you're searching using a coord, specify `coord`. + * Likewise, if you're using a stop ID or global stop ID as an input, use `stop` + * for more accurate results. + * + * Available values : any, coord, poi, stop + */ + const val typeSf: String = "type_sf" + + /** + * This is the search term that will be used to find locations. + * To lookup a coordinate, set type_sf to coord, and use the following format: + * LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). For example, 151.206290:-33.884080:EPSG:4326. + * To lookup a stop set type_sf to stop and enter the stop id or global stop ID. For example, 10101100 + * + * Default value : Circular Quay + */ + const val nameSf: String = "name_sf" + + /** + * This specifies the format the coordinates are returned in. + * While other variations are available, the EPSG:4326 format will return the widely-used format. + * + * Available values : EPSG:4326 + */ + const val coordOutputFormat = "coordOutputFormat" // "EPSG:4326", + + /** + * Including this parameter enables a number of options that result in the stop finder + * operating in the same way as the Transport for NSW Trip Planner web site. + * + * Available values : true + * + * Default value : true + */ + const val tfNSWSF = "TfNSWSF" + + /** + * Indicates which version of the API the caller is expecting for both request and response + * data. Note that if this version differs + * from the version listed above then the returned data may not be as expected. + * + * Default value : 10.2.1.42 + */ + const val version = "version" +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt new file mode 100644 index 00000000..d8d102bd --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt @@ -0,0 +1,237 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.trip + +internal object TripRequestParams { + + /** + * Used to set the response data type. This documentation only covers responses that use + * the JSON format. Setting the outputFormat value to rapidJSON is required to enable JSON output. + */ + const val outputFormat: String = "outputFormat" + + /** + * This specifies the format the coordinates are returned in. While other variations are + * available, the EPSG:4326 format will return the widely-used format. + */ + const val coordOutputFormat: String = "coordOutputFormat" + + /** + * This value anchors the requested date time. If set to dep, then trips departing after + * the specified date/time at the specified location are included. + * + * If set to arr, then trips arriving before the specified time at its destination stop are included. + * Works in conjunctions with the [itdDate] and [itdTime] values. + * + * "dep" or "arr" + */ + const val depArrMacro: String = "depArrMacro" + + /** + * The reference date used when searching trips, in YYYYMMDD format. + * For instance, 20160901 refers to 1 September 2016. Works in conjunction with the + * [itdTime] and [depArrMacro] values. If not specified, the current server date is used. + * + * Optional, default to null if not provided + */ + const val itdDate = "itdDate" + + /** + * The reference time used when searching trips, in HHMM 24-hour format. + * For instance, 2215 refers to 10:15 PM. + * Works in conjunction with the [itdDate] and [depArrMacro] values. + * + * If not specified, the current server time is used. + * + * Optional, default to null if not provided + */ + const val itdTime = "itdTime" + + /** + * This is the type of data specified in the name_origin field. The origin indicates the + * starting point when searching for journeys. + * The best way to use the trip planner is to use use any for this field then specify a + * valid location ID in type_origin, or to use coord + * in this field and a correctly formatted coordinate in type_origin. + * + * Available values : any, coord + * + * Default value : any + */ + const val typeOrigin: String = "type_origin" + + /** + * This value is used to indicate the starting point when searching for journeys. + * This value can be one of three things: + * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be + * determined using stop_finder). + * + * A valid global stop ID (for example, 200060 indicates Central Station - this can be + * determined using stop_finder) Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 + * (Note that longitude is first). + * + * Default value : 10101331 + */ + const val nameOrigin = "name_origin" + + /** + * This is the type of data specified in the name_destination field. The origin indicates + * the finishing point when searching for journeys. The best way to use the trip planner + * is to use use any for this field then specify a valid location ID in type_destination, + * or to use coord in this field and a correctly formatted coordinate in type_destination. + * + * Available values : any, coord + * + * Default value : any + */ + const val typeDestination: String = "type_destination" + + /** + * his value is used to indicate the finishing point when searching for journeys. + * This value can be one of three things: + * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be + * determined using [stopFinder]). + * + * A valid global stop ID (for example, 200060 indicates Central Station - this can be + * determined using [stopFinder]) + * Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). + * + * Default value : 10102027 + */ + const val nameDestination = "name_destination" + + /** + * This parameter indicates the maximum number of trips to returned. Fewer trips may be returned anyway, + * depending on the available public transport services. + * + * Default value : 6 + */ + const val calcNumberOfTrips = "calcNumberOfTrips" + + /** + * Including this parameter (regardless of its value) ensures that only wheelchair-accessible + * options are returned. + * + * Available values : on + */ + const val wheelchair: String = "wheelchair"// Optional, used for wheelchair accessible trips + + /** + * This parameter which means of transport to exclude from the trip plan. + * To exclude one means, + * select one of the following: 1 = train, 2 = metro, 4 = light rail, 5 = bus, 7 = coach, + * 9 = ferry, 11 = school bus. + * `checkbox` allows you to exclude more than one means of transport when used in conjunction + * with the exclMOT_ parameters. + * + * Available values : checkbox, 1, 2, 4, 5, 7, 9, 11 + */ + const val excludedMeans: String = + "excludedMeans" // Optional, to exclude specific transport modes + + /** + * Excludes train services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT1 = "exclMOT_1" // Optional, to exclude trains + + /** + * Excludes metro services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT2 = "exclMOT_2" // Optional, to exclude metro + + /** + * Excludes light rail services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT4 = "exclMOT_4" // Optional, to exclude light rail + + /** + * Excludes bus services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT5 = "exclMOT_5" // Optional, to exclude bus + + + /** + * Excludes coach services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT7: String = "exclMOT_7" // Optional, to exclude bus + + /** + * Excludes ferry services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT9 = "exclMOT_9"// Optional, to exclude bus + + /** + * Excludes school bus services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT11 = "exclMOT_11" + + /** + * Including this parameter enables a number of options that result in this API call + * operating in the same way as the Transport for NSW Trip Planner web site, + * including enabling real-time data. + * + * Available values : true + * + * Default value : true + */ + const val tfNSWTR = "TfNSWTR" + + /** + * Indicates which version of the API the caller is expecting for both request and response + * data. Note that if this version differs from the version listed above then the returned + * data may not be as expected. + * + * Default value : 10.2.1.42 + */ + const val version = "version" + + /** + * This parameter activates the options for individual transport. If the parameter is + * disabled, the parameters concerning individual transport will not be taken into account. + * possible values are 0 and 1 + * + * Default value : 1 + */ + const val itOptionsActive = "itOptionsActive" // Activates individual transport options + + /** + * Activates the calculation of a monomodal trip, i.e., a trip that takes place exclusively + * with the means of transport , e.g., with bicycle. + * + * Note 1: In order to use this parameter, the options for individual transport must be + * activated with itOptionsActive=1. + * + * Note 2: If no monomodal trip with the means of transport is calculated despite the + * parameter, the maximum time is often set too low. The parameter MaxITTime applies to + * all means of transport, the parameter MaxITTimeto the means of transport (e.g., MaxITTime107). + * These parameters are located in the [Parameters] section or are added to it. + * The configuration can be alternatively overridden by the [maxTime] parameter. + * + */ + const val computeMonomodalTripBicycle = "computeMonomodalTripBicycle" + + const val cycleSpeed = "cycleSpeed" + + const val useElevationData = "useElevationData" + + const val elevFac = "elevFac" +} diff --git a/feature/trip-planner/network/api/src/test/assets/hige_multiple_legs_trip.json b/feature/trip-planner/network/src/commonTest/assets/hige_multiple_legs_trip.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/hige_multiple_legs_trip.json rename to feature/trip-planner/network/src/commonTest/assets/hige_multiple_legs_trip.json diff --git a/feature/trip-planner/network/api/src/test/assets/multiple_legs b/feature/trip-planner/network/src/commonTest/assets/multiple_legs similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/multiple_legs rename to feature/trip-planner/network/src/commonTest/assets/multiple_legs diff --git a/feature/trip-planner/network/api/src/test/assets/stop_finder_any.json b/feature/trip-planner/network/src/commonTest/assets/stop_finder_any.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/stop_finder_any.json rename to feature/trip-planner/network/src/commonTest/assets/stop_finder_any.json diff --git a/feature/trip-planner/network/api/src/test/assets/stop_finder_stop.json b/feature/trip-planner/network/src/commonTest/assets/stop_finder_stop.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/stop_finder_stop.json rename to feature/trip-planner/network/src/commonTest/assets/stop_finder_stop.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip.json b/feature/trip-planner/network/src/commonTest/assets/trip.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip.json rename to feature/trip-planner/network/src/commonTest/assets/trip.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_metro_bus.json b/feature/trip-planner/network/src/commonTest/assets/trip_metro_bus.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_metro_bus.json rename to feature/trip-planner/network/src/commonTest/assets/trip_metro_bus.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_occupancy_platform.json b/feature/trip-planner/network/src/commonTest/assets/trip_occupancy_platform.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_occupancy_platform.json rename to feature/trip-planner/network/src/commonTest/assets/trip_occupancy_platform.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_sevenhills_townhall.json b/feature/trip-planner/network/src/commonTest/assets/trip_sevenhills_townhall.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_sevenhills_townhall.json rename to feature/trip-planner/network/src/commonTest/assets/trip_sevenhills_townhall.json diff --git a/feature/trip-planner/network/real/src/test/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt b/feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt similarity index 94% rename from feature/trip-planner/network/real/src/test/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt rename to feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt index 64292b24..1e0215ea 100644 --- a/feature/trip-planner/network/real/src/test/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt +++ b/feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.trip.planner.network.real.ratelimit +package xyz.ksharma.krail.trip.planner.network.api.ratelimit import app.cash.turbine.test import kotlinx.coroutines.delay @@ -9,7 +9,7 @@ import kotlin.time.Duration.Companion.seconds class APIRateLimiterTest { - private val rateLimiter = APIRateLimiter() + private val rateLimiter = NetworkRateLimiter() @Test fun `Given rate limiter When triggered once Then should emit only once within the time interval`() = runTest { diff --git a/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt new file mode 100644 index 00000000..22ace500 --- /dev/null +++ b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt @@ -0,0 +1,3 @@ +package xyz.ksharma.krail.trip.planner.network.api + +//actual fun createNetworkComponent(): NetworkComponent = NetworkComponent::class.create() diff --git a/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..eaa44398 --- /dev/null +++ b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,33 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.engine.darwin.Darwin +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import xyz.ksharma.krail.trip.planner.network.BuildKonfig + +actual fun httpClient(): HttpClient { + return HttpClient(Darwin) { + expectSuccess = true + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + install(Logging) { +// if(debug) - TODO + level = LogLevel.BODY + } + + defaultRequest { + headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") + } + } +} diff --git a/feature/trip-planner/state/build.gradle.kts b/feature/trip-planner/state/build.gradle.kts index 4c63505b..6ea023c0 100644 --- a/feature/trip-planner/state/build.gradle.kts +++ b/feature/trip-planner/state/build.gradle.kts @@ -1,13 +1,37 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) } android { namespace = "xyz.ksharma.krail.trip.planner.state" } -dependencies { - implementation(libs.kotlinx.collections.immutable) - implementation(libs.kotlinx.serialization.json) +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.collections.immutable) + implementation(libs.kotlinx.serialization.json) + + implementation(compose.runtime) + } + } + } } diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt similarity index 96% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt index a6ff02b8..fbc7b7b1 100644 --- a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt +++ b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt @@ -8,7 +8,7 @@ data class ServiceAlert( val heading: String, val message: String, val priority: ServiceAlertPriority, -) : java.io.Serializable { +) { fun toJsonString() = Json.encodeToString(serializer(), this) diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt similarity index 95% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt index d90b3b2e..2c8a9a74 100644 --- a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt +++ b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt @@ -4,7 +4,6 @@ import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.serialization.json.Json import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -import java.io.Serializable /** * Represents a Stop item in the search results when searching for stops. @@ -15,7 +14,7 @@ data class StopItem( val stopName: String, val transportModes: ImmutableSet = persistentSetOf(), val stopId: String, -) : Serializable { +) { fun toJsonString() = Json.encodeToString(serializer(), this) @Suppress("ConstPropertyName") diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 347729bb..3eaab54a 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -1,31 +1,58 @@ plugins { - alias(libs.plugins.krail.android.library.compose) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) } -android { - namespace = "xyz.ksharma.krail.trip.planner.ui" +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(projects.taj) + implementation(projects.feature.tripPlanner.state) + implementation(projects.core.dateTime) + implementation(projects.sandook) + implementation(projects.feature.tripPlanner.network) + + implementation(compose.foundation) + implementation(compose.animation) + implementation(compose.ui) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.collections.immutable) + implementation(libs.kotlinx.serialization.json) + implementation(libs.navigation.compose) + implementation(libs.lifecycle.viewmodel.compose) + api(libs.di.koinComposeViewmodel) + } + } + commonTest { + dependencies { + implementation(libs.test.kotlin) + } + } + } } -dependencies { - implementation(projects.core.dateTime) - implementation(projects.core.designSystem) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.state) - implementation(projects.sandook.api) - - implementation(libs.compose.ui) - implementation(libs.compose.foundation) - implementation(libs.compose.navigation) - implementation(libs.hilt.navigation.compose) - implementation(libs.kotlinx.collections.immutable) - implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) - implementation(libs.kotlinx.datetime) - implementation(projects.sandook.real) - implementation(libs.compose.material3) - - testImplementation(libs.test.composeUiTestJunit4) - testImplementation(libs.test.kotlin) +android { + namespace = "xyz.ksharma.krail.trip.planner.ui" } diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_a11y.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_a11y.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_a11y.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_a11y.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_alert.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_alert.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_alert.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_alert.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_arrow_down.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_down.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_arrow_down.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_down.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_arrow_right.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_right.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_arrow_right.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_right.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_clock.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_clock.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_clock.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_clock.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_loc.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_loc.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_loc.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_loc.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_location.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_location.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_location.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_location.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_mode_ferry.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_mode_ferry.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_mode_ferry.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_mode_ferry.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_reverse.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_reverse.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_reverse.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_reverse.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_search.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_search.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_search.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_search.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_star.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_star.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_star_filled.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star_filled.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_star_filled.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star_filled.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_walk.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_walk.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_walk.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_walk.xml diff --git a/feature/trip-planner/ui/src/main/res/values/strings.xml b/feature/trip-planner/ui/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/values/strings.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/values/strings.xml diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt similarity index 90% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt index cb096bdd..b073d7de 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt @@ -1,9 +1,6 @@ package xyz.ksharma.krail.trip.planner.ui +/* -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.graphics.Color import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge @@ -39,3 +36,4 @@ internal fun DefaultSystemBarColors() { onDispose {} } } +*/ diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt similarity index 68% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt index cc8e7c8a..011554d2 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt @@ -1,12 +1,10 @@ package xyz.ksharma.krail.trip.planner.ui.alerts -import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute import kotlinx.collections.immutable.toImmutableSet -import timber.log.Timber import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert @@ -18,15 +16,6 @@ internal fun NavGraphBuilder.alertsDestination(navController: NavHostController) ServiceAlert.fromJsonString(alertJson) }.toImmutableSet() - LaunchedEffect(route.alertsJsonList) { - route.alertsJsonList.forEach { - ServiceAlert.fromJsonString(it)?.let { alert -> - // Timber.d("Alert Heading: ${alert.heading}") - Timber.d("Alert Message: ${alert.message}") - } - } - } - ServiceAlertScreen(serviceAlerts = serviceAlerts, onBackClick = { navController.popBackStack() }) diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt similarity index 91% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt index a99bab90..145c1ccd 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt @@ -21,13 +21,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveSize +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveSize import xyz.ksharma.krail.trip.planner.ui.components.themeBackgroundColor import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert @@ -91,7 +89,8 @@ fun CollapsibleAlert( ) } if (isHtml) { - HtmlText(serviceAlert.message, onClick = onClick) + // TODO - Html Text Component + Text(text = serviceAlert.message) // , onClick = onClick } else { Text( text = serviceAlert.message, @@ -105,8 +104,8 @@ fun CollapsibleAlert( // region Previews -@PreviewLightDark -@Preview(fontScale = 2f) + +//@Preview(fontScale = 2f) @Composable private fun PreviewCollapsibleAlertCollapsed() { KrailTheme { @@ -126,7 +125,7 @@ private fun PreviewCollapsibleAlertCollapsed() { } } -@PreviewLightDark + @Composable private fun PreviewCollapsibleAlertExpanded() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt similarity index 97% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt index 7dea84c5..dbdcf5d2 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt @@ -1,4 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.alerts +/* import android.graphics.Typeface import android.text.method.LinkMovementMethod @@ -14,11 +15,13 @@ import androidx.compose.ui.text.font.FontSynthesis import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.HtmlCompat -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.KrailTheme +*/ /** * Reference - https://developer.android.com/codelabs/jetpack-compose-migration#8 - */ + *//* + @Composable fun HtmlText(html: String, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { // Remembers the HTML formatted description. Re-executes on a new description @@ -61,3 +64,4 @@ fun HtmlText(html: String, modifier: Modifier = Modifier, onClick: () -> Unit = modifier = modifier, ) } +*/ diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt index 44efaf31..5127b987 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt @@ -20,15 +20,13 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.DefaultSystemBarColors +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlertPriority import xyz.ksharma.krail.trip.planner.ui.timetable.ActionButton @@ -39,7 +37,7 @@ fun ServiceAlertScreen( modifier: Modifier = Modifier, onBackClick: () -> Unit = {}, ) { - DefaultSystemBarColors() +// DefaultSystemBarColors() Column( modifier = modifier @@ -95,7 +93,7 @@ fun ServiceAlertScreen( } } -@Preview +//@Preview @Composable private fun PreviewServiceAlertScreen() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt similarity index 52% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt index 996c1460..8609d4bc 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt @@ -5,26 +5,38 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.trip.planner.ui.state.TransportMode +import kotlin.math.absoluteValue -/** - * Converts a hexadecimal color string to a Compose Color object. - * - * This function takes a string representing a hexadecimal color code - * (e.g., "#FF0000" for red) and attempts to convert it into a Compose Color - * object. - * - * @throws IllegalArgumentException if the provided string is not a valid - * hexadecimal color code. A valid code must start with "#" followed by - * either 6 or 8 hexadecimal digits (0-9, A-F, a-f). - * - * @return A Compose Color object representing the provided hex color code. - */ fun String.hexToComposeColor(): Color { - require(this.isValidHexColorCode()) { "Invalid hex color code: $this" } - return Color(android.graphics.Color.parseColor(this)) + require(isValidHexColorCode()) { + "Invalid hex color code: $this. Hex color codes must be in the format #RRGGBB or #AARRGGBB." + } + + // Remove the leading '#' if present + val hex = removePrefix("#") + + // Parse the hex value + return when (hex.length) { + 6 -> { + // If the string is in the format RRGGBB, add full opacity (FF) at the start + val r = hex.substring(0, 2).toInt(16) + val g = hex.substring(2, 4).toInt(16) + val b = hex.substring(4, 6).toInt(16) + Color(red = r / 255f, green = g / 255f, blue = b / 255f) + } + 8 -> { + // If the string is in the format AARRGGBB + val a = hex.substring(0, 2).toInt(16) + val r = hex.substring(2, 4).toInt(16) + val g = hex.substring(4, 6).toInt(16) + val b = hex.substring(6, 8).toInt(16) + Color(alpha = a / 255f, red = r / 255f, green = g / 255f, blue = b / 255f) + } + else -> throw IllegalArgumentException("Invalid hex color format. Use #RRGGBB or #AARRGGBB.") + } } /** @@ -80,26 +92,17 @@ internal fun themeContentColor(): Color { * * @return A string representing the hexadecimal color code (e.g., "#FF0000" for red). */ -@Suppress("ImplicitDefaultLocale") fun Color.toHex(): String { val red = (this.red * 255).toInt() val green = (this.green * 255).toInt() val blue = (this.blue * 255).toInt() val alpha = (this.alpha * 255).toInt() - return String.format("#%02X%02X%02X%02X", alpha, red, green, blue) + return "#${alpha.toHex()}${red.toHex()}${green.toHex()}${blue.toHex()}" } -/** - * The default light scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598 - */ -val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF) - -/** - * The default dark scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598 - */ -val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) +private fun Int.toHex(): String { + return this.toString(16).padStart(2, '0').uppercase() +} /** * Update the theme color to make text more readable on top of it. @@ -115,26 +118,63 @@ val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) * * @return The brightened color in ARGB format */ -private fun brightenColor(color: Int, factor: Float = 0.2f): Int { - // Convert the color to RGB components - val red = android.graphics.Color.red(color) / 255f - val green = android.graphics.Color.green(color) / 255f - val blue = android.graphics.Color.blue(color) / 255f - - // Convert RGB to HSL - val hsl = FloatArray(3) - android.graphics.Color.RGBToHSV( - (red * 255).toInt(), - (green * 255).toInt(), - (blue * 255).toInt(), - hsl, - ) - - // Adjust lightness (value) within bounds - hsl[2] = (hsl[2] + factor).coerceIn(0f, 1f) - - // Convert back to RGB - return android.graphics.Color.HSVToColor(hsl) +fun brightenColor(color: Int, factor: Float = 0.2f): Int { + // Extract RGB components from the color + val red = (color shr 16 and 0xFF) / 255f + val green = (color shr 8 and 0xFF) / 255f + val blue = (color and 0xFF) / 255f + + // Convert RGB to HSV + val hsv = rgbToHsv(red, green, blue) + + // Adjust brightness (value) within bounds + hsv[2] = (hsv[2] + factor).coerceIn(0f, 1f) + + // Convert back to RGB and return the color as an Int + return hsvToColor(hsv) +} + +private fun rgbToHsv(r: Float, g: Float, b: Float): FloatArray { + val max = maxOf(r, g, b) + val min = minOf(r, g, b) + val delta = max - min + + val h: Float = when { + delta == 0f -> 0f + max == r -> ((g - b) / delta + (if (g < b) 6 else 0)) % 6 + max == g -> (b - r) / delta + 2 + else -> (r - g) / delta + 4 + } * 60 + + val s: Float = if (max == 0f) 0f else delta / max + val v: Float = max + + return floatArrayOf(h, s, v) +} + +private fun hsvToColor(hsv: FloatArray): Int { + val h = hsv[0] + val s = hsv[1] + val v = hsv[2] + + val c = v * s + val x = c * (1 - ((h / 60) % 2 - 1).absoluteValue) + val m = v - c + + val (r, g, b) = when { + h < 60 -> Triple(c, x, 0f) + h < 120 -> Triple(x, c, 0f) + h < 180 -> Triple(0f, c, x) + h < 240 -> Triple(0f, x, c) + h < 300 -> Triple(x, 0f, c) + else -> Triple(c, 0f, x) + } + + val red = ((r + m) * 255).toInt() + val green = ((g + m) * 255).toInt() + val blue = ((b + m) * 255).toInt() + + return (255 shl 24) or (red shl 16) or (green shl 8) or blue } /** @@ -152,27 +192,20 @@ private fun Color.brighten(factor: Float = 0.2f): Color { val brightenedArgb = brightenColor(argb, factor) return Color(brightenedArgb) } +fun darkenColor(color: Int, factor: Float = 0.2f): Int { + // Extract RGB components from the color + val red = (color shr 16 and 0xFF) / 255f + val green = (color shr 8 and 0xFF) / 255f + val blue = (color and 0xFF) / 255f + + // Convert RGB to HSV + val hsv = rgbToHsv(red, green, blue) + + // Adjust brightness (value) within bounds + hsv[2] = (hsv[2] - factor).coerceIn(0f, 1f) -private fun darkenColor(color: Int, factor: Float = 0.2f): Int { - // Convert the color to RGB components - val red = android.graphics.Color.red(color) / 255f - val green = android.graphics.Color.green(color) / 255f - val blue = android.graphics.Color.blue(color) / 255f - - // Convert RGB to HSL - val hsl = FloatArray(3) - android.graphics.Color.RGBToHSV( - (red * 255).toInt(), - (green * 255).toInt(), - (blue * 255).toInt(), - hsl, - ) - - // Adjust lightness (value) within bounds - hsl[2] = (hsl[2] - factor).coerceIn(0f, 1f) - - // Convert back to RGB - return android.graphics.Color.HSVToColor(hsl) + // Convert back to RGB and return the color as an Int + return hsvToColor(hsv) } private fun Color.darken(factor: Float = 0.2f): Color { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt similarity index 92% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt index 537f2f4b..57f76067 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt @@ -13,12 +13,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode @Composable @@ -83,7 +82,7 @@ data class ActionData( // region Preview -@PreviewLightDark + @Composable private fun PreviewErrorMessage() { val themeColor = remember { mutableStateOf(TransportMode.Ferry().colorCode) } diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt index 977756a6..b8b812b7 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -31,29 +33,31 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.SeparatorIcon -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.getForegroundColor -import xyz.ksharma.krail.design.system.toAdaptiveDecorativeIconSize -import xyz.ksharma.krail.design.system.toAdaptiveSize -import xyz.ksharma.krail.trip.planner.ui.R +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_a11y +import krail.feature.trip_planner.ui.generated.resources.ic_alert +import krail.feature.trip_planner.ui.generated.resources.ic_clock +import krail.feature.trip_planner.ui.generated.resources.ic_walk +import org.jetbrains.compose.resources.painterResource +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.SeparatorIcon +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.getForegroundColor +import xyz.ksharma.krail.taj.toAdaptiveDecorativeIconSize +import xyz.ksharma.krail.taj.toAdaptiveSize import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState @@ -196,8 +200,6 @@ fun ExpandedJourneyCardContent( onAlertClick: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current - Column(modifier = modifier) { FlowRow( modifier = Modifier @@ -232,12 +234,12 @@ fun ExpandedJourneyCardContent( ) { if (totalUniqueServiceAlerts > 0) { SmallButton( - icon = R.drawable.ic_alert, - text = context.resources.getQuantityString( - R.plurals.alerts, - totalUniqueServiceAlerts, - totalUniqueServiceAlerts, - ), + painter = painterResource(Res.drawable.ic_alert), + text = if (totalUniqueServiceAlerts > 1) { + "$totalUniqueServiceAlerts Alerts" + } else { + "$totalUniqueServiceAlerts Alert" + }, color = getForegroundColor(KrailTheme.colors.alert), iconSize = iconSize, onClick = onAlertClick, @@ -246,7 +248,7 @@ fun ExpandedJourneyCardContent( } TextWithIcon( - icon = R.drawable.ic_clock, + painter = painterResource(Res.drawable.ic_clock), text = totalTravelTime, textStyle = KrailTheme.typography.bodyLarge, iconSize = iconSize, @@ -366,7 +368,7 @@ fun DefaultJourneyCardContent( } } - platformNumber?.let { platform -> // todo - extract + platformNumber?.let { platform -> Box( modifier = Modifier .padding(start = 8.dp) @@ -380,7 +382,7 @@ fun DefaultJourneyCardContent( contentAlignment = Alignment.Center, ) { Text( - text = platform.toString(), + text = platform, textAlign = TextAlign.Center, style = KrailTheme.typography.labelLarge, ) @@ -409,7 +411,7 @@ fun DefaultJourneyCardContent( modifier = Modifier.padding(end = 10.dp), ) TextWithIcon( - icon = R.drawable.ic_clock, + painter = painterResource(Res.drawable.ic_clock), text = totalTravelTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -417,7 +419,7 @@ fun DefaultJourneyCardContent( ) totalWalkTime?.let { TextWithIcon( - icon = R.drawable.ic_walk, + painter = painterResource(Res.drawable.ic_walk), text = totalWalkTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -427,7 +429,7 @@ fun DefaultJourneyCardContent( Spacer(modifier = Modifier.weight(1f)) if (isWheelchairAccessible) { Image( - painter = painterResource(R.drawable.ic_a11y), + painter = painterResource(Res.drawable.ic_a11y), contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -441,7 +443,7 @@ fun DefaultJourneyCardContent( @Composable private fun TextWithIcon( - icon: Int, + painter: Painter, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -456,7 +458,7 @@ private fun TextWithIcon( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - painter = painterResource(icon), + painter = painter, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -473,7 +475,7 @@ private fun TextWithIcon( @Composable private fun SmallButton( - icon: Int, + painter: Painter, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -503,7 +505,7 @@ private fun SmallButton( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - painter = painterResource(icon), + painter = painter, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -530,8 +532,8 @@ internal fun List?.toColors(onSurface: Color): List = when // region Previews -@PreviewLightDark -@Preview(fontScale = 2f) + +//@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCard() { KrailTheme { @@ -556,8 +558,8 @@ private fun PreviewJourneyCard() { } } -@PreviewLightDark -@Preview(fontScale = 2f) + +//@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardCollapsed() { KrailTheme { @@ -596,7 +598,7 @@ private fun PreviewJourneyCardCollapsed() { ), ), - ), + ), cardState = JourneyCardState.EXPANDED, totalWalkTime = "10 mins", totalUniqueServiceAlerts = 1, @@ -605,8 +607,6 @@ private fun PreviewJourneyCardCollapsed() { } } -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardExpanded() { KrailTheme { @@ -671,8 +671,6 @@ private val PREVIEW_STOPS = persistentListOf( ), ) -@Preview -@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardLongData() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt index d6708741..a9e98e6a 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt @@ -28,24 +28,23 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveDecorativeIconSize -import xyz.ksharma.krail.trip.planner.ui.R +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_a11y +import krail.feature.trip_planner.ui.generated.resources.ic_clock +import org.jetbrains.compose.resources.painterResource +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveDecorativeIconSize import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState @@ -120,14 +119,8 @@ fun LegView( .padding(start = 16.dp, top = 12.dp), ) { if (stops.size > 2) { - val context = LocalContext.current StopsRow( - // Need to pass count twice - https://developer.android.com/guide/topics/resources/string-resource#Plurals - stops = context.resources.getQuantityString( - R.plurals.stops, - stops.size - 2, - stops.size - 2, - ), + stops = if (stops.size == 1) "${stops.size} stop" else "${stops.size} stops", line = transportModeLine, ) } else { @@ -208,7 +201,7 @@ private fun RouteSummary( if (displayDuration) { Row(horizontalArrangement = Arrangement.End) { Image( - painter = painterResource(R.drawable.ic_clock), + painter = painterResource(Res.drawable.ic_clock), contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -253,7 +246,7 @@ private fun StopInfo( ) if (isWheelchairAccessible) { Image( - painter = painterResource(R.drawable.ic_a11y), + painter = painterResource(Res.drawable.ic_a11y), contentDescription = null, colorFilter = ColorFilter.tint( color = if (isProminent) { @@ -309,8 +302,6 @@ private fun StopsRow(stops: String, line: TransportModeLine, modifier: Modifier // region Previews -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewLegView() { KrailTheme { @@ -344,8 +335,6 @@ private fun PreviewLegView() { } } -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewLegViewTwoStops() { KrailTheme { @@ -374,7 +363,6 @@ private fun PreviewLegViewTwoStops() { } } -@PreviewLightDark @Composable private fun PreviewLegViewMetro() { KrailTheme { @@ -403,7 +391,6 @@ private fun PreviewLegViewMetro() { } } -@PreviewLightDark @Composable private fun PreviewLegViewFerry() { KrailTheme { @@ -432,7 +419,6 @@ private fun PreviewLegViewFerry() { } } -@PreviewLightDark @Composable private fun PreviewLegViewLightRail() { KrailTheme { @@ -461,7 +447,6 @@ private fun PreviewLegViewLightRail() { } } -@PreviewLightDark @Composable private fun PreviewStopsRow() { KrailTheme { @@ -475,7 +460,6 @@ private fun PreviewStopsRow() { } } -@PreviewLightDark @Composable private fun PreviewProminentStopInfo() { KrailTheme { @@ -489,8 +473,6 @@ private fun PreviewProminentStopInfo() { } } -@Preview -@Preview(fontScale = 2f) @Composable private fun PreviewRouteSummary() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt similarity index 97% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt index c2c4494c..f7cdd1be 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt @@ -19,8 +19,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip @Composable diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt similarity index 91% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt index 58afffb3..683498c4 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -21,19 +23,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_star_filled +import org.jetbrains.compose.resources.painterResource +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import xyz.ksharma.krail.design.system.R as DSR @Composable fun SavedTripCard( @@ -92,7 +92,7 @@ fun SavedTripCard( contentAlignment = Alignment.Center, ) { Image( - imageVector = ImageVector.vectorResource(DSR.drawable.star_filled), + painter = painterResource(Res.drawable.ic_star_filled), contentDescription = "Save Trip", colorFilter = ColorFilter.tint( primaryTransportMode?.colorCode @@ -105,7 +105,6 @@ fun SavedTripCard( // region Previews -@PreviewComponent @Composable private fun SavedTripCardPreview() { KrailTheme { @@ -127,7 +126,6 @@ private fun SavedTripCardPreview() { } } -@PreviewLightDark @Composable private fun SavedTripCardListPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt similarity index 78% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt index d73f9866..da6f6441 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt @@ -10,6 +10,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -17,21 +20,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalOnContentColor -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.RoundIconButton -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TextFieldButton -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import krail.feature.trip_planner.ui.generated.resources.ic_search +import org.jetbrains.compose.resources.painterResource +import xyz.ksharma.krail.taj.LocalOnContentColor +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.RoundIconButton +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TextFieldButton +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem -import xyz.ksharma.krail.trip.planner.ui.R as TripPlannerUiR @Composable fun SearchStopRow( @@ -69,14 +71,14 @@ fun SearchStopRow( TextFieldButton(onClick = fromButtonClick) { Text( text = fromStopItem?.stopName - ?: stringResource(TripPlannerUiR.string.from_text_field_placeholder), + ?: "Where from", maxLines = 1, ) } TextFieldButton(onClick = toButtonClick) { Text( text = toStopItem?.stopName - ?: stringResource(TripPlannerUiR.string.to_text_field_placeholder), + ?: "Where to", maxLines = 1, ) } @@ -90,7 +92,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = ImageVector.vectorResource(TripPlannerUiR.drawable.ic_reverse), + painter = painterResource(Res.drawable.ic_reverse), contentDescription = "Reverse", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) @@ -101,7 +103,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = ImageVector.vectorResource(TripPlannerUiR.drawable.ic_search), + painter = painterResource(Res.drawable.ic_search), contentDescription = "Search", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) @@ -114,7 +116,6 @@ fun SearchStopRow( // region Previews -@PreviewComponent @Composable private fun SearchStopColumnPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt similarity index 92% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt index 88997717..93c44b0b 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt @@ -11,13 +11,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem @@ -64,7 +62,6 @@ fun StopSearchListItem( // region Preview -@PreviewComponent @Composable private fun StopSearchListItemPreview() { KrailTheme { @@ -81,7 +78,6 @@ private fun StopSearchListItemPreview() { } } -@Preview @Composable private fun StopSearchListItemLongNamePreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt similarity index 90% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt index 2e32e30d..a6f90458 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt @@ -15,9 +15,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeBadge( @@ -48,7 +47,6 @@ fun TransportModeBadge( // region Previews -@PreviewComponent @Composable private fun TransportModeBadgeBusPreview() { KrailTheme { @@ -59,7 +57,6 @@ private fun TransportModeBadgeBusPreview() { } } -@PreviewComponent @Composable private fun TransportModeBadgeTrainPreview() { KrailTheme { @@ -70,7 +67,6 @@ private fun TransportModeBadgeTrainPreview() { } } -@PreviewComponent @Composable private fun TransportModeBadgeFerryPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt similarity index 91% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt index cf709e45..249c17a1 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt @@ -17,10 +17,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeIcon( @@ -67,7 +66,7 @@ private fun Modifier.borderIfEnabled(enabled: Boolean): Modifier = // region Previews -@PreviewComponent + @Composable private fun TrainPreview() { KrailTheme { @@ -75,7 +74,7 @@ private fun TrainPreview() { } } -@PreviewComponent + @Composable private fun BusPreview() { KrailTheme { @@ -86,7 +85,7 @@ private fun BusPreview() { } } -@PreviewComponent + @Composable private fun MetroPreview() { KrailTheme { @@ -97,7 +96,7 @@ private fun MetroPreview() { } } -@PreviewComponent + @Composable private fun LightRailPreview() { KrailTheme { @@ -108,7 +107,7 @@ private fun LightRailPreview() { } } -@PreviewComponent + @Composable private fun FerryPreview() { KrailTheme { @@ -119,7 +118,7 @@ private fun FerryPreview() { } } -@PreviewComponent + @Composable private fun TrainWithBackgroundPreview() { KrailTheme { @@ -131,7 +130,6 @@ private fun TrainWithBackgroundPreview() { } } -@PreviewComponent @Composable private fun BusWithBackgroundPreview() { KrailTheme { @@ -143,7 +141,7 @@ private fun BusWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun MetroWithBackgroundPreview() { KrailTheme { @@ -155,7 +153,7 @@ private fun MetroWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun LightRailWithBackgroundPreview() { KrailTheme { @@ -167,7 +165,7 @@ private fun LightRailWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun FerryWithBackgroundPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt similarity index 87% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt index 61f728cd..7cb379eb 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt @@ -6,10 +6,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeInfo( @@ -39,8 +38,6 @@ fun TransportModeInfo( // region Previews -@Preview -@Preview(fontScale = 2.0f) @Composable private fun TransportModeInfoPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt similarity index 77% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt index a67d4224..aab41145 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt @@ -10,14 +10,15 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.R +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_walk +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun WalkingLeg( @@ -36,7 +37,7 @@ fun WalkingLeg( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Image( - painter = painterResource(id = R.drawable.ic_walk), + painter = painterResource(Res.drawable.ic_walk), contentDescription = null, colorFilter = ColorFilter.tint( color = KrailTheme.colors.onSurface.copy(alpha = contentAlpha), @@ -47,7 +48,7 @@ fun WalkingLeg( } } -@Preview(showBackground = true) +@Preview @Composable private fun PreviewWalkingLeg() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt similarity index 94% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt index e171c4dd..bbf13cd0 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt @@ -16,10 +16,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.loading.LoadingEmojiManager.getRandomEmoji @Composable @@ -70,7 +69,7 @@ fun LoadingEmojiAnim(modifier: Modifier = Modifier, emoji: String? = null) { } } -@Preview +//@Preview @Composable private fun Preview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt similarity index 87% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt index 574022f3..d27d98fd 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt @@ -3,8 +3,8 @@ package xyz.ksharma.krail.trip.planner.ui.components.loading import kotlinx.collections.immutable.persistentListOf import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone +import kotlinx.datetime.number import kotlinx.datetime.todayIn -import java.time.MonthDay import kotlin.random.Random object LoadingEmojiManager { @@ -35,6 +35,13 @@ object LoadingEmojiManager { FestivalType.CHINESE_NEW_YEAR to listOf("🧧"), ) + // TODO - test logic + data class MonthDay(val month: Int, val dayOfMonth: Int) { + companion object { + fun of(month: Int, dayOfMonth: Int) = MonthDay(month, dayOfMonth) + } + } + private val knownFestivalDates = mapOf( FestivalType.CHRISTMAS to MonthDay.of(12, 25), FestivalType.NEW_YEAR to MonthDay.of(1, 1), @@ -48,7 +55,7 @@ object LoadingEmojiManager { val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) val festivalEmoji = knownFestivalDates.entries - .firstOrNull { it.value.month == today.month && it.value.dayOfMonth == today.dayOfMonth } + .firstOrNull { it.value.month == today.month.number && it.value.dayOfMonth == today.dayOfMonth } ?.let { festivalEmojiMap[it.key]?.randomOrNull() } if (festivalEmoji != null) return festivalEmoji diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt new file mode 100644 index 00000000..40ada16f --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.trip.planner.ui.di + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module +import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel +import xyz.ksharma.krail.trip.planner.ui.searchstop.SearchStopViewModel +import xyz.ksharma.krail.trip.planner.ui.timetable.TimeTableViewModel +import xyz.ksharma.krail.trip.planner.ui.usualride.UsualRideViewModel + +val viewModelsModule = module { + viewModelOf(::SavedTripsViewModel) + viewModelOf(::SearchStopViewModel) + viewModelOf(::TimeTableViewModel) + viewModelOf(::UsualRideViewModel) +} diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt similarity index 69% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt index 5de59eea..7c64272b 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt @@ -15,7 +15,7 @@ import xyz.ksharma.krail.trip.planner.ui.usualride.usualRideDestination * It contains all the screens in the feature Trip Planner. */ fun NavGraphBuilder.tripPlannerDestinations( - // TODO - do not wanna add NavController here, but moving all callbacks to app module is not scaleable. + // TODO - do not wanna add NavController here, but moving all callbacks to android-app module is not scaleable. navController: NavHostController, ) { navigation(startDestination = SavedTripsRoute) { @@ -31,9 +31,20 @@ fun NavGraphBuilder.tripPlannerDestinations( } } -internal enum class SearchStopFieldType(val key: String) { - FROM(key = "FromSearchStopResult"), - TO(key = "ToSearchStopResult"), +@Serializable +internal sealed class SearchStopFieldType(val key: String) { + data object FROM : SearchStopFieldType("FromSearchStopResult") + data object TO : SearchStopFieldType("ToSearchStopResult") + + companion object { + fun fromKey(key: String): SearchStopFieldType { + return when (key) { + FROM.key -> FROM + TO.key -> TO + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + } } @Serializable @@ -51,7 +62,10 @@ internal data class TimeTableRoute( ) @Serializable -internal data class SearchStopRoute(val fieldType: SearchStopFieldType) +internal data class SearchStopRoute(val fieldTypeKey: String) { + val fieldType: SearchStopFieldType + get() = SearchStopFieldType.fromKey(fieldTypeKey) +} @Serializable data object UsualRideRoute diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt similarity index 86% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 5f47aaa6..36d07467 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -5,13 +5,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import timber.log.Timber +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopFieldType import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute @@ -23,7 +22,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Compani @Suppress("LongMethod") internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel: SavedTripsViewModel = koinViewModel() val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = @@ -45,12 +44,12 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl LaunchedEffect(fromArg) { fromArg?.let { fromStopItem = fromJsonString(it) } - Timber.d("Change fromStopItem: $fromStopItem") +// Timber.d("Change fromStopItem: $fromStopItem") } LaunchedEffect(toArg) { toArg?.let { toStopItem = fromJsonString(it) } - Timber.d("Change toStopItem: $toStopItem") +// Timber.d("Change toStopItem: $toStopItem") } SavedTripsScreen( @@ -58,18 +57,18 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl fromStopItem = fromStopItem, toStopItem = toStopItem, fromButtonClick = { - Timber.d("fromButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.FROM)}") - navController.navigate(SearchStopRoute(fieldType = SearchStopFieldType.FROM)) + // Timber.d("fromButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.FROM)}") + navController.navigate(SearchStopRoute(fieldTypeKey = SearchStopFieldType.FROM.key)) }, toButtonClick = { - Timber.d("toButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.TO)}") + // Timber.d("toButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.TO)}") navController.navigate( - route = SearchStopRoute(fieldType = SearchStopFieldType.TO), + route = SearchStopRoute(fieldTypeKey = SearchStopFieldType.TO.key), navOptions = NavOptions.Builder().setLaunchSingleTop(true).build(), ) }, onReverseButtonClick = { - Timber.d("onReverseButtonClick:") + // Timber.d("onReverseButtonClick:") val bufferStop = fromStopItem backStackEntry.savedStateHandle[SearchStopFieldType.FROM.key] = toStopItem?.toJsonString() @@ -102,7 +101,7 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl ) } else { // TODO - show message - to select both stops - Timber.e("Select both stops") + // Timber.e("Select both stops") } }, onEvent = { event -> viewModel.onEvent(event) }, diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt similarity index 82% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt index 4ced2e6d..a4c1961a 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt @@ -1,8 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.savedtrips -import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,28 +12,24 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.R +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.SavedTripCard import xyz.ksharma.krail.trip.planner.ui.components.SearchStopRow -import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor -import xyz.ksharma.krail.trip.planner.ui.getActivityOrNull import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import org.jetbrains.compose.resources.painterResource + @Composable fun SavedTripsScreen( @@ -51,8 +44,8 @@ fun SavedTripsScreen( onEvent: (SavedTripUiEvent) -> Unit = {}, ) { val themeContentColor by LocalThemeContentColor.current - val context = LocalContext.current - DisposableEffect(themeContentColor) { + // TODO - handle colors of status bar +/* DisposableEffect(themeContentColor) { context.getActivityOrNull()?.let { activity -> (activity as ComponentActivity).enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto( @@ -62,7 +55,7 @@ fun SavedTripsScreen( ) } onDispose {} - } + }*/ Box( modifier = modifier @@ -72,7 +65,7 @@ fun SavedTripsScreen( ) { Column { TitleBar(title = { - Text(text = stringResource(R.string.saved_trips_screen_title)) + Text(text = "Saved Trips") }) LazyColumn( @@ -116,8 +109,7 @@ fun SavedTripsScreen( }, primaryTransportMode = null, // TODO modifier = Modifier - .padding(horizontal = 16.dp) - .animateItem(), + .padding(horizontal = 16.dp), ) Spacer(modifier = Modifier.height(12.dp)) @@ -140,7 +132,6 @@ fun SavedTripsScreen( // region Previews -@PreviewLightDark @Composable private fun SavedTripsScreenPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt similarity index 51% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 5e81dd15..344eba5f 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -2,48 +2,47 @@ package xyz.ksharma.krail.trip.planner.ui.savedtrips import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import timber.log.Timber -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory +import xyz.ksharma.krail.sandook.SavedTrip import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import javax.inject.Inject -@HiltViewModel -class SavedTripsViewModel @Inject constructor( - sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, +class SavedTripsViewModel( + private val sandook: Sandook, ) : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) - private val _uiState: MutableStateFlow = MutableStateFlow(SavedTripsState()) val uiState: StateFlow = _uiState private fun loadSavedTrips() { - viewModelScope.launch(context = ioDispatcher) { - val trips = sandook.keys().mapNotNull { key -> - val tripString = sandook.getString(key, null) - tripString?.let { tripJsonString -> - Trip.fromJsonString(tripJsonString) - } - }.toImmutableList() + viewModelScope.launch(context = Dispatchers.IO) { + val trips = mutableSetOf() + + val savedTrips = sandook.selectAllTrips() + println("SavedTrips: $savedTrips") + savedTrips.forEachIndexed { index, savedTrip -> + val trip = savedTrip.toTrip() + println("Trip: #$index $trip") + trips.add(savedTrip.toTrip()) + } + trips.addAll(savedTrips.map { savedTrip -> savedTrip.toTrip() }) + + println("SavedTrips: ${trips.size} number") trips.forEachIndexed { index, trip -> - Timber.d("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") + println("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") } - updateUiState { copy(savedTrips = trips) } + updateUiState { copy(savedTrips = trips.toImmutableList()) } } } @@ -55,9 +54,9 @@ class SavedTripsViewModel @Inject constructor( } private fun onDeleteSavedTrip(savedTrip: Trip) { - Timber.d("onDeleteSavedTrip: $savedTrip") - viewModelScope.launch(context = ioDispatcher) { - sandook.remove(key = savedTrip.tripId) + println("onDeleteSavedTrip: $savedTrip") + viewModelScope.launch(context = Dispatchers.IO) { + sandook.deleteTrip(tripId = savedTrip.tripId) loadSavedTrips() } } @@ -66,3 +65,10 @@ class SavedTripsViewModel @Inject constructor( _uiState.update(block) } } + +private fun SavedTrip.toTrip(): Trip = Trip( + fromStopId = fromStopId, + fromStopName = fromStopName, + toStopId = toStopId, + toStopName = toStopName, +) diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt similarity index 81% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index 774cbbed..4757f2a9 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -1,25 +1,24 @@ package xyz.ksharma.krail.trip.planner.ui.searchstop import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute -import timber.log.Timber +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel: SearchStopViewModel = koinViewModel() val searchStopState by viewModel.uiState.collectAsStateWithLifecycle() val route: SearchStopRoute = backStackEntry.toRoute() SearchStopScreen( searchStopState = searchStopState, onStopSelect = { stopItem -> - Timber.d("onStopSelected: fieldTypeKey=${route.fieldType.key} and stopItem: $stopItem") + //Timber.d("onStopSelected: fieldTypeKey=${route.fieldType.key} and stopItem: $stopItem") navController.previousBackStackEntry?.savedStateHandle?.set( route.fieldType.key, diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt similarity index 94% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt index bffc7bbb..ca05c977 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt @@ -25,22 +25,22 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.mapLatest -import timber.log.Timber -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Divider -import xyz.ksharma.krail.design.system.components.TextField -import xyz.ksharma.krail.design.system.theme.KrailTheme +import kotlinx.datetime.Clock +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Divider +import xyz.ksharma.krail.taj.components.TextField +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.StopSearchListItem import xyz.ksharma.krail.trip.planner.ui.components.backgroundColorOf @@ -50,6 +50,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem +@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) @Composable fun SearchStopScreen( searchStopState: SearchStopState, @@ -76,7 +77,7 @@ fun SearchStopScreen( .debounce(250) .filter { it.isNotBlank() } .mapLatest { text -> - Timber.d("Query - $text") + // Timber.d("Query - $text") onEvent(SearchStopUiEvent.SearchTextChanged(text)) }.collectLatest {} } @@ -93,7 +94,7 @@ fun SearchStopScreen( // track the time of the last query. If new results come in during the delay period, // then lastQueryTime will be different, therefore, it will prevent // "No match found" message from being displayed. - val currentQueryTime = System.currentTimeMillis() + val currentQueryTime = Clock.System.now().toEpochMilliseconds() lastQueryTime = currentQueryTime delay(1000) if (lastQueryTime == currentQueryTime && searchStopState.stops.isEmpty()) { @@ -130,7 +131,7 @@ fun SearchStopScreen( input.filter { it.isLetterOrDigit() || it.isWhitespace() } }, ) { value -> - Timber.d("value: $value") + //Timber.d("value: $value") textFieldText = value.toString() } @@ -188,7 +189,6 @@ fun SearchStopScreen( // region Previews -@Preview @Composable private fun PreviewSearchStopScreenLoading() { KrailTheme { @@ -202,7 +202,6 @@ private fun PreviewSearchStopScreenLoading() { } } -@Preview @Composable private fun PreviewSearchStopScreenError() { KrailTheme { @@ -216,7 +215,6 @@ private fun PreviewSearchStopScreenError() { } } -@Preview @Composable private fun PreviewSearchStopScreenEmpty() { KrailTheme { @@ -234,7 +232,6 @@ private fun PreviewSearchStopScreenEmpty() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenTrain() { KrailTheme { @@ -248,7 +245,6 @@ private fun PreviewSearchStopScreenTrain() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenCoach() { KrailTheme { @@ -262,7 +258,6 @@ private fun PreviewSearchStopScreenCoach() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenFerry() { KrailTheme { @@ -276,7 +271,6 @@ private fun PreviewSearchStopScreenFerry() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenMetro() { KrailTheme { @@ -290,7 +284,6 @@ private fun PreviewSearchStopScreenMetro() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenLightRail() { KrailTheme { @@ -304,7 +297,6 @@ private fun PreviewSearchStopScreenLightRail() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenBus() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt similarity index 67% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index 2861edb0..e48affbc 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -2,25 +2,18 @@ package xyz.ksharma.krail.trip.planner.ui.searchstop import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import timber.log.Timber -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService import xyz.ksharma.krail.trip.planner.ui.searchstop.StopResultMapper.toStopResults import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent -import javax.inject.Inject -@HiltViewModel -class SearchStopViewModel @Inject constructor( - private val tripPlanningRepository: TripPlanningRepository, -) : ViewModel() { +class SearchStopViewModel(private val tripPlanningService: TripPlanningService) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(SearchStopState()) val uiState: StateFlow = _uiState @@ -32,17 +25,21 @@ class SearchStopViewModel @Inject constructor( } private fun onSearchTextChanged(query: String) { - Timber.d("onSearchTextChanged: $query") + //Timber.d("onSearchTextChanged: $query") updateUiState { displayLoading() } viewModelScope.launch { - tripPlanningRepository.stopFinder(stopSearchQuery = query) - .onSuccess { response: StopFinderResponse -> - val results = response.toStopResults() - updateUiState { displayData(results) } - }.onFailure { - updateUiState { displayError() } - } + runCatching { + val response = tripPlanningService.stopFinder(stopSearchQuery = query) + println("response VM: $response") + + val results = response.toStopResults() + println("results: $results") + + updateUiState { displayData(results) } + }.getOrElse { + updateUiState { displayError() } + } } } diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt index a4257826..acc4aeb2 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt @@ -19,12 +19,16 @@ object StopResultMapper { fun StopFinderResponse.toStopResults( selectedModes: Set = TransportMode.values(), ): List { + println( "selectedModes: " + selectedModes) + return locations.orEmpty().mapNotNull { location -> val stopName = location.disassembledName ?: return@mapNotNull null // Skip if stop name is null val stopId = location.id ?: return@mapNotNull null // Skip if stop ID is null val modes = location.productClasses.orEmpty() .mapNotNull { productClass -> TransportMode.toTransportModeType(productClass) } + println("productClasses [${location.name}]: ${location.productClasses}") + // Filter based on selected mode types if (selectedModes.isNotEmpty() && !modes.any { it in selectedModes }) { return@mapNotNull null diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt similarity index 92% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index dbefc756..9b8ed166 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -1,14 +1,13 @@ package xyz.ksharma.krail.trip.planner.ui.timetable import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import timber.log.Timber +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent @@ -16,7 +15,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip internal fun NavGraphBuilder.timeTableDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel: TimeTableViewModel = koinViewModel() val timeTableState by viewModel.uiState.collectAsStateWithLifecycle() val route: TimeTableRoute = backStackEntry.toRoute() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() @@ -33,7 +32,7 @@ internal fun NavGraphBuilder.timeTableDestination(navController: NavHostControll onEvent = { viewModel.onEvent(it) }, onBackClick = { navController.popBackStack() }, onAlertClick = { journeyId -> - Timber.d("journeyId: $journeyId") + println("journeyId: $journeyId") viewModel.fetchAlertsForJourney(journeyId) { alerts -> if (alerts.isNotEmpty()) { navController.navigate( diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt similarity index 90% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt index fc1e723a..38b66d44 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt @@ -1,8 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.timetable -import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -21,9 +18,10 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.Star import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -31,27 +29,25 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.shouldUseDarkIcons -import xyz.ksharma.krail.trip.planner.ui.R +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import krail.feature.trip_planner.ui.generated.resources.ic_star +import krail.feature.trip_planner.ui.generated.resources.ic_star_filled +import org.jetbrains.compose.resources.painterResource +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.shouldUseDarkIcons import xyz.ksharma.krail.trip.planner.ui.components.ActionData import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.JourneyCard @@ -59,13 +55,11 @@ import xyz.ksharma.krail.trip.planner.ui.components.JourneyCardState import xyz.ksharma.krail.trip.planner.ui.components.OriginDestination import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.loading.LoadingEmojiAnim -import xyz.ksharma.krail.trip.planner.ui.getActivityOrNull import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import xyz.ksharma.krail.design.system.R as DesignSystemR @Composable fun TimeTableScreen( @@ -80,9 +74,8 @@ fun TimeTableScreen( val themeContentColorHex by LocalThemeContentColor.current val themeColor by remember { mutableStateOf(themeColorHex.hexToComposeColor()) } val themeContentColor by remember { mutableStateOf(themeContentColorHex.hexToComposeColor()) } - val context = LocalContext.current val darkIcons = shouldUseDarkIcons(themeColor) - DisposableEffect(darkIcons) { + /*DisposableEffect(darkIcons) { context.getActivityOrNull()?.let { activity -> (activity as ComponentActivity).enableEdgeToEdge( statusBarStyle = SystemBarStyle.auto( @@ -96,7 +89,7 @@ fun TimeTableScreen( ) } onDispose {} - } + }*///TODO - Fix status bar colors Column( modifier = modifier @@ -124,7 +117,7 @@ fun TimeTableScreen( }, title = { Text( - text = stringResource(R.string.time_table_screen_title), + text = "TimeTable", color = themeContentColor, ) }, @@ -136,7 +129,7 @@ fun TimeTableScreen( contentDescription = "Reverse Trip Search", ) { Image( - imageVector = ImageVector.vectorResource(R.drawable.ic_reverse), + painter = painterResource(Res.drawable.ic_reverse), contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), modifier = Modifier.size(24.dp), @@ -151,13 +144,11 @@ fun TimeTableScreen( }, ) { Image( - imageVector = ImageVector.vectorResource( - if (timeTableState.isTripSaved) { - DesignSystemR.drawable.star_filled - } else { - DesignSystemR.drawable.star_outline - }, - ), + painter = if (timeTableState.isTripSaved) { + painterResource(Res.drawable.ic_star_filled) + } else { + painterResource(Res.drawable.ic_star) + }, contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), modifier = Modifier.size(24.dp), @@ -336,7 +327,6 @@ fun ActionButton( // region Preview -@PreviewLightDark @Composable private fun PreviewTimeTableScreen() { KrailTheme { @@ -379,7 +369,6 @@ private fun PreviewTimeTableScreen() { } } -@PreviewLightDark @Composable private fun PreviewTimeTableScreenError() { KrailTheme { @@ -405,7 +394,6 @@ private fun PreviewTimeTableScreenError() { } } -@PreviewLightDark @Composable private fun PreviewTimeTableScreenNoResults() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt similarity index 74% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index ace2c8bb..8eb1679d 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -2,10 +2,10 @@ package xyz.ksharma.krail.trip.planner.ui.timetable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -17,45 +17,35 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList -import xyz.ksharma.krail.trip.planner.ui.timetable.business.logForUnderstandingData -import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -@HiltViewModel -class TimeTableViewModel @Inject constructor( - private val tripRepository: TripPlanningRepository, - sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, +class TimeTableViewModel( + private val tripPlanningService: TripPlanningService, private val rateLimiter: RateLimiter, + private val sandook: Sandook, ) : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) - private val _uiState: MutableStateFlow = MutableStateFlow(TimeTableState()) val uiState: StateFlow = _uiState private val _isLoading: MutableStateFlow = MutableStateFlow(false) val isLoading: StateFlow = _isLoading - // Will start fetching the trip as soon as the screen is visible, which means if app goes + // Will start fetching the trip as soon as the screen is visible, which means if android-app goes // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { - Timber.d("onStart: Fetching Trip") + println("onStart: Fetching Trip") fetchTrip() }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANR_TIMEOUT), true) @@ -89,17 +79,17 @@ class TimeTableViewModel @Inject constructor( } private fun fetchTrip() { - Timber.d("fetchTrip API Call") - viewModelScope.launch(ioDispatcher) { + println("fetchTrip API Call") + viewModelScope.launch(Dispatchers.IO) { // TODO - silent refresh here, UI to display loading but silent one. rateLimiter.rateLimitFlow { - Timber.d("rateLimitFlow block") + // println("rateLimitFlow block") loadTrip() }.catch { e -> - Timber.e("Error while fetching trip: $e") + println("Error while fetching trip: $e") }.collectLatest { result -> result.onSuccess { response -> - Timber.d("Success API response") + // println("Success API response") updateUiState { copy( isLoading = false, @@ -107,42 +97,51 @@ class TimeTableViewModel @Inject constructor( isError = false, ) } - response.logForUnderstandingData() + //response.logForUnderstandingData() }.onFailure { - Timber.e("Error while fetching trip: $it") + // Timber.e("Error while fetching trip: $it") updateUiState { copy(isLoading = false, isError = true) } } } } } - private suspend fun loadTrip(): Result = withContext(ioDispatcher) { - Timber.d("loadTrip API Call") + private suspend fun loadTrip(): Result = withContext(Dispatchers.IO) { + println("loadTrip API Call") require( tripInfo != null && tripInfo!!.fromStopId.isNotEmpty() && tripInfo!!.toStopId.isNotEmpty(), ) { "Trip Info is null or empty" } - tripRepository.trip( - originStopId = tripInfo!!.fromStopId, - destinationStopId = tripInfo!!.toStopId, - ) + runCatching { + val tripResponse = tripPlanningService.trip( + originStopId = tripInfo!!.fromStopId, + destinationStopId = tripInfo!!.toStopId, + ) + Result.success(tripResponse) + }.getOrElse { error -> + Result.failure(error) + } } private fun onSaveTripButtonClicked() { - Timber.d("Save Trip Button Clicked") - viewModelScope.launch(ioDispatcher) { + println("Save Trip Button Clicked") + viewModelScope.launch(Dispatchers.IO) { tripInfo?.let { trip -> - Timber.d("Toggle Save Trip: $trip") - val savedTrip = sandook.getString(key = trip.tripId) + println("Toggle Save Trip: $trip") + val savedTrip = sandook.selectTripById(tripId = trip.tripId) if (savedTrip != null) { // Trip is already saved, so delete it - sandook.remove(key = trip.tripId) - Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") + sandook.deleteTrip(tripId = trip.tripId) + println("Deleted Trip (Pref): $savedTrip") updateUiState { copy(isTripSaved = false) } } else { // Trip is not saved, so save it - sandook.putString(key = trip.tripId, value = trip.toJsonString()) - Timber.d("Saved Trip (Pref): $trip") + sandook.insertOrReplaceTrip( + tripId = trip.tripId, + fromStopId = trip.fromStopId, fromStopName = trip.fromStopName, + toStopId = trip.toStopId, toStopName = trip.toStopName + ) + println("Saved Trip (Pref): $trip") updateUiState { copy(isTripSaved = true) } } } @@ -150,14 +149,15 @@ class TimeTableViewModel @Inject constructor( } private fun onJourneyCardClicked(journeyId: String) { - Timber.d("Journey Card Clicked(JourneyId): $journeyId") + println("Journey Card Clicked(JourneyId): $journeyId") _expandedJourneyId.update { if (it == journeyId) null else journeyId } } private fun onLoadTimeTable(trip: Trip) { - Timber.d("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") + println("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") tripInfo = trip - val savedTrip = sandook.getString(key = trip.tripId) + val savedTrip = sandook.selectTripById(tripId = trip.tripId) + println("Saved Trip[${trip.tripId}]: $savedTrip") updateUiState { copy( isLoading = true, @@ -170,7 +170,7 @@ class TimeTableViewModel @Inject constructor( } private fun onReverseTripButtonClicked() { - Timber.d("Reverse Trip Button Clicked -- Trigger") + println("Reverse Trip Button Clicked -- Trigger") require(tripInfo != null) { "Trip Info is null" } val reverseTrip = Trip( fromStopId = tripInfo!!.toStopId, @@ -179,7 +179,7 @@ class TimeTableViewModel @Inject constructor( toStopName = tripInfo!!.fromStopName, ) tripInfo = reverseTrip - val savedTrip = sandook.getString(key = reverseTrip.tripId) + val savedTrip = sandook.selectTripById(tripId = reverseTrip.tripId) updateUiState { copy( trip = reverseTrip, @@ -196,7 +196,7 @@ class TimeTableViewModel @Inject constructor( * journey card should be updated. */ private fun updateTimeText() = viewModelScope.launch { - val updatedJourneyList = withContext(ioDispatcher) { + val updatedJourneyList = withContext(Dispatchers.IO) { _uiState.value.journeyList.map { journeyCardInfo -> journeyCardInfo.copy( timeText = calculateTimeDifferenceFromNow( @@ -210,7 +210,7 @@ class TimeTableViewModel @Inject constructor( copy(journeyList = updatedJourneyList) } - // Timber.d("New Time: ${uiState.value.journeyList.joinToString(", ") { it.timeText }}") + // println("New Time: ${uiState.value.journeyList.joinToString(", ") { it.timeText }}") } private inline fun updateUiState(block: TimeTableState.() -> TimeTableState) { @@ -219,7 +219,7 @@ class TimeTableViewModel @Inject constructor( fun fetchAlertsForJourney(journeyId: String, onResult: (Set) -> Unit) { viewModelScope.launch { - val alerts = withContext(ioDispatcher) { + val alerts = withContext(Dispatchers.IO) { runCatching { _uiState.value.journeyList.find { it.journeyId == journeyId }?.let { journey -> getAlertsFromJourney(journey) diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 8e7099c8..afc24d11 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -2,14 +2,14 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import timber.log.Timber -import xyz.ksharma.krail.core.datetime.DateTimeHelper.aestToHHMM import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifference import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toHHMM import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToLocalDateTimeAEST import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine @@ -68,7 +68,7 @@ internal fun TripResponse.buildJourneyList(): ImmutableList leg.infos.orEmpty() }.toSet().size, ).also { - Timber.d("\tJourneyId: ${it.journeyId}") + println("\tJourneyId: ${it.journeyId}") } } else { null @@ -113,8 +113,8 @@ fun TripResponse.Leg?.getPlatformNumber(): String? { private fun List.logTransportModes() = forEachIndexed { index, leg -> // log origin's disassembledName - Timber.d("Origin #$index: ${leg.origin?.disassembledName}") - Timber.d( + println("Origin #$index: ${leg.origin?.disassembledName}") + println( "TransportMode #$index: ${leg.transportation?.product?.productClass}, " + "name: ${leg.transportation?.product?.name}, " + "stops: ${leg.stopSequence?.size}, " + @@ -137,13 +137,13 @@ private fun List.getTransportModeLines() = mapNotNull { leg -> private fun List.getLegsList() = mapNotNull { it.toUiModel() }.toImmutableList() private fun String.getTimeText() = let { - Timber.d("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") + // println("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") calculateTimeDifferenceFromNow(utcDateString = it).toGenericFormattedTimeString() } @Suppress("ComplexCondition") private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { - Timber.d( + println( "\tFFF Leg: ${this.duration}, " + "leg: ${this.origin?.name} TO ${this.destination?.name}" + " - isWalk:${this.isWalkingLeg()}, " + @@ -161,9 +161,9 @@ private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { val stops = stopSequence?.mapNotNull { it.toUiModel() }?.toImmutableList() val alerts = infos?.mapNotNull { it.toAlert() }?.toImmutableList() alerts?.forEach { - Timber.d("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") + //println("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") } - Timber.d("Alert---") + // println("Alert---") return when { // Walking Leg - Always check before public transport leg @@ -174,7 +174,7 @@ private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { } else -> { // Public Transport Leg -// Timber.d("FFF PTLeg: $displayDuration, leg: ${this.destination?.name} ") +// println("FFF PTLeg: $displayDuration, leg: ${this.destination?.name} ") if (transportMode != null && lineName != null && displayText != null && numberOfStops != null && stops != null && displayDuration != null ) { @@ -249,19 +249,19 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf } internal fun TripResponse.logForUnderstandingData() { - Timber.d("Journeys: ${journeys?.size}") + println("Journeys: ${journeys?.size}") journeys?.mapIndexed { jindex, j -> - Timber.d("JOURNEY #${jindex + 1}") + println("JOURNEY #${jindex + 1}") j.legs?.forEachIndexed { index, leg -> val transportationProductClass = leg.transportation?.product?.productClass - Timber.d( + println( " LEG#${index + 1} -- Duration: ${leg.duration} -- " + "productClass:${transportationProductClass?.toInt()}", ) - Timber.d( + println( "\t\t ORG: ${ leg.origin?.departureTimeEstimated?.utcToAEST() ?.formatTo12HourTime() @@ -300,7 +300,7 @@ private fun List.interchangeStopsList() = this.mapNot } } -private fun String.fromUTCToDisplayTimeString() = this.utcToAEST().aestToHHMM() +private fun String.fromUTCToDisplayTimeString() = this.utcToLocalDateTimeAEST().toHHMM() private fun TripResponse.Leg.isWalkingLeg(): Boolean = transportation?.product?.productClass == 99L || transportation?.product?.productClass == 100L diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt similarity index 88% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index be366eb2..19ce8d44 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -4,15 +4,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.collections.immutable.toImmutableSet -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.theme.getForegroundColor +import org.koin.compose.viewmodel.koinViewModel +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.theme.getForegroundColor import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.toHex import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute @@ -23,7 +23,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent internal fun NavGraphBuilder.usualRideDestination(navController: NavHostController) { composable { - val viewModel = hiltViewModel() + val viewModel:UsualRideViewModel = koinViewModel() var themeColor by LocalThemeColor.current var themeContentColor by LocalThemeContentColor.current var mode: TransportMode? by remember { mutableStateOf(null) } diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt similarity index 97% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt index 351b91e9..08a351b2 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt @@ -30,14 +30,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.TransportModeIcon import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.transportModeBackgroundColor @@ -184,7 +183,6 @@ private fun TransportModeRadioButton( } } -@PreviewLightDark @Composable private fun PreviewUsualRideScreen() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt new file mode 100644 index 00000000..a5edb147 --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -0,0 +1,30 @@ +package xyz.ksharma.krail.trip.planner.ui.usualride + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import xyz.ksharma.krail.sandook.Sandook +import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent +import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState + +class UsualRideViewModel(private val sandook: Sandook) : ViewModel() { + + private val _uiState: MutableStateFlow = MutableStateFlow(UsualRideState()) + val uiState: StateFlow = _uiState + + fun onEvent(event: UsualRideEvent) { + when (event) { + is UsualRideEvent.TransportModeSelected -> onTransportModeSelected(event.productClass) + } + } + + private fun onTransportModeSelected(productClass: Int) { + viewModelScope.launch(Dispatchers.IO) { + sandook.insertOrReplaceTheme(productClass.toLong()) + } + } +} diff --git a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt similarity index 96% rename from feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt rename to feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt index dd547f37..a926cd66 100644 --- a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt @@ -2,8 +2,9 @@ package xyz.ksharma.krail.trip.planner.ui.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance -import org.junit.Assert.assertEquals -import org.junit.Test +import kotlin.test.Test +import kotlin.test.assertEquals + // Define the colors private val myWhiteLightMode = Color(0xFFFFFBFF) @@ -35,13 +36,13 @@ fun getForegroundColor(backgroundColor: Color, isDarkMode: Boolean): Color { return if (whiteContrast >= 4.0f) whiteColor else blackColor } -fun Color.toHex(): String { +/*fun Color.toHex(): String { val red = (this.red * 255).toInt() val green = (this.green * 255).toInt() val blue = (this.blue * 255).toInt() val alpha = (this.alpha * 255).toInt() return String.format("#%02X%02X%02X%02X", alpha, red, green, blue) -} +}*/ class ColorUtilsTest { diff --git a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt similarity index 97% rename from feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt rename to feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt index f558b460..407eec37 100644 --- a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt @@ -1,8 +1,8 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business -import org.junit.Assert.assertEquals -import org.junit.Test import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import kotlin.test.Test +import kotlin.test.assertEquals class PlatformTextTest { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt deleted file mode 100644 index f9c988e5..00000000 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ /dev/null @@ -1,45 +0,0 @@ -package xyz.ksharma.krail.trip.planner.ui.usualride - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import timber.log.Timber -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher -import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory -import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent -import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState -import javax.inject.Inject - -@HiltViewModel -class UsualRideViewModel @Inject constructor( - sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, -) : ViewModel() { - - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.THEME) - - private val _uiState: MutableStateFlow = MutableStateFlow(UsualRideState()) - val uiState: StateFlow = _uiState - - fun onEvent(event: UsualRideEvent) { - when (event) { - is UsualRideEvent.TransportModeSelected -> onTransportModeSelected(event.productClass) - } - } - - private fun onTransportModeSelected(productClass: Int) { - viewModelScope.launch(ioDispatcher) { - TransportMode.toTransportModeType(productClass)?.let { mode -> - Timber.d("onTransportModeSelected: $mode") - sandook.putInt("selectedMode", mode.productClass) - } - } - } -} diff --git a/gradle.properties b/gradle.properties index 7fedda27..2d2f0c0a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,3 +14,4 @@ org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" android.useAndroidX=true org.gradle.caching=true +kotlin.native.ignoreDisabledTargets=true diff --git a/gradle/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts new file mode 100644 index 00000000..24daebf4 --- /dev/null +++ b/gradle/build-logic/convention/build.gradle.kts @@ -0,0 +1,77 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +group = "xyz.ksharma.krail.gradle" + +val javaVersion = libs.versions.java.get().toInt() + +java { + sourceCompatibility = JavaVersion.values()[javaVersion - 1] + targetCompatibility = JavaVersion.values()[javaVersion - 1] +} + +tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } +} + +kotlin { + jvmToolchain(javaVersion) +} + +dependencies { + compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.android.gradlePlugin) + compileOnly(libs.compose.gradlePlugin) + compileOnly(libs.composeCompiler.gradlePlugin) +} + +gradlePlugin { + plugins { + /** + * Supports building UIs for Android, Desktop, and Web using Jetpack Compose. + */ + register("composeMultiplatform") { + id = "krail.compose.multiplatform" + implementationClass = "xyz.ksharma.krail.gradle.ComposeMultiplatformConventionPlugin" + } + + /** + * Configures the project for Android application development. + */ + register("androidApplication") { + id = "krail.android.application" + implementationClass = "xyz.ksharma.krail.gradle.AndroidApplicationConventionPlugin" + } + + /** + * Configures the project for developing Android libraries. + */ + register("androidLibrary") { + id = "krail.android.library" + implementationClass = "xyz.ksharma.krail.gradle.AndroidLibraryConventionPlugin" + } + + /** + * Adds support for Kotlin in Android projects. + */ + register("kotlinAndroid") { + id = "krail.kotlin.android" + implementationClass = "xyz.ksharma.krail.gradle.KotlinAndroidConventionPlugin" + } + + /** + * Configures the project for Kotlin Multiplatform development, supporting JVM, Android, iOS, + * and JS targets. + */ + register("kotlinMultiplatform") { + id = "krail.kotlin.multiplatform" + implementationClass = "xyz.ksharma.krail.gradle.KotlinMultiplatformConventionPlugin" + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt new file mode 100644 index 00000000..ef7fcdf0 --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt @@ -0,0 +1,48 @@ +package xyz.ksharma.krail.gradle + +import com.android.build.api.dsl.CommonExtension +import com.android.build.gradle.BaseExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +fun Project.configureAndroid( +) { + extensions.configure { + compileSdkVersion(AndroidVersion.COMPILE_SDK) + + defaultConfig { + minSdk = AndroidVersion.MIN_SDK + targetSdk = AndroidVersion.TARGET_SDK + } + + configureJava() + + if (this is CommonExtension<*, *, *, *, *, *>) { + buildFeatures { + compose = true + buildConfig = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + } +} + +object AndroidVersion { + // https://developer.android.com/build/releases/gradle-plugin#api-level-support + const val COMPILE_SDK = 34 + const val MIN_SDK = 26 // Oreo 8.0 + const val TARGET_SDK = 34 +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt new file mode 100644 index 00000000..5bc31781 --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class AndroidApplicationConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + } + + configureAndroid() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt new file mode 100644 index 00000000..82a804d3 --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class AndroidLibraryConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.library") + } + configureAndroid() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt new file mode 100644 index 00000000..09683492 --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class ComposeMultiplatformConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + // Compose Multiplatform plugin - Supports building UIs for Android, Desktop, and Web + apply("org.jetbrains.compose") + + // Enables Compose-specific features in Kotlin projects + apply("org.jetbrains.kotlin.plugin.compose") + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt new file mode 100644 index 00000000..5fb27c7b --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.configure + +internal fun Project.configureJava() { + extensions.configure { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt new file mode 100644 index 00000000..6ee1d27f --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +class KotlinAndroidConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + apply("org.jetbrains.kotlin.android") // Support Kotlin in Android projects + } + configureJava() + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt new file mode 100644 index 00000000..78727da7 --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt @@ -0,0 +1,39 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + +class KotlinMultiplatformConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + apply("org.jetbrains.kotlin.multiplatform") // Kotlin Multiplatform plugin - JVM, Android, iOS, JS + } + + extensions.configure { + applyDefaultHierarchyTemplate() + + if (pluginManager.hasPlugin("com.android.library")) { + androidTarget() + } + + iosArm64() + iosSimulatorArm64() + + targets.withType().configureEach { + binaries.configureEach { + // Add linker flag for SQLite. See: + // https://github.com/touchlab/SQLiter/issues/77 + // required for iOS targets + linkerOpts("-lsqlite3") + } + } + + configureJava() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt new file mode 100644 index 00000000..750c52aa --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt @@ -0,0 +1,9 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs + get(): VersionCatalog = extensions.getByType().named("libs") diff --git a/gradle/build-logic/gradle.properties b/gradle/build-logic/gradle.properties new file mode 100644 index 00000000..c6cd2a7e --- /dev/null +++ b/gradle/build-logic/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configureondemand=true diff --git a/gradle/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts new file mode 100644 index 00000000..5f522649 --- /dev/null +++ b/gradle/build-logic/settings.gradle.kts @@ -0,0 +1,22 @@ +dependencyResolutionManagement { + repositories { + google { + content { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } + mavenCentral() + gradlePluginPortal() + } + + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5280aae..1b154c66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,30 +1,32 @@ [versions] java = "17" -agp = "8.7.2" # Android Gradle Plugin +# AGP - Android API level mapping https://developer.android.com/build/releases/gradle-plugin#api-level-support +agp = "8.5.2" # Android Gradle Plugin kotlin = "2.0.21" -core-ktx = "1.15.0" +core-ktx = "1.13.0" junit = "4.13.2" androidx-test = "1.6.1" androidx-test-ext-junit = "1.2.1" android-lifecycle = "2.8.7" activity-compose = "1.9.3" -compose-bom = "2024.10.01" -compose-navigation = "2.8.3" -detekt = "1.23.7" -detektCompose = "0.4.17" -firebaseBom = "33.5.1" -hilt = "2.52" -hiltNavigationCompose = "1.2.0" +kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" -okhttpBom = "4.12.0" +ktorClientAuth = "2.3.12" +lifecycleViewmodelCompose = "2.8.3" +navigationCompose = "2.8.0-alpha10" kotlinxSerializationJson = "1.7.3" ksp = "2.0.21-1.0.27" # ksp to kotlin version mapping https://github.com/google/ksp/releases paparazzi = "1.3.5" -retrofit = "2.11.0" -retrofit2KotlinxSerializationConverter = "1.0.0" -timber = "5.0.1" -wire = "5.1.0" +compose-multiplatform = "1.7.0" +ktor = "3.0.1" +androidx-lifecycle = "2.8.3" +kotlinxCoroutines = "1.9.0" +buildkonfigGradlePlugin = "0.15.2" +kermit = "2.0.4" +sqlDelight = "2.0.2" +koin = "4.0.1-Beta1" +slf4jSimple = "2.0.16" #SDK minSdk = "26" @@ -32,43 +34,38 @@ compileSdk = "35" targetSdk = "35" [libraries] +buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" } core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } -firebase-perf = { module = "com.google.firebase:firebase-perf" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktorClientAuth" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } -retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } -timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } -#Compose -compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } -compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } -compose-ui = { group = "androidx.compose.ui", name = "ui" } -compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "compose-navigation" } -compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.3.1" } +log-kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } -#Firebase -firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } -firebase-analytics = { module = "com.google.firebase:firebase-analytics" } -firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } +lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } +androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } -#Hilt -hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } -hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } -hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } +# DI +di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} +di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} +di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} #Network kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } -okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } -okhttp = { module = "com.squareup.okhttp3:okhttp" } -okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } -retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } +ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } #Test +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4jSimple" } test-composeUiTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } test-composeUiTestJunit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } test-junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -79,33 +76,36 @@ test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.0" test-mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } test-kotlinxCoroutineTest = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version = "1.9.0" } test-androidxCoreKtx = { group = "androidx.test", name = "core-ktx", version.ref = "androidx-test" } -test-paparazzi = { group = "app.cash.paparazzi", name = "paparazzi-gradle-plugin", version.ref = "paparazzi" } #BuildLogic kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } +compose-gradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-multiplatform" } +composeCompiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } -#CodeStyle -detektFormat = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } -detektCompose = { group = "io.nlopez.compose.rules", name = "detekt", version.ref = "detektCompose" } +#CodeStyle Use spotless plugin for multiplatform support + +# Database +db-sqlAndroidDriver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } +db-sqlNativeDriver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" } +db-sqlRuntime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -wire = { id = "com.squareup.wire", version.ref = "wire" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } -firebase-crashlyticsPlugin = { id = "com.google.firebase.crashlytics", version = "3.0.2" } -firebase-performancePlugin = { id = "com.google.firebase.firebase-perf", version = "1.4.2" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" } #Convention Plugins krail-android-application = { id = "krail.android.application", version = "unspecified" } -krail-android-hilt = { id = "krail.android.hilt", version = "unspecified" } +krail-compose-multiplatform = { id = "krail.compose.multiplatform", version = "unspecified" } krail-android-library = { id = "krail.android.library", version = "unspecified" } -krail-android-library-compose = { id = "krail.android.library.compose", version = "unspecified" } -krail-jvm-library = { id = "krail.jvm.library", version = "unspecified" } +krail-kotlin-android = { id = "krail.kotlin.android", version = "unspecified" } +krail-kotlin-multiplatform = { id = "krail.kotlin.multiplatform", version = "unspecified" } diff --git a/gradlew b/gradlew index f5feea6d..b87e4980 100755 --- a/gradlew +++ b/gradlew @@ -164,7 +164,7 @@ fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line -# * the main class name +# * the commonMain class name # * -classpath # * -D...appname settings # * --module-path (only if needed) diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig new file mode 100644 index 00000000..c713f578 --- /dev/null +++ b/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,3 @@ +TEAM_ID= +BUNDLE_ID=xyz.ksharma.krail +APP_NAME=Krail App diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..70e9f1e3 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,397 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.xcfilelist; }; + 7555FF7B242A565900829871 /* Krail App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Krail App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B92378962B6B1156000C7307 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 42799AB246E5F90AF97AA0EF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + AB1DB47929225F7C00F7AF9C /* Configuration */, + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 42799AB246E5F90AF97AA0EF /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* Krail App.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; + AB1DB47929225F7C00F7AF9C /* Configuration */ = { + isa = PBXGroup; + children = ( + AB3632DC29227652001CCB65 /* Config.xcconfig */, + ); + path = Configuration; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, + 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + packageProductDependencies = ( + ); + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* Krail App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1540; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + packageReferences = ( + ); + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 7555FF83242A565900829871 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + NEW_SETTING = ""; + ONLY_ACTIVE_ARCH = YES; + "OTHER_LDFLAGS[sdk=iphone*]" = "-lsqlite3"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + NEW_SETTING = ""; + "OTHER_LDFLAGS[sdk=iphone*]" = "-lsqlite3"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..45937bee Binary files /dev/null and b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/parora.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcodeproj/xcuserdata/parora.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..56b5955f --- /dev/null +++ b/iosApp/iosApp.xcodeproj/xcuserdata/parora.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + iosApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..ee7e3ca0 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..8edf56e7 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 00000000..bc98427c Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 00000000..762bcbe2 --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,18 @@ +import UIKit +import SwiftUI +import KrailApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea() + } +} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..412e3781 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000..8fa5091d --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,13 @@ +import SwiftUI + +@main +struct iOSApp: App { + init() { + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/sandook/api/build.gradle.kts b/sandook/api/build.gradle.kts deleted file mode 100644 index 749b3eb4..00000000 --- a/sandook/api/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - alias(libs.plugins.krail.jvm.library) -} diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts new file mode 100644 index 00000000..fb611254 --- /dev/null +++ b/sandook/build.gradle.kts @@ -0,0 +1,62 @@ +plugins { + alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.sqldelight) +} + +android { + namespace = "xyz.ksharma.krail.sandook" +} + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + androidMain { + dependencies { + implementation(libs.db.sqlAndroidDriver) + implementation(libs.di.koinAndroid) + } + } + + commonMain { + dependencies { + implementation(libs.kotlinx.serialization.json) + + implementation(compose.runtime) + implementation(libs.log.kermit) + implementation(libs.kotlinx.datetime) + implementation(libs.db.sqlRuntime) + api(libs.di.koinComposeViewmodel) + } + } + + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + api(libs.db.sqlNativeDriver) + } + } +} + +sqldelight { + databases { + create("KrailSandook") { + packageName.set("xyz.ksharma.krail.sandook") + } + } +} diff --git a/sandook/real/build.gradle.kts b/sandook/real/build.gradle.kts deleted file mode 100644 index 2166ad06..00000000 --- a/sandook/real/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) -} - -android { - namespace = "xyz.ksharma.krail.sandook.real" -} - -dependencies { - implementation(projects.sandook.api) - - implementation(libs.kotlinx.serialization.json) -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt deleted file mode 100644 index a0400356..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ /dev/null @@ -1,57 +0,0 @@ -package xyz.ksharma.krail.sandook - -import android.content.SharedPreferences -import javax.inject.Inject - -internal class RealSandook @Inject constructor(private val sharedPreferences: SharedPreferences) : Sandook { - - override fun keys(): Set = sharedPreferences.all.keys - - override fun putString(key: String, value: String) { - sharedPreferences.edit().putString(key, value).apply() - } - - override fun getString(key: String, defaultValue: String?): String? { - return sharedPreferences.getString(key, defaultValue) - } - - override fun putInt(key: String, value: Int) { - sharedPreferences.edit().putInt(key, value).apply() - } - - override fun getInt(key: String, defaultValue: Int): Int { - return sharedPreferences.getInt(key, defaultValue) - } - - override fun putBoolean(key: String, value: Boolean) { - sharedPreferences.edit().putBoolean(key, value).apply() - } - - override fun getBoolean(key: String, defaultValue: Boolean): Boolean { - return sharedPreferences.getBoolean(key, defaultValue) - } - - override fun putFloat(key: String, value: Float) { - sharedPreferences.edit().putFloat(key, value).apply() - } - - override fun getFloat(key: String, defaultValue: Float): Float { - return sharedPreferences.getFloat(key, defaultValue) - } - - override fun putLong(key: String, value: Long) { - sharedPreferences.edit().putLong(key, value).apply() - } - - override fun getLong(key: String, defaultValue: Long): Long { - return sharedPreferences.getLong(key, defaultValue) - } - - override fun remove(key: String) { - sharedPreferences.edit().remove(key).apply() - } - - override fun clear() { - sharedPreferences.edit().clear().apply() - } -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt deleted file mode 100644 index 719435a3..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt +++ /dev/null @@ -1,22 +0,0 @@ -package xyz.ksharma.krail.sandook - -interface Sandook { - - fun clear() - fun remove(key: String) - fun getLong(key: String, defaultValue: Long = 0L): Long - fun putLong(key: String, value: Long) - fun getFloat(key: String, defaultValue: Float = 0f): Float - fun putFloat(key: String, value: Float) - fun getBoolean(key: String, defaultValue: Boolean = false): Boolean - fun putBoolean(key: String, value: Boolean) - fun getInt(key: String, defaultValue: Int = 0): Int - fun putInt(key: String, value: Int) - fun getString(key: String, defaultValue: String? = null): String? - fun putString(key: String, value: String) - - /** - * Returns a set of all keys in the Sandook. - */ - fun keys(): Set -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt deleted file mode 100644 index 329d03a8..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package xyz.ksharma.krail.sandook.di - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SandookFactory @Inject constructor( - @ApplicationContext private val context: Context, -) { - fun create(sandookKey: SandookKey): Sandook { - val sharedPreferences = context.getSharedPreferences(sandookKey.fileName, Context.MODE_PRIVATE) - return RealSandook(sharedPreferences) - } - - enum class SandookKey(val fileName: String) { - SAVED_TRIP("saved_trip"), - THEME("theme"), - } -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt deleted file mode 100644 index 2d76da29..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package xyz.ksharma.krail.sandook.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class SandookModule { - - @Binds - @Singleton - internal abstract fun bindSandook(impl: RealSandook): Sandook -} diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt new file mode 100644 index 00000000..04d9f39d --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.sandook + +import android.content.Context +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver + +class AndroidSandookDriverFactory(private val context: Context) : SandookDriverFactory { + override fun createDriver(): SqlDriver { + return AndroidSqliteDriver( + schema = KrailSandook.Schema, + context = context, + name = "krailSandook.db" + ) + } +} diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt new file mode 100644 index 00000000..f8b027e5 --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt @@ -0,0 +1,21 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.AndroidSandookDriverFactory +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.SandookDriverFactory + +actual val sqlDriverModule = module { + singleOf(::AndroidSandookDriverFactory) { bind() } + + single { + RealSandook( + factory = AndroidSandookDriverFactory( + context = androidContext() + ) + ) + } +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt new file mode 100644 index 00000000..de19e997 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -0,0 +1,56 @@ +package xyz.ksharma.krail.sandook + +internal class RealSandook(factory: SandookDriverFactory) : Sandook { + + private val sandook = KrailSandook(factory.createDriver()) + private val query = sandook.krailSandookQueries + + // region Theme + override fun insertOrReplaceTheme(productClass: Long) { + query.insertOrReplaceProductClass(productClass) + } + + override fun getProductClass(): Long? { + return query.selectProductClass().executeAsOneOrNull() + } + + override fun clearTheme() { + query.clearTheme() + } + + // endregion + + // region SavedTrip + override fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String, + ) { + query.insertOrReplaceTrip( + tripId, + fromStopId, + fromStopName, + toStopId, + toStopName, + ) + } + + override fun deleteTrip(tripId: String) { + query.deleteTrip(tripId) + } + + override fun selectAllTrips(): List { + return query.selectAllTrips().executeAsList() + } + + override fun selectTripById(tripId: String): SavedTrip? { + return query.selectTripById(tripId).executeAsOneOrNull() + } + + override fun clearSavedTrips() { + query.clearSavedTrips() + } + // endregion +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt new file mode 100644 index 00000000..047718e4 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt @@ -0,0 +1,24 @@ +package xyz.ksharma.krail.sandook + +interface Sandook { + + // region Theme + fun insertOrReplaceTheme(productClass: Long) + fun getProductClass(): Long? + fun clearTheme() + // endregion + + // region SavedTrip + fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String + ) + fun deleteTrip(tripId: String) + fun selectAllTrips(): List + fun selectTripById(tripId: String): SavedTrip? + fun clearSavedTrips() + // endregion +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt new file mode 100644 index 00000000..678a5186 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver + +interface SandookDriverFactory { + fun createDriver(): SqlDriver +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt new file mode 100644 index 00000000..6ad33284 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.core.module.Module +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook + +val sandookModule = module { + singleOf(::RealSandook) { bind() } + includes(sqlDriverModule) +} + +expect val sqlDriverModule: Module diff --git a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq new file mode 100644 index 00000000..183515fe --- /dev/null +++ b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq @@ -0,0 +1,42 @@ +-- Theme Table -- +CREATE TABLE Theme ( + productClass INTEGER NOT NULL +); + +insertOrReplaceProductClass: +INSERT OR REPLACE INTO Theme(productClass) +VALUES (?); + +selectProductClass: +SELECT productClass FROM Theme LIMIT 1; + +clearTheme: +DELETE FROM Theme; + +-- Saved Trip Table -- +CREATE TABLE SavedTrip ( + tripId TEXT PRIMARY KEY, + fromStopId TEXT NOT NULL, + fromStopName TEXT NOT NULL, + toStopId TEXT NOT NULL, + toStopName TEXT NOT NULL, + timestamp TEXT DEFAULT (datetime('now')) +); + +insertOrReplaceTrip: +INSERT OR REPLACE INTO SavedTrip(tripId, fromStopId, fromStopName, toStopId, toStopName, timestamp) +VALUES (?, ?, ?, ?, ?, datetime('now')); + +deleteTrip: +DELETE FROM SavedTrip WHERE tripId = ?; + +selectAllTrips: +SELECT * FROM SavedTrip +ORDER BY timestamp DESC; + +selectTripById: +SELECT * FROM SavedTrip +WHERE tripId = ?; + +clearSavedTrips: +DELETE FROM SavedTrip; diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt new file mode 100644 index 00000000..e9d0d03d --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.native.NativeSqliteDriver + +class IosSandookDriverFactory : SandookDriverFactory { + override fun createDriver(): SqlDriver { + return NativeSqliteDriver(KrailSandook.Schema, name = "krailSandook.db") + } +} diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt new file mode 100644 index 00000000..79d3fec7 --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.IosSandookDriverFactory +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.SandookDriverFactory + +actual val sqlDriverModule = module { + singleOf(::IosSandookDriverFactory) { bind() } + + single { + RealSandook(factory = IosSandookDriverFactory()) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index eb44a49d..66f91389 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,33 +1,40 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { - - includeBuild("build-logic") + includeBuild("gradle/build-logic") repositories { - google() + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } mavenCentral() gradlePluginPortal() } } + dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } mavenCentral() } } rootProject.name = "Krail" -include(":app") -include(":core:coroutines-ext") +//include(":android-app") +include(":composeApp") +include(":taj") // Design System include(":core:date-time") -include(":core:di") -include(":core:design-system") -include(":core:network") -include(":feature:trip-planner:network:api") -include(":feature:trip-planner:network:real") -include(":feature:trip-planner:state") include(":feature:trip-planner:ui") -include(":sandook:api") -include(":sandook:real") +include(":feature:trip-planner:state") +include(":feature:trip-planner:network") +include(":sandook") diff --git a/taj/build.gradle.kts b/taj/build.gradle.kts new file mode 100644 index 00000000..422d673f --- /dev/null +++ b/taj/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.krail.android.library) +} + +android { + namespace = "xyz.ksharma.krail.taj" +} + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(compose.foundation) + implementation(compose.animation) + implementation(compose.ui) + implementation(compose.material3) + implementation(compose.runtime) + } + } + } +} diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt similarity index 94% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt index ccf29ae5..1be538b5 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system +package xyz.ksharma.krail.taj import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt similarity index 95% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt index f699e24e..475dce61 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system +package xyz.ksharma.krail.taj import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt similarity index 89% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt index b4204686..d1ab971e 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -12,10 +12,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun Divider( @@ -49,7 +48,6 @@ enum class DividerType { VERTICAL, } -@PreviewLightDark @Composable private fun DividerPreview() { KrailTheme { @@ -65,7 +63,6 @@ private fun DividerPreview() { } } -@PreviewLightDark @Composable private fun DividerVerticalPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt similarity index 65% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt index 678fb409..b9545cef 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt @@ -1,6 +1,5 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -12,16 +11,11 @@ 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.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.LocalOnContentColor -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.ButtonTokens.RoundButtonSize +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.LocalOnContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.ButtonTokens.RoundButtonSize /** * A round icon button with customizable content and colors. @@ -62,18 +56,4 @@ fun RoundIconButton( // region Previews -@PreviewComponent -@Composable -private fun RoundIconButtonPreview() { - KrailTheme { - RoundIconButton(onClick = {}) { - Image( - imageVector = ImageVector.vectorResource(R.drawable.star_outline), - contentDescription = null, - colorFilter = ColorFilter.tint(LocalOnContentColor.current), - ) - } - } -} - // endregion diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt similarity index 80% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt index 22eef7d9..c3037446 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -10,9 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveSize +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveSize @Composable fun SeparatorIcon(modifier: Modifier = Modifier, color: Color = KrailTheme.colors.onSurface) { @@ -27,7 +26,6 @@ fun SeparatorIcon(modifier: Modifier = Modifier, color: Color = KrailTheme.color // region Previews -@PreviewComponent @Composable private fun SeparatorIconPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt similarity index 89% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt index e76467d7..1709f38f 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -13,11 +13,10 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun Text( @@ -79,7 +78,6 @@ fun Text( // region Previews -@PreviewComponent @Composable private fun TextPreview() { KrailTheme { @@ -92,7 +90,6 @@ private fun TextPreview() { } } -@PreviewComponent @Composable private fun TextWithColorPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt similarity index 90% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt index 4b36d631..0b533aff 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource @@ -30,16 +30,15 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.LocaleList -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.PlaceholderOpacity -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextFieldHeight -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextSelectionBackgroundOpacity +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.TextFieldTokens +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.PlaceholderOpacity +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextSelectionBackgroundOpacity /** * Important documentation links: @@ -147,7 +146,7 @@ private fun TextFieldPlaceholder(placeholder: String? = null) { // region Previews -@PreviewLightDark + @Composable private fun TextFieldEnabledPreview() { KrailTheme { @@ -155,7 +154,6 @@ private fun TextFieldEnabledPreview() { } } -@PreviewLightDark @Composable private fun TextFieldEnabledPlaceholderPreview() { KrailTheme { @@ -163,7 +161,6 @@ private fun TextFieldEnabledPlaceholderPreview() { } } -@PreviewLightDark @Composable private fun TextFieldDisabledPreview() { KrailTheme { @@ -171,7 +168,6 @@ private fun TextFieldDisabledPreview() { } } -@PreviewLightDark @Composable private fun TextFieldDisabledPlaceholderPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt similarity index 73% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt index bffec20c..26a9105d 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -14,13 +14,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.TextFieldTokens +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextFieldHeight /** * A button that looks like a text field. @@ -59,17 +58,3 @@ fun TextFieldButton( } } } - -// region Previews - -@PreviewComponent -@Composable -private fun TextFieldButtonPreview() { - KrailTheme { - TextFieldButton(onClick = {}) { - Text(text = "Search") - } - } -} - -// endregion diff --git a/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt new file mode 100644 index 00000000..c0ad1772 --- /dev/null +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt @@ -0,0 +1,64 @@ +package xyz.ksharma.krail.taj.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme + +@Composable +fun TitleBar( + title: @Composable () -> Unit, + modifier: Modifier = Modifier, + navAction: @Composable (() -> Unit)? = null, + actions: @Composable (() -> Unit)? = null, +) { + Row( + modifier = modifier + .statusBarsPadding() + .fillMaxWidth() + .heightIn(min = 56.dp) + .padding(end = 16.dp, start = 8.dp) + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + navAction?.let { + navAction() + } + Row( + modifier = Modifier + .weight(1f) + .padding(start = 10.dp), + ) { + CompositionLocalProvider( + LocalTextColor provides KrailTheme.colors.onSurface, + LocalTextStyle provides KrailTheme.typography.headlineMedium, + ) { + title() + } + } + actions?.let { + Row( + modifier = Modifier.padding(start = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + CompositionLocalProvider( + LocalContentColor provides KrailTheme.colors.onSurface, + ) { + actions() + } + } + } + } +} diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt similarity index 95% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt index 3d61e68c..7383239f 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt similarity index 98% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt index 6e344775..b0114d34 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt similarity index 99% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt index 4b70f5dd..2d6c462a 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt similarity index 94% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt index 1845d6f3..28f50b46 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt similarity index 68% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt index b28eeaf5..a88a62e8 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.tokens +package xyz.ksharma.krail.taj.tokens import androidx.compose.ui.unit.dp diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt similarity index 83% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt index 35f1e697..66e2e13c 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.tokens +package xyz.ksharma.krail.taj.tokens import androidx.compose.ui.unit.dp