From 1f07f153b23fa9a7ae0ea648b498dad60f96c594 Mon Sep 17 00:00:00 2001 From: JustZvan Date: Wed, 8 Apr 2026 19:41:36 +0200 Subject: feat: 1.2 --- .../main/java/sh/lajo/buddy/OnboardingScreens.kt | 128 ++++++++++++++++++--- 1 file changed, 109 insertions(+), 19 deletions(-) (limited to 'app/src/main/java/sh/lajo/buddy/OnboardingScreens.kt') diff --git a/app/src/main/java/sh/lajo/buddy/OnboardingScreens.kt b/app/src/main/java/sh/lajo/buddy/OnboardingScreens.kt index d1b2378..2a29079 100644 --- a/app/src/main/java/sh/lajo/buddy/OnboardingScreens.kt +++ b/app/src/main/java/sh/lajo/buddy/OnboardingScreens.kt @@ -53,9 +53,8 @@ import sh.lajo.buddy.ApiConfig.BASE_URL import sh.lajo.buddy.HttpClient.JSON @Serializable -data class LoginRequest( - val email: String, - val password: String +data class KidLinkRequest( + val code: String, ) @Serializable @@ -455,13 +454,90 @@ fun OnboardingStep6Screen( ) } +@Composable +fun OnboardingStepMediaScreen( + onBack: () -> Unit, + onNext: () -> Unit +) { + val context = LocalContext.current + var isMediaGranted by remember { mutableStateOf(isMediaPermissionGranted(context)) } + + val mediaPermissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + isMediaGranted = isGranted + } + + // Use LifecycleObserver to refresh state when returning from settings + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_RESUME) { + isMediaGranted = isMediaPermissionGranted(context) + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + + OnboardingScaffold( + title = stringResource(R.string.onboarding_step_media_title), + body = stringResource(R.string.onboarding_step_media_body), + primaryButtonText = stringResource(R.string.onboarding_next), + onPrimary = onNext, + primaryEnabled = isMediaGranted, + showBack = true, + onBack = onBack, + extraContent = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = stringResource( + R.string.onboarding_media_permission_status, + if (isMediaGranted) stringResource(R.string.onboarding_permission_granted) + else stringResource(R.string.onboarding_permission_not_granted) + ), + style = MaterialTheme.typography.bodyMedium + ) + + if (!isMediaGranted) { + Button( + onClick = { + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE + } + mediaPermissionLauncher.launch(permission) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.onboarding_grant_media_permission)) + } + } + + if (!isMediaGranted) { + Spacer(Modifier.height(12.dp)) + Text( + stringResource(R.string.onboarding_grant_media_to_continue), + style = MaterialTheme.typography.bodySmall, + ) + } + } + } + ) +} + @Composable fun OnboardingStep7Screen( onFinish: () -> Unit, onBack: () -> Unit ) { - var email by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } + var code by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(false) } var resultText by remember { mutableStateOf(null) } @@ -473,7 +549,7 @@ fun OnboardingStep7Screen( OnboardingScaffold( title = stringResource(R.string.onboarding_step7_title), body = stringResource(R.string.onboarding_step7_body), - primaryButtonText = if (isLoading) stringResource(R.string.onboarding_logging_in) else stringResource(R.string.onboarding_login), + primaryButtonText = if (isLoading) stringResource(R.string.onboarding_linking) else stringResource(R.string.onboarding_link_device), onPrimary = { if (isLoading) return@OnboardingScaffold @@ -485,7 +561,8 @@ fun OnboardingStep7Screen( try { val client = HttpClient.client - val jsonBody = networkJson.encodeToString(LoginRequest(email, password)) + val normalizedCode = code.trim().uppercase() + val jsonBody = networkJson.encodeToString(KidLinkRequest(normalizedCode)) val body = jsonBody.toRequestBody(JSON) val request = Request.Builder() @@ -545,17 +622,20 @@ fun OnboardingStep7Screen( onBack = onBack, extraContent = { OutlinedTextField( - value = email, - onValueChange = { email = it }, - label = { Text(stringResource(R.string.onboarding_email)) }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading, - ) - OutlinedTextField( - value = password, - onValueChange = { password = it }, - label = { Text(stringResource(R.string.onboarding_password)) }, + value = code, + onValueChange = { input -> + val sanitized = input.uppercase().filter { it.isLetterOrDigit() } + val withDash = buildString { + sanitized.take(6).forEachIndexed { index, c -> + if (index == 3) { + append('-') + } + append(c) + } + } + code = withDash + }, + label = { Text(stringResource(R.string.onboarding_link_code)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !isLoading, @@ -675,6 +755,16 @@ private fun isContactsPermissionGranted(context: Context): Boolean { android.content.pm.PackageManager.PERMISSION_GRANTED } +private fun isMediaPermissionGranted(context: Context): Boolean { + val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE + } + return context.checkSelfPermission(permission) == + android.content.pm.PackageManager.PERMISSION_GRANTED +} + private fun isVpnActive(context: Context): Boolean { val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return false @@ -690,4 +780,4 @@ private fun safeStartActivity(context: Context, intent: Intent) { if (safeIntent.resolveActivity(context.packageManager) != null) { context.startActivity(safeIntent) } -} \ No newline at end of file +} -- cgit v1.2.3