@@ -10,6 +10,7 @@ import androidx.compose.material.icons.Icons
1010import androidx.compose.material.icons.automirrored.filled.ArrowBack
1111import androidx.compose.material.icons.automirrored.filled.ArrowForward
1212import androidx.compose.material.icons.filled.Download
13+ import androidx.compose.material.icons.filled.Edit
1314import androidx.compose.material.icons.filled.Groups
1415import androidx.compose.material.icons.filled.Search
1516import androidx.compose.material.icons.filled.Shield
@@ -25,6 +26,7 @@ import com.artifactkeeper.android.data.api.ApiClient
2526import com.artifactkeeper.android.data.api.unwrap
2627import com.artifactkeeper.android.data.models.Artifact
2728import com.artifactkeeper.android.data.models.Repository
29+ import com.artifactkeeper.client.models.UpdateRepositoryRequest
2830import com.artifactkeeper.android.ui.util.formatBytes
2931import com.artifactkeeper.android.ui.util.formatDownloadCount
3032import com.artifactkeeper.android.ui.util.formatRelativeTime
@@ -44,6 +46,7 @@ fun RepositoryDetailScreen(
4446 var isRefreshing by remember { mutableStateOf(false ) }
4547 var errorMessage by remember { mutableStateOf<String ?>(null ) }
4648 var searchQuery by remember { mutableStateOf(" " ) }
49+ var showEditDialog by remember { mutableStateOf(false ) }
4750 val coroutineScope = rememberCoroutineScope()
4851
4952 fun loadData (refresh : Boolean = false) {
@@ -78,6 +81,11 @@ fun RepositoryDetailScreen(
7881 Icon (Icons .AutoMirrored .Filled .ArrowBack , contentDescription = " Back" )
7982 }
8083 },
84+ actions = {
85+ IconButton (onClick = { showEditDialog = true }) {
86+ Icon (Icons .Default .Edit , contentDescription = " Edit Repository" )
87+ }
88+ },
8189 )
8290
8391 when {
@@ -185,6 +193,22 @@ fun RepositoryDetailScreen(
185193 }
186194 }
187195 }
196+
197+ if (showEditDialog && repository != null ) {
198+ EditRepositoryDialog (
199+ repository = repository!! ,
200+ onDismiss = { showEditDialog = false },
201+ onSaved = { updatedRepo, originalKey ->
202+ showEditDialog = false
203+ if (updatedRepo.key != originalKey) {
204+ onBack()
205+ } else {
206+ repository = updatedRepo
207+ loadData(refresh = true )
208+ }
209+ },
210+ )
211+ }
188212 }
189213}
190214
@@ -381,3 +405,125 @@ private fun VirtualMembersCard(onClick: () -> Unit) {
381405 }
382406 }
383407}
408+
409+ @Composable
410+ private fun EditRepositoryDialog (
411+ repository : Repository ,
412+ onDismiss : () -> Unit ,
413+ onSaved : (updatedRepo: Repository , originalKey: String ) -> Unit ,
414+ ) {
415+ var key by remember { mutableStateOf(repository.key) }
416+ var name by remember { mutableStateOf(repository.name) }
417+ var description by remember { mutableStateOf(repository.description ? : " " ) }
418+ var isPublic by remember { mutableStateOf(repository.isPublic) }
419+ var isSaving by remember { mutableStateOf(false ) }
420+ var errorMessage by remember { mutableStateOf<String ?>(null ) }
421+ val coroutineScope = rememberCoroutineScope()
422+
423+ AlertDialog (
424+ onDismissRequest = { if (! isSaving) onDismiss() },
425+ title = { Text (" Edit Repository" ) },
426+ text = {
427+ Column (verticalArrangement = Arrangement .spacedBy(12 .dp)) {
428+ OutlinedTextField (
429+ value = key,
430+ onValueChange = { key = it.lowercase() },
431+ label = { Text (" Key" ) },
432+ modifier = Modifier .fillMaxWidth(),
433+ singleLine = true ,
434+ enabled = ! isSaving,
435+ )
436+
437+ if (key != repository.key) {
438+ Text (
439+ text = " Changing the repository key will update all URLs. This may break existing client configurations." ,
440+ style = MaterialTheme .typography.bodySmall,
441+ color = MaterialTheme .colorScheme.error,
442+ )
443+ }
444+
445+ OutlinedTextField (
446+ value = name,
447+ onValueChange = { name = it },
448+ label = { Text (" Name" ) },
449+ modifier = Modifier .fillMaxWidth(),
450+ singleLine = true ,
451+ enabled = ! isSaving,
452+ )
453+
454+ OutlinedTextField (
455+ value = description,
456+ onValueChange = { description = it },
457+ label = { Text (" Description" ) },
458+ modifier = Modifier .fillMaxWidth(),
459+ minLines = 2 ,
460+ maxLines = 4 ,
461+ enabled = ! isSaving,
462+ )
463+
464+ Row (
465+ modifier = Modifier .fillMaxWidth(),
466+ horizontalArrangement = Arrangement .SpaceBetween ,
467+ verticalAlignment = Alignment .CenterVertically ,
468+ ) {
469+ Text (" Public" , style = MaterialTheme .typography.bodyLarge)
470+ Switch (
471+ checked = isPublic,
472+ onCheckedChange = { isPublic = it },
473+ enabled = ! isSaving,
474+ )
475+ }
476+
477+ if (errorMessage != null ) {
478+ Text (
479+ text = errorMessage ? : " " ,
480+ style = MaterialTheme .typography.bodySmall,
481+ color = MaterialTheme .colorScheme.error,
482+ )
483+ }
484+ }
485+ },
486+ confirmButton = {
487+ TextButton (
488+ onClick = {
489+ coroutineScope.launch {
490+ isSaving = true
491+ errorMessage = null
492+ try {
493+ val request = UpdateRepositoryRequest (
494+ key = if (key != repository.key) key else null ,
495+ name = name,
496+ description = description.ifBlank { null },
497+ isPublic = isPublic,
498+ )
499+ val updatedRepo = ApiClient .reposApi.updateRepository(
500+ repository.key,
501+ request,
502+ ).unwrap()
503+ onSaved(updatedRepo, repository.key)
504+ } catch (e: Exception ) {
505+ errorMessage = e.message ? : " Failed to update repository"
506+ } finally {
507+ isSaving = false
508+ }
509+ }
510+ },
511+ enabled = ! isSaving,
512+ ) {
513+ if (isSaving) {
514+ CircularProgressIndicator (modifier = Modifier .size(16 .dp))
515+ } else {
516+ Text (" Save" )
517+ }
518+ }
519+ },
520+ dismissButton = {
521+ TextButton (
522+ onClick = onDismiss,
523+ enabled = ! isSaving,
524+ ) {
525+ Text (" Cancel" )
526+ }
527+ },
528+ )
529+ }
0 commit comments