Prechádzať zdrojové kódy

Added edit key button

Vadik Sirekanyan 2 rokov pred
rodič
commit
14118bc47d

+ 5 - 4
app/src/main/java/org/sirekanyan/outline/MainActivity.kt

@@ -16,6 +16,7 @@ import org.sirekanyan.outline.api.OutlineApi
 import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.db.rememberApiUrlDao
 import org.sirekanyan.outline.ui.DraftContent
+import org.sirekanyan.outline.ui.EditKeyContent
 import org.sirekanyan.outline.ui.theme.OutlineTheme
 
 class MainActivity : ComponentActivity() {
@@ -37,10 +38,10 @@ class MainActivity : ComponentActivity() {
             }
             OutlineTheme {
                 Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
-                    if (state.page is DraftPage) {
-                        DraftContent(api, dao, state)
-                    } else {
-                        MainContent(api, dao, state, keys)
+                    when (val page = state.page) {
+                        is DraftPage -> DraftContent(api, dao, state)
+                        is EditKeyPage -> EditKeyContent(api, state, page)
+                        else -> MainContent(api, dao, state, keys)
                     }
                 }
             }

+ 3 - 0
app/src/main/java/org/sirekanyan/outline/MainContent.kt

@@ -77,6 +77,9 @@ fun MainContent(api: OutlineApi, dao: ApiUrlDao, state: MainState, keys: List<Ke
                 KeyBottomSheet(
                     key = selectedKey,
                     onDismissRequest = { state.selectedKey = null },
+                    onEditClick = {
+                        state.page = EditKeyPage(selected, selectedKey)
+                    },
                     onDeleteClick = {
                         state.scope.launch {
                             api.deleteAccessKey(selected, selectedKey.accessKey.id)

+ 2 - 0
app/src/main/java/org/sirekanyan/outline/MainState.kt

@@ -50,4 +50,6 @@ data object HelloPage : Page()
 
 data object DraftPage : Page()
 
+data class EditKeyPage(val selected: String, val key: Key) : Page()
+
 data class SelectedPage(val selected: String) : Page()

+ 12 - 0
app/src/main/java/org/sirekanyan/outline/api/OutlineApi.kt

@@ -7,10 +7,15 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
 import io.ktor.client.request.delete
 import io.ktor.client.request.get
 import io.ktor.client.request.post
+import io.ktor.client.request.put
+import io.ktor.client.request.setBody
+import io.ktor.http.ContentType
+import io.ktor.http.contentType
 import io.ktor.serialization.kotlinx.json.json
 import kotlinx.serialization.json.Json
 import org.sirekanyan.outline.api.model.AccessKeysResponse
 import org.sirekanyan.outline.api.model.Key
+import org.sirekanyan.outline.api.model.RenameRequest
 import org.sirekanyan.outline.api.model.ServerNameResponse
 import org.sirekanyan.outline.api.model.TransferMetricsResponse
 
@@ -42,6 +47,13 @@ class OutlineApi {
         httpClient.post("$apiUrl/access-keys")
     }
 
+    suspend fun renameAccessKey(apiUrl: String, id: String, name: String) {
+        httpClient.put("$apiUrl/access-keys/$id/name") {
+            contentType(ContentType.Application.Json)
+            setBody(RenameRequest(name))
+        }
+    }
+
     suspend fun deleteAccessKey(apiUrl: String, id: String) {
         httpClient.delete("$apiUrl/access-keys/$id")
     }

+ 3 - 1
app/src/main/java/org/sirekanyan/outline/api/model/AccessKey.kt

@@ -8,6 +8,8 @@ class AccessKey(
     val accessUrl: String,
     private val name: String,
 ) {
+    val defaultName: String
+        get() = "Key $id"
     val nameOrDefault: String
-        get() = name.ifEmpty { "Key $id" }
+        get() = name.ifEmpty { defaultName }
 }

+ 6 - 0
app/src/main/java/org/sirekanyan/outline/api/model/RenameRequest.kt

@@ -0,0 +1,6 @@
+package org.sirekanyan.outline.api.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class RenameRequest(val name: String)

+ 33 - 0
app/src/main/java/org/sirekanyan/outline/ui/DialogToolbar.kt

@@ -0,0 +1,33 @@
+package org.sirekanyan.outline.ui
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun DialogToolbar(title: String, onCloseClick: () -> Unit, action: Pair<String, () -> Unit>) {
+    TopAppBar(
+        title = { Text(title) },
+        navigationIcon = {
+            IconButton({ onCloseClick() }) { Icon(Icons.Default.Close, null) }
+        },
+        actions = {
+            val (actionName, onActionClick) = action
+            TextButton({ onActionClick() }) { Text(actionName) }
+        },
+        colors = TopAppBarDefaults.topAppBarColors(
+            containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
+        ),
+    )
+}

+ 3 - 29
app/src/main/java/org/sirekanyan/outline/ui/DraftContent.kt

@@ -5,18 +5,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.surfaceColorAtElevation
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -51,9 +41,10 @@ fun DraftContent(api: OutlineApi, dao: ApiUrlDao, state: MainState) {
         }
     }
     Column {
-        DraftTopAppBar(
+        DialogToolbar(
+            title = "Add server",
             onCloseClick = { state.page = HelloPage },
-            onAddClick = { state.scope.launch { onAddClick() } },
+            action = "Add" to { state.scope.launch { onAddClick() } },
         )
         val focusRequester = remember { FocusRequester() }
         OutlinedTextField(
@@ -76,20 +67,3 @@ fun DraftContent(api: OutlineApi, dao: ApiUrlDao, state: MainState) {
         }
     }
 }
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class)
-private fun DraftTopAppBar(onCloseClick: () -> Unit, onAddClick: () -> Unit) {
-    TopAppBar(
-        title = { Text("Add server") },
-        navigationIcon = {
-            IconButton({ onCloseClick() }) { Icon(Icons.Default.Close, null) }
-        },
-        actions = {
-            TextButton({ onAddClick() }) { Text("Add") }
-        },
-        colors = TopAppBarDefaults.topAppBarColors(
-            containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
-        ),
-    )
-}

+ 70 - 0
app/src/main/java/org/sirekanyan/outline/ui/EditKeyContent.kt

@@ -0,0 +1,70 @@
+package org.sirekanyan.outline.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+import org.sirekanyan.outline.EditKeyPage
+import org.sirekanyan.outline.MainState
+import org.sirekanyan.outline.SelectedPage
+import org.sirekanyan.outline.api.OutlineApi
+
+@Composable
+fun EditKeyContent(api: OutlineApi, state: MainState, page: EditKeyPage) {
+    val accessKey = page.key.accessKey
+    var draft by remember {
+        mutableStateOf(TextFieldValue(accessKey.nameOrDefault, TextRange(Int.MAX_VALUE)))
+    }
+    var error by remember(draft) {
+        mutableStateOf("")
+    }
+    Column {
+        DialogToolbar(
+            title = "Edit key",
+            onCloseClick = { state.page = SelectedPage(page.selected) },
+            action = "Save" to {
+                state.scope.launch {
+                    try {
+                        val newName = draft.text.ifBlank { accessKey.defaultName }
+                        api.renameAccessKey(page.selected, accessKey.id, newName)
+                        state.page = SelectedPage(page.selected)
+                    } catch (exception: Exception) {
+                        exception.printStackTrace()
+                        error = "Check name or try again"
+                    }
+                }
+            },
+        )
+        val focusRequester = remember { FocusRequester() }
+        OutlinedTextField(
+            value = draft,
+            onValueChange = { draft = it.copy(text = it.text.trim('\n')) },
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = 16.dp, vertical = 24.dp)
+                .focusRequester(focusRequester),
+            label = { Text("Name") },
+            placeholder = { Text(accessKey.defaultName) },
+            isError = error.isNotEmpty(),
+            supportingText = { Text(error) },
+            maxLines = 4,
+        )
+        LaunchedEffect(Unit) {
+            focusRequester.requestFocus()
+        }
+    }
+}

+ 15 - 1
app/src/main/java/org/sirekanyan/outline/ui/KeyBottomSheet.kt

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.filled.Share
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
@@ -32,7 +33,12 @@ import org.sirekanyan.outline.ui.icons.IconCopy
 
 @Composable
 @OptIn(ExperimentalMaterial3Api::class)
-fun KeyBottomSheet(key: Key, onDismissRequest: () -> Unit, onDeleteClick: () -> Unit) {
+fun KeyBottomSheet(
+    key: Key,
+    onDismissRequest: () -> Unit,
+    onEditClick: () -> Unit,
+    onDeleteClick: () -> Unit,
+) {
     val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
     val localClipboard = LocalClipboardManager.current
     val localContext = LocalContext.current
@@ -79,6 +85,14 @@ fun KeyBottomSheet(key: Key, onDismissRequest: () -> Unit, onDeleteClick: () ->
                     )
                 },
             )
+            ListItem(
+                headlineContent = { Text("Edit") },
+                leadingContent = { Icon(Icons.Default.Edit, null) },
+                modifier = Modifier.clickable {
+                    onEditClick()
+                    onDismissRequest()
+                },
+            )
             ListItem(
                 headlineContent = { Text("Delete") },
                 leadingContent = { Icon(Icons.Default.Delete, null) },