feat(android): migrate auth, telemetry, feature flags to ByteLyst Kotlin Platform SDK
This commit is contained in:
parent
b7688b55d1
commit
6b82ca1b33
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<List<CMTimer>>(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
|
||||
|
||||
@ -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(":"))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user