Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ceb852d
Implement height selection step in profile setup onboarding
dbAbstract May 15, 2026
e62d2fe
Update Sex enum cases and migrate Compose previews to YaseyoPreview
dbAbstract May 15, 2026
015a2a3
Update name in `NameStep` filled preview from "Taki" to "Alex"
dbAbstract May 15, 2026
e4f93ad
Add current weight and activity level steps to onboarding process
dbAbstract May 15, 2026
846b077
Implement goal weight suggestion in onboarding flow
dbAbstract May 15, 2026
5a0b0fd
Rename ModalRoute to RouteMarkers and add AppRootRoute interface
dbAbstract May 15, 2026
a9fa2e9
Implement summary step in onboarding profile setup
dbAbstract May 15, 2026
9d9ec70
Add onboarding enum mappings and update ProfileSetupViewModel network…
dbAbstract May 15, 2026
c312f56
Refactor onboarding data models and move mapping logic to the network…
dbAbstract May 15, 2026
2a56229
Add libs:datastore module and integrate with Koin di module
dbAbstract May 15, 2026
538aee3
Implement local data source and repository for onboarding draft persi…
dbAbstract May 15, 2026
179abef
Add coroutines-testing module and implement Onboarding tests
dbAbstract May 15, 2026
43d33e2
Implement clear backstack navigation and update onboarding flow
dbAbstract May 15, 2026
3c7dffe
Refine onboarding UI and navigation bottom sheet styles
dbAbstract May 15, 2026
8bf3ff5
Implement user creation and profile submission in ProfileSetupViewModel
dbAbstract May 17, 2026
2245185
Extract `format1dp` extension to `FormatUtils.kt` and optimize `Wheel…
dbAbstract May 17, 2026
1758bb3
Refactor WheelPicker and update WheelDatePicker selection UI
dbAbstract May 18, 2026
67877ee
Add feature:home module and update android target in libs:auth
dbAbstract Jul 19, 2026
45c5927
Configure cleartext traffic for Android debug and update local baseUr…
dbAbstract Jul 19, 2026
ad93979
formatting
dbAbstract Jul 19, 2026
50e2bd0
Refactor onboarding steps by splitting `AboutYouStep` into separate `…
dbAbstract Jul 19, 2026
c830cb6
Add HttpResponseValidator to NetworkModule and fix User sex and date …
dbAbstract Jul 19, 2026
8b9b384
Add FakeUserRepository and update onboarding steps in tests
dbAbstract Jul 21, 2026
f54b8b6
Remove authModule and update DI to use authImplModule directly
dbAbstract Jul 21, 2026
006a053
Remove userModule and migrate Koin initialization to use userImplModu…
dbAbstract Jul 21, 2026
edb3766
Remove UserDependencies helper from libs:user iosMain
dbAbstract Jul 21, 2026
a1f7069
Set sourceSetTreeName for host tests and update ProfileSetupViewModel…
dbAbstract Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ captures
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings
node_modules/
CLAUDE.md
CLAUDE.md
.hotswan/
6 changes: 6 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets {
getByName("debug") {
manifest.srcFile("src/androidDebug/AndroidManifest.xml")
res.srcDirs("src/androidDebug/res")
}
}
}

dependencies {
Expand Down
9 changes: 9 additions & 0 deletions composeApp/src/androidDebug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:networkSecurityConfig="@xml/network_security_config"
tools:node="merge" />

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.0.2.2</domain>
</domain-config>
</network-security-config>
1 change: 1 addition & 0 deletions composeApp/src/androidMain/kotlin/dev/yaseyo/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fun App(navigator: Navigator) =
appRouter.events.collect { event ->
when (event) {
is NavigationEvent.GoTo -> navigator.goTo(event.route)
is NavigationEvent.ClearBackStackAndGoTo -> navigator.clearAndGoTo(event.route)
is NavigationEvent.GoBack -> navigator.goBack()
}
}
Expand Down
6 changes: 5 additions & 1 deletion composeApp/src/androidMain/kotlin/dev/yaseyo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.yaseyo.navigation.FinishHandler
import dev.yaseyo.navigation.Navigator
import org.koin.android.scope.AndroidScopeComponent
import org.koin.androidx.scope.activityRetainedScope
Expand All @@ -19,7 +20,8 @@ import org.koin.core.scope.Scope

class MainActivity :
ComponentActivity(),
AndroidScopeComponent {
AndroidScopeComponent,
FinishHandler {
override val scope: Scope by activityRetainedScope()

private val viewModel: MainViewModel by viewModel()
Expand All @@ -30,6 +32,8 @@ class MainActivity :
enableEdgeToEdge()
super.onCreate(savedInstanceState)

scope.declare(this as FinishHandler, allowOverride = true)

splashScreen.setKeepOnScreenCondition {
viewModel.startDestination.value == null
}
Expand Down
74 changes: 74 additions & 0 deletions feature/home/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
plugins {
id("yaseyo.kmp.library")
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}

kotlin {
android {
namespace = "dev.yaseyo.home"

withHostTestBuilder {
sourceSetTreeName = "test"
}

withDeviceTestBuilder {
}.configure {
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}

sourceSets {
commonMain {
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.koin.core)
implementation(libs.koin.core.viewmodel)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
api(libs.kotlinx.coroutines.core)

implementation(projects.libs.coroutines)
api(projects.libs.navigation)
implementation(projects.libs.design)

implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
implementation(libs.compose.uiToolingPreview)
implementation(libs.compose.material.icons.extended)
implementation(libs.compose.navigationevent)
}
}

commonTest {
dependencies {
implementation(libs.kotlin.test)
implementation(libs.turbine)
implementation(projects.libs.coroutines.testing)
}
}

androidMain {
dependencies {
implementation(libs.koin.android)
implementation(libs.koin.compose.nav3)
implementation(libs.androidx.ui.tooling)
}
}

getByName("androidDeviceTest") {
dependencies {
implementation(libs.androidx.runner)
implementation(libs.androidx.core)
implementation(libs.androidx.testExt.junit)
}
}

iosMain {
dependencies {
}
}
}
}
6 changes: 5 additions & 1 deletion feature/onboarding/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ kotlin {
namespace = "dev.yaseyo.onboarding"

withHostTestBuilder {
sourceSetTreeName = "test"
}

withDeviceTestBuilder {
sourceSetTreeName = "test"
}.configure {
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -31,8 +31,10 @@ kotlin {

implementation(projects.libs.auth)
implementation(projects.libs.user)
implementation(projects.libs.coroutines)
api(projects.libs.navigation)
implementation(projects.libs.design)
implementation(projects.libs.datastore)

implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.compose.runtime)
Expand All @@ -47,6 +49,8 @@ kotlin {
commonTest {
dependencies {
implementation(libs.kotlin.test)
implementation(libs.turbine)
implementation(projects.libs.coroutines.testing)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.yaseyo.onboarding.datasource

import dev.yaseyo.onboarding.model.OnboardingDraft
import kotlinx.coroutines.flow.Flow

internal interface OnboardingLocalDataSource {
val draft: Flow<OnboardingDraft>

suspend fun saveDraft(draft: OnboardingDraft)

suspend fun clearDraft()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dev.yaseyo.onboarding.datasource

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import dev.yaseyo.onboarding.model.OnboardingDraft
import dev.yaseyo.user.api.ActivityLevel
import dev.yaseyo.user.api.Sex
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

internal class OnboardingLocalDataSourceImpl(
private val dataStore: DataStore<Preferences>,
) : OnboardingLocalDataSource {
override val draft: Flow<OnboardingDraft> = dataStore.data.map { prefs ->
OnboardingDraft(
currentStepIndex = prefs[Keys.CURRENT_STEP_INDEX] ?: 0,
name = prefs[Keys.NAME] ?: "",
sex = prefs[Keys.SEX]?.toSex(),
dobDay = prefs[Keys.DOB_DAY] ?: 1,
dobMonth = prefs[Keys.DOB_MONTH] ?: 1,
dobYear = prefs[Keys.DOB_YEAR] ?: 1990,
heightCm = prefs[Keys.HEIGHT_CM] ?: 170,
currentWeightKg = prefs[Keys.CURRENT_WEIGHT_KG] ?: 70,
activityLevel = prefs[Keys.ACTIVITY_LEVEL]?.toActivityLevel(),
targetWeightKg = prefs[Keys.TARGET_WEIGHT_KG] ?: 70,
dailyDeficitKcal = prefs[Keys.DAILY_DEFICIT_KCAL] ?: 500,
)
}

override suspend fun saveDraft(draft: OnboardingDraft) {
dataStore.edit { prefs ->
prefs[Keys.CURRENT_STEP_INDEX] = draft.currentStepIndex
prefs[Keys.NAME] = draft.name
draft.sex?.let { prefs[Keys.SEX] = it.toStorageValue() } ?: prefs.remove(Keys.SEX)
prefs[Keys.DOB_DAY] = draft.dobDay
prefs[Keys.DOB_MONTH] = draft.dobMonth
prefs[Keys.DOB_YEAR] = draft.dobYear
prefs[Keys.HEIGHT_CM] = draft.heightCm
prefs[Keys.CURRENT_WEIGHT_KG] = draft.currentWeightKg
draft.activityLevel?.let { prefs[Keys.ACTIVITY_LEVEL] = it.toStorageValue() } ?: prefs.remove(Keys.ACTIVITY_LEVEL)
prefs[Keys.TARGET_WEIGHT_KG] = draft.targetWeightKg
prefs[Keys.DAILY_DEFICIT_KCAL] = draft.dailyDeficitKcal
}
}

override suspend fun clearDraft() {
dataStore.edit { it.clear() }
}

private fun String.toSex(): Sex? =
when (this) {
"MALE" -> Sex.Male
"FEMALE" -> Sex.Female
else -> null
}

private fun String.toActivityLevel(): ActivityLevel? =
when (this) {
"SEDENTARY" -> ActivityLevel.Sedentary
"LIGHTLY_ACTIVE" -> ActivityLevel.LightlyActive
else -> null
}

private fun Sex.toStorageValue() =
when (this) {
Sex.Male -> "MALE"
Sex.Female -> "FEMALE"
}

private fun ActivityLevel.toStorageValue() =
when (this) {
ActivityLevel.Sedentary -> "SEDENTARY"
ActivityLevel.LightlyActive -> "LIGHTLY_ACTIVE"
}

private object Keys {
val CURRENT_STEP_INDEX = intPreferencesKey("current_step_index")
val NAME = stringPreferencesKey("name")
val SEX = stringPreferencesKey("sex")
val DOB_DAY = intPreferencesKey("dob_day")
val DOB_MONTH = intPreferencesKey("dob_month")
val DOB_YEAR = intPreferencesKey("dob_year")
val HEIGHT_CM = intPreferencesKey("height_cm")
val CURRENT_WEIGHT_KG = intPreferencesKey("current_weight_kg")
val ACTIVITY_LEVEL = stringPreferencesKey("activity_level")
val TARGET_WEIGHT_KG = intPreferencesKey("target_weight_kg")
val DAILY_DEFICIT_KCAL = intPreferencesKey("daily_deficit_kcal")
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package dev.yaseyo.onboarding.di

import dev.yaseyo.onboarding.ResolveStartDestinationUseCase
import dev.yaseyo.onboarding.datasource.OnboardingLocalDataSource
import dev.yaseyo.onboarding.datasource.OnboardingLocalDataSourceImpl
import dev.yaseyo.onboarding.network.OnboardingApi
import dev.yaseyo.onboarding.network.OnboardingApiImpl
import dev.yaseyo.onboarding.repository.OnboardingRepository
import dev.yaseyo.onboarding.repository.OnboardingRepositoryImpl
import dev.yaseyo.onboarding.ui.auth.AuthViewModel
import dev.yaseyo.onboarding.ui.profilesetup.ProfileSetupViewModel
import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.bind
import org.koin.dsl.module

val onboardingModule = module {
factoryOf(::ResolveStartDestinationUseCase)
viewModelOf(::AuthViewModel)

single<OnboardingApi> { OnboardingApiImpl(client = get()) }
single { OnboardingLocalDataSourceImpl(dataStore = get()) } bind OnboardingLocalDataSource::class
single { OnboardingRepositoryImpl(localDataSource = get(), api = get()) } bind OnboardingRepository::class
viewModelOf(::ProfileSetupViewModel)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.yaseyo.onboarding.model

import dev.yaseyo.user.api.ActivityLevel

internal data class ActivityLevelOption(
val level: ActivityLevel,
val label: String,
val description: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.yaseyo.onboarding.model

internal data class GoalSuggestion(
val currentBmi: Double,
val currentBodyFatPct: Double?,
val currentBodyFatCategory: String?,
val suggestedTargetKg: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.yaseyo.onboarding.model

import dev.yaseyo.user.api.ActivityLevel
import dev.yaseyo.user.api.Sex

internal data class OnboardingDraft(
val currentStepIndex: Int = 0,
val name: String = "",
val sex: Sex? = null,
val dobDay: Int = 1,
val dobMonth: Int = 1,
val dobYear: Int = 1990,
val heightCm: Int = 170,
val currentWeightKg: Int = 70,
val activityLevel: ActivityLevel? = null,
val targetWeightKg: Int = 70,
val dailyDeficitKcal: Int = 500,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.yaseyo.onboarding.model

internal data class OnboardingSummary(
val tdee: Int,
val dailyCalorieTarget: Int,
val weeklyLossKg: Double,
val proteinG: Int,
val carbsG: Int,
val fatG: Int,
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package dev.yaseyo.onboarding.navigation

import dev.yaseyo.navigation.AppRootRoute
import dev.yaseyo.navigation.AppRoute
import dev.yaseyo.navigation.ModalRoute

sealed interface OnboardingRoutes : AppRoute {
data object Auth : OnboardingRoutes {
data object Landing : OnboardingRoutes
data object Landing : OnboardingRoutes, AppRootRoute

data object SignIn : OnboardingRoutes, ModalRoute

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.yaseyo.onboarding.network

import kotlinx.serialization.Serializable

@Serializable
internal data class ActivityLevelNet(
val level: String,
val label: String,
val description: String,
val multiplier: Double,
)
Loading