@@ -9,6 +9,7 @@ import androidx.compose.foundation.clickable
99import androidx.compose.foundation.layout.*
1010import androidx.compose.foundation.rememberScrollState
1111import androidx.compose.foundation.shape.RoundedCornerShape
12+ import androidx.compose.foundation.text.KeyboardOptions
1213import androidx.compose.foundation.verticalScroll
1314import androidx.compose.material.icons.Icons
1415import androidx.compose.material.icons.filled.*
@@ -19,8 +20,10 @@ import androidx.compose.ui.Alignment
1920import androidx.compose.ui.Modifier
2021import androidx.compose.ui.graphics.Color
2122import androidx.compose.ui.platform.LocalContext
22- import androidx.compose.ui.platform.LocalUriHandler
2323import androidx.compose.ui.text.font.FontWeight
24+ import androidx.compose.ui.text.input.KeyboardType
25+ import androidx.compose.ui.text.input.PasswordVisualTransformation
26+ import androidx.compose.ui.text.input.VisualTransformation
2427import androidx.compose.ui.unit.dp
2528import androidx.lifecycle.compose.collectAsStateWithLifecycle
2629import androidx.lifecycle.viewmodel.compose.viewModel
@@ -69,6 +72,48 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
6972 )
7073 }
7174
75+ // API Configuration Section
76+ SettingsSection (title = " API Configuration (Testing)" ) {
77+ ApiConfigurationRow (
78+ label = " API Key" ,
79+ isConfigured = uiState.isApiKeyConfigured,
80+ )
81+ HorizontalDivider (modifier = Modifier .padding(vertical = 4 .dp))
82+ ApiConfigurationRow (
83+ label = " Base URL" ,
84+ isConfigured = uiState.isBaseURLConfigured,
85+ )
86+ HorizontalDivider (modifier = Modifier .padding(vertical = 8 .dp))
87+ Row (
88+ horizontalArrangement = Arrangement .spacedBy(8 .dp),
89+ ) {
90+ OutlinedButton (
91+ onClick = { viewModel.showApiConfigSheet() },
92+ colors = ButtonDefaults .outlinedButtonColors(
93+ contentColor = AppColors .primaryAccent,
94+ ),
95+ ) {
96+ Text (" Configure" )
97+ }
98+ if (uiState.isApiKeyConfigured && uiState.isBaseURLConfigured) {
99+ OutlinedButton (
100+ onClick = { viewModel.clearApiConfiguration() },
101+ colors = ButtonDefaults .outlinedButtonColors(
102+ contentColor = AppColors .primaryRed,
103+ ),
104+ ) {
105+ Text (" Clear" )
106+ }
107+ }
108+ }
109+ Spacer (modifier = Modifier .height(8 .dp))
110+ Text (
111+ text = " Configure custom API key and base URL for testing. Requires app restart." ,
112+ style = MaterialTheme .typography.bodySmall,
113+ color = MaterialTheme .colorScheme.onSurfaceVariant,
114+ )
115+ }
116+
72117 // Storage Overview Section
73118 SettingsSection (
74119 title = " Storage Overview" ,
@@ -240,6 +285,43 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
240285 },
241286 )
242287 }
288+
289+ // API Configuration Dialog
290+ if (uiState.showApiConfigSheet) {
291+ ApiConfigurationDialog (
292+ apiKey = uiState.apiKey,
293+ baseURL = uiState.baseURL,
294+ onApiKeyChange = { viewModel.updateApiKey(it) },
295+ onBaseURLChange = { viewModel.updateBaseURL(it) },
296+ onSave = { viewModel.saveApiConfiguration() },
297+ onDismiss = { viewModel.hideApiConfigSheet() },
298+ )
299+ }
300+
301+ // Restart Required Dialog
302+ if (uiState.showRestartDialog) {
303+ AlertDialog (
304+ onDismissRequest = { viewModel.dismissRestartDialog() },
305+ title = { Text (" Restart Required" ) },
306+ text = {
307+ Text (" API configuration has been updated. Please restart the app for changes to take effect." )
308+ },
309+ confirmButton = {
310+ TextButton (
311+ onClick = { viewModel.dismissRestartDialog() },
312+ ) {
313+ Text (" OK" )
314+ }
315+ },
316+ icon = {
317+ Icon (
318+ imageVector = Icons .Outlined .RestartAlt ,
319+ contentDescription = null ,
320+ tint = AppColors .primaryOrange,
321+ )
322+ },
323+ )
324+ }
243325}
244326
245327/* *
@@ -421,3 +503,131 @@ private fun StorageManagementButton(
421503 }
422504 }
423505}
506+
507+ /* *
508+ * API Configuration Row
509+ */
510+ @Composable
511+ private fun ApiConfigurationRow (
512+ label : String ,
513+ isConfigured : Boolean ,
514+ ) {
515+ Row (
516+ modifier =
517+ Modifier
518+ .fillMaxWidth()
519+ .padding(vertical = 4 .dp),
520+ horizontalArrangement = Arrangement .SpaceBetween ,
521+ verticalAlignment = Alignment .CenterVertically ,
522+ ) {
523+ Text (
524+ text = label,
525+ style = MaterialTheme .typography.bodyMedium,
526+ )
527+ Text (
528+ text = if (isConfigured) " Configured" else " Not Set" ,
529+ style = MaterialTheme .typography.bodySmall,
530+ color = if (isConfigured) AppColors .primaryGreen else AppColors .primaryOrange,
531+ )
532+ }
533+ }
534+
535+ /* *
536+ * API Configuration Dialog
537+ */
538+ @OptIn(ExperimentalMaterial3Api ::class )
539+ @Composable
540+ private fun ApiConfigurationDialog (
541+ apiKey : String ,
542+ baseURL : String ,
543+ onApiKeyChange : (String ) -> Unit ,
544+ onBaseURLChange : (String ) -> Unit ,
545+ onSave : () -> Unit ,
546+ onDismiss : () -> Unit ,
547+ ) {
548+ var showPassword by remember { mutableStateOf(false ) }
549+
550+ AlertDialog (
551+ onDismissRequest = onDismiss,
552+ title = { Text (" API Configuration" ) },
553+ text = {
554+ Column (
555+ verticalArrangement = Arrangement .spacedBy(16 .dp),
556+ ) {
557+ // API Key Input
558+ OutlinedTextField (
559+ value = apiKey,
560+ onValueChange = onApiKeyChange,
561+ label = { Text (" API Key" ) },
562+ placeholder = { Text (" Enter your API key" ) },
563+ singleLine = true ,
564+ modifier = Modifier .fillMaxWidth(),
565+ visualTransformation = if (showPassword) VisualTransformation .None else PasswordVisualTransformation (),
566+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Password ),
567+ trailingIcon = {
568+ IconButton (onClick = { showPassword = ! showPassword }) {
569+ Icon (
570+ imageVector = if (showPassword) Icons .Outlined .VisibilityOff else Icons .Outlined .Visibility ,
571+ contentDescription = if (showPassword) " Hide password" else " Show password" ,
572+ )
573+ }
574+ },
575+ supportingText = {
576+ Text (" Your API key for authenticating with the backend" )
577+ },
578+ )
579+
580+ // Base URL Input
581+ OutlinedTextField (
582+ value = baseURL,
583+ onValueChange = onBaseURLChange,
584+ label = { Text (" Base URL" ) },
585+ placeholder = { Text (" https://api.example.com" ) },
586+ singleLine = true ,
587+ modifier = Modifier .fillMaxWidth(),
588+ keyboardOptions = KeyboardOptions (keyboardType = KeyboardType .Uri ),
589+ supportingText = {
590+ Text (" The backend API URL (https:// added automatically if missing)" )
591+ },
592+ )
593+
594+ // Warning
595+ Surface (
596+ color = AppColors .primaryOrange.copy(alpha = 0.1f ),
597+ shape = RoundedCornerShape (8 .dp),
598+ ) {
599+ Row (
600+ modifier = Modifier .padding(12 .dp),
601+ horizontalArrangement = Arrangement .spacedBy(8 .dp),
602+ verticalAlignment = Alignment .Top ,
603+ ) {
604+ Icon (
605+ imageVector = Icons .Outlined .Warning ,
606+ contentDescription = null ,
607+ tint = AppColors .primaryOrange,
608+ modifier = Modifier .size(20 .dp),
609+ )
610+ Text (
611+ text = " After saving, you must restart the app for changes to take effect. The SDK will reinitialize with your custom configuration." ,
612+ style = MaterialTheme .typography.bodySmall,
613+ color = MaterialTheme .colorScheme.onSurfaceVariant,
614+ )
615+ }
616+ }
617+ }
618+ },
619+ confirmButton = {
620+ TextButton (
621+ onClick = onSave,
622+ enabled = apiKey.isNotEmpty() && baseURL.isNotEmpty(),
623+ ) {
624+ Text (" Save" )
625+ }
626+ },
627+ dismissButton = {
628+ TextButton (onClick = onDismiss) {
629+ Text (" Cancel" )
630+ }
631+ },
632+ )
633+ }
0 commit comments