From 6b82ca1b333a579327746bd86558e5bc40ab6a0e Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 1 Mar 2026 18:16:13 -0800 Subject: [PATCH] feat(android): migrate auth, telemetry, feature flags to ByteLyst Kotlin Platform SDK --- android/app/build.gradle.kts | 3 + .../java/com/chronomind/app/MainActivity.kt | 12 ++- .../com/chronomind/app/auth/LoginScreen.kt | 17 ++-- .../java/com/chronomind/app/di/AppModule.kt | 19 ----- .../com/chronomind/app/platform/Config.kt | 25 ++++++ .../chronomind/app/platform/PlatformModule.kt | 79 +++++++++++++++++++ .../app/ui/screens/SettingsScreen.kt | 13 ++- .../app/viewmodel/TimerViewModel.kt | 12 +-- android/settings.gradle.kts | 7 ++ 9 files changed, 140 insertions(+), 47 deletions(-) create mode 100644 android/app/src/main/java/com/chronomind/app/platform/Config.kt create mode 100644 android/app/src/main/java/com/chronomind/app/platform/PlatformModule.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e7c9e1f..6580e3f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -76,6 +76,9 @@ dependencies { implementation(libs.androidx.glance) implementation(libs.androidx.glance.material3) + // ByteLyst Platform SDK (via Gradle includeBuild in settings.gradle.kts) + implementation("com.bytelyst.platform:kotlin-platform-sdk") + // Kotlin implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) diff --git a/android/app/src/main/java/com/chronomind/app/MainActivity.kt b/android/app/src/main/java/com/chronomind/app/MainActivity.kt index 4548601..6856bf6 100644 --- a/android/app/src/main/java/com/chronomind/app/MainActivity.kt +++ b/android/app/src/main/java/com/chronomind/app/MainActivity.kt @@ -9,8 +9,7 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.chronomind.app.auth.AuthService -import com.chronomind.app.auth.AuthState +import com.bytelyst.platform.BLAuthClient import com.chronomind.app.auth.LoginScreen import com.chronomind.app.ui.navigation.ChronoMindNavHost import com.chronomind.app.ui.theme.CMColors @@ -20,17 +19,16 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { - @Inject lateinit var authService: AuthService + @Inject lateinit var authClient: BLAuthClient override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - authService.checkExistingSession() setContent { ChronoMindTheme { - val authState by authService.state.collectAsState() - if (authState is AuthState.LoggedIn) { + val authState by authClient.state.collectAsState() + if (authState is BLAuthClient.AuthState.LoggedIn) { Surface( modifier = Modifier.fillMaxSize(), color = CMColors.bg @@ -38,7 +36,7 @@ class MainActivity : ComponentActivity() { ChronoMindNavHost() } } else { - LoginScreen(authService = authService) + LoginScreen(authClient = authClient) } } } diff --git a/android/app/src/main/java/com/chronomind/app/auth/LoginScreen.kt b/android/app/src/main/java/com/chronomind/app/auth/LoginScreen.kt index 73fa1e9..21f6760 100644 --- a/android/app/src/main/java/com/chronomind/app/auth/LoginScreen.kt +++ b/android/app/src/main/java/com/chronomind/app/auth/LoginScreen.kt @@ -37,12 +37,13 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.bytelyst.platform.BLAuthClient import com.chronomind.app.ui.theme.CMColors import kotlinx.coroutines.launch @Composable -fun LoginScreen(authService: AuthService) { - val authState by authService.state.collectAsState() +fun LoginScreen(authClient: BLAuthClient) { + val authState by authClient.state.collectAsState() val scope = rememberCoroutineScope() var isRegister by remember { mutableStateOf(false) } var name by remember { mutableStateOf("") } @@ -56,7 +57,7 @@ fun LoginScreen(authService: AuthService) { && password.any { it.isLowerCase() } && password.any { it.isDigit() } val isValidName = !isRegister || name.trim().isNotEmpty() - val canSubmit = isValidEmail && isValidPassword && isValidName && authState !is AuthState.Loading + val canSubmit = isValidEmail && isValidPassword && isValidName && authState !is BLAuthClient.AuthState.Loading val fieldColors = OutlinedTextFieldDefaults.colors( focusedTextColor = CMColors.text, @@ -150,10 +151,10 @@ fun LoginScreen(authService: AuthService) { ) } - if (authState is AuthState.Error) { + if (authState is BLAuthClient.AuthState.Error) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = (authState as AuthState.Error).message, + text = (authState as BLAuthClient.AuthState.Error).message, color = CMColors.error, style = MaterialTheme.typography.bodySmall, ) @@ -164,8 +165,8 @@ fun LoginScreen(authService: AuthService) { Button( onClick = { scope.launch { - if (isRegister) authService.register(name, email, password) - else authService.login(email, password) + if (isRegister) authClient.register(name, email, password) + else authClient.login(email, password) } }, modifier = Modifier @@ -175,7 +176,7 @@ fun LoginScreen(authService: AuthService) { enabled = canSubmit, colors = ButtonDefaults.buttonColors(containerColor = CMColors.accent), ) { - if (authState is AuthState.Loading) { + if (authState is BLAuthClient.AuthState.Loading) { CircularProgressIndicator( modifier = Modifier.size(20.dp), strokeWidth = 2.dp, diff --git a/android/app/src/main/java/com/chronomind/app/di/AppModule.kt b/android/app/src/main/java/com/chronomind/app/di/AppModule.kt index e2adcfb..54b4e13 100644 --- a/android/app/src/main/java/com/chronomind/app/di/AppModule.kt +++ b/android/app/src/main/java/com/chronomind/app/di/AppModule.kt @@ -6,8 +6,6 @@ import com.chronomind.app.data.TimerDao import com.chronomind.app.data.TimerDatabase import com.chronomind.app.notifications.TimerNotificationManager import com.chronomind.app.sync.SyncRepository -import com.chronomind.app.telemetry.FeatureFlagService -import com.chronomind.app.telemetry.TelemetryService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -54,21 +52,4 @@ object AppModule { } } - @Provides - @Singleton - fun provideTelemetryService( - @ApplicationContext context: Context - ): TelemetryService { - return TelemetryService(context).also { - it.start() - } - } - - @Provides - @Singleton - fun provideFeatureFlagService( - @ApplicationContext context: Context - ): FeatureFlagService { - return FeatureFlagService(context) - } } diff --git a/android/app/src/main/java/com/chronomind/app/platform/Config.kt b/android/app/src/main/java/com/chronomind/app/platform/Config.kt new file mode 100644 index 0000000..d71391a --- /dev/null +++ b/android/app/src/main/java/com/chronomind/app/platform/Config.kt @@ -0,0 +1,25 @@ +package com.chronomind.app.platform + +import android.content.Context +import com.bytelyst.platform.BLPlatformConfig + +/** + * ChronoMind platform configuration. + * + * Creates a [BLPlatformConfig] with ChronoMind-specific values. + * All SDK components receive this config via Hilt DI. + */ +object Config { + fun create(context: Context): BLPlatformConfig { + val baseUrl = context.applicationInfo.metaData?.getString("PLATFORM_SERVICE_URL") + ?: "https://api.chronomind.app" + + return BLPlatformConfig( + productId = "chronomind", + baseUrl = "$baseUrl/api", + platform = "android", + channel = "native", + applicationId = "com.chronomind.app", + ) + } +} diff --git a/android/app/src/main/java/com/chronomind/app/platform/PlatformModule.kt b/android/app/src/main/java/com/chronomind/app/platform/PlatformModule.kt new file mode 100644 index 0000000..12fa5e3 --- /dev/null +++ b/android/app/src/main/java/com/chronomind/app/platform/PlatformModule.kt @@ -0,0 +1,79 @@ +package com.chronomind.app.platform + +import android.content.Context +import com.bytelyst.platform.BLAuthClient +import com.bytelyst.platform.BLFeatureFlagClient +import com.bytelyst.platform.BLKillSwitchClient +import com.bytelyst.platform.BLPlatformConfig +import com.bytelyst.platform.BLSecureStore +import com.bytelyst.platform.BLTelemetryClient +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** + * Hilt module providing ByteLyst Platform SDK components. + * + * Replaces the hand-rolled AuthService, TelemetryService, and FeatureFlagService + * with SDK-backed implementations. + */ +@Module +@InstallIn(SingletonComponent::class) +object PlatformModule { + + @Provides + @Singleton + fun provideConfig(@ApplicationContext context: Context): BLPlatformConfig { + return Config.create(context) + } + + @Provides + @Singleton + fun provideSecureStore( + @ApplicationContext context: Context, + config: BLPlatformConfig, + ): BLSecureStore { + return BLSecureStore(context, config.applicationId) + } + + @Provides + @Singleton + fun provideAuthClient( + config: BLPlatformConfig, + secureStore: BLSecureStore, + ): BLAuthClient { + return BLAuthClient(config, secureStore).also { + it.checkExistingSession() + } + } + + @Provides + @Singleton + fun provideTelemetryClient( + config: BLPlatformConfig, + @ApplicationContext context: Context, + ): BLTelemetryClient { + return BLTelemetryClient(config, context).also { + it.start() + } + } + + @Provides + @Singleton + fun provideFeatureFlagClient( + config: BLPlatformConfig, + ): BLFeatureFlagClient { + return BLFeatureFlagClient(config) + } + + @Provides + @Singleton + fun provideKillSwitchClient( + config: BLPlatformConfig, + ): BLKillSwitchClient { + return BLKillSwitchClient(config) + } +} diff --git a/android/app/src/main/java/com/chronomind/app/ui/screens/SettingsScreen.kt b/android/app/src/main/java/com/chronomind/app/ui/screens/SettingsScreen.kt index 9e478a0..d6dc51c 100644 --- a/android/app/src/main/java/com/chronomind/app/ui/screens/SettingsScreen.kt +++ b/android/app/src/main/java/com/chronomind/app/ui/screens/SettingsScreen.kt @@ -11,8 +11,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel -import com.chronomind.app.auth.AuthService -import com.chronomind.app.auth.AuthState +import com.bytelyst.platform.BLAuthClient import com.chronomind.app.engine.CascadePreset import com.chronomind.app.engine.UrgencyLevel import com.chronomind.app.engine.getUrgencyConfig @@ -22,12 +21,12 @@ import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( - val authService: AuthService, + val authClient: BLAuthClient, ) : ViewModel() @Composable fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { - val authState by viewModel.authService.state.collectAsState() + val authState by viewModel.authClient.state.collectAsState() var defaultUrgency by remember { mutableStateOf(UrgencyLevel.STANDARD) } var defaultCascade by remember { mutableStateOf(CascadePreset.STANDARD) } var hapticEnabled by remember { mutableStateOf(true) } @@ -50,8 +49,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { // Account SettingsSection("Account") { - if (authState is AuthState.LoggedIn) { - val user = (authState as AuthState.LoggedIn).user + if (authState is BLAuthClient.AuthState.LoggedIn) { + val user = (authState as BLAuthClient.AuthState.LoggedIn).user SettingsRow("Email") { Text(user.email, color = CMColors.textMuted, fontSize = 14.sp) } @@ -64,7 +63,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) { .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.End, ) { - TextButton(onClick = { viewModel.authService.logout() }) { + TextButton(onClick = { viewModel.authClient.logout() }) { Text("Sign Out", color = CMColors.error) } } diff --git a/android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt b/android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt index 9bbf898..3588840 100644 --- a/android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt +++ b/android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt @@ -7,7 +7,7 @@ import com.chronomind.app.data.toEntity import com.chronomind.app.data.toTimer import com.chronomind.app.engine.* import com.chronomind.app.sync.SyncRepository -import com.chronomind.app.telemetry.TelemetryService +import com.bytelyst.platform.BLTelemetryClient import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -23,7 +23,7 @@ import javax.inject.Inject class TimerViewModel @Inject constructor( private val timerDao: TimerDao, private val syncRepository: SyncRepository, - private val telemetryService: TelemetryService, + private val telemetryClient: BLTelemetryClient, ) : ViewModel() { private val _timers = MutableStateFlow>(emptyList()) @@ -70,7 +70,7 @@ class TimerViewModel @Inject constructor( _timers.value = _timers.value + timer persist(timer) if (syncRepository.syncEnabled) syncRepository.enqueueCreate(timer) - telemetryService.trackTimer("timer_created", tags = mapOf("type" to "countdown")) + telemetryClient.trackEvent("info", "timers", "timer_created", tags = mapOf("type" to "countdown")) } fun addAlarm(label: String, targetTime: Date, urgency: UrgencyLevel = UrgencyLevel.STANDARD) { @@ -78,7 +78,7 @@ class TimerViewModel @Inject constructor( _timers.value = _timers.value + timer persist(timer) if (syncRepository.syncEnabled) syncRepository.enqueueCreate(timer) - telemetryService.trackTimer("timer_created", tags = mapOf("type" to "alarm", "urgency" to urgency.name)) + telemetryClient.trackEvent("info", "timers", "timer_created", tags = mapOf("type" to "alarm", "urgency" to urgency.name)) } fun addPomodoro(label: String = "Focus Session", config: PomodoroConfig = PomodoroConfig()) { @@ -86,7 +86,7 @@ class TimerViewModel @Inject constructor( _timers.value = _timers.value + timer persist(timer) if (syncRepository.syncEnabled) syncRepository.enqueueCreate(timer) - telemetryService.trackTimer("timer_created", tags = mapOf("type" to "pomodoro")) + telemetryClient.trackEvent("info", "timers", "timer_created", tags = mapOf("type" to "pomodoro")) } fun pause(id: String) { @@ -118,7 +118,7 @@ class TimerViewModel @Inject constructor( _timers.value = _timers.value.filter { it.id != id } viewModelScope.launch { timerDao.deleteById(id) } if (syncRepository.syncEnabled) syncRepository.enqueueDelete(id) - telemetryService.trackTimer("timer_deleted") + telemetryClient.trackEvent("info", "timers", "timer_deleted") } // MARK: - Tick diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 66dfa75..676a675 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -22,3 +22,10 @@ dependencyResolution { rootProject.name = "ChronoMind" include(":app") include(":wear") + +// ByteLyst Platform SDK — local composite build from sibling repo +includeBuild("../../learning_ai_common_plat/packages/kotlin-platform-sdk") { + dependencySubstitution { + substitute(module("com.bytelyst.platform:kotlin-platform-sdk")).using(project(":")) + } +}