ソースを参照

Added transfer metrics

Vadik Sirekanyan 2 年 前
コミット
becb3bf640

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

@@ -12,7 +12,7 @@ import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.core.view.WindowCompat
 import org.sirekanyan.outline.api.OutlineApi
-import org.sirekanyan.outline.api.model.AccessKey
+import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.ui.theme.OutlineTheme
 
 class MainActivity : ComponentActivity() {
@@ -22,12 +22,12 @@ class MainActivity : ComponentActivity() {
         setContent {
             val api = remember { OutlineApi() }
             val state = rememberMainState()
-            val accessKeys by produceState(listOf<AccessKey>(), state.selected) {
-                value = state.selected?.let { api.getAccessKeys(it) } ?: listOf()
+            val keys by produceState(listOf<Key>(), state.selected) {
+                value = state.selected?.let { api.getKeys(it) } ?: listOf()
             }
             OutlineTheme {
                 Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
-                    MainContent(api, state, accessKeys)
+                    MainContent(api, state, keys)
                 }
             }
         }

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

@@ -8,16 +8,16 @@ import androidx.compose.material3.ModalNavigationDrawer
 import androidx.compose.runtime.Composable
 import kotlinx.coroutines.launch
 import org.sirekanyan.outline.api.OutlineApi
-import org.sirekanyan.outline.api.model.AccessKey
+import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.ui.AddKeyButton
 import org.sirekanyan.outline.ui.DrawerContent
 import org.sirekanyan.outline.ui.KeyContent
 
 @Composable
-fun MainContent(api: OutlineApi, state: MainState, keys: List<AccessKey>) {
+fun MainContent(api: OutlineApi, state: MainState, keys: List<Key>) {
     ModalNavigationDrawer({ DrawerContent(api, state) }, drawerState = state.drawer) {
         LazyColumn(contentPadding = WindowInsets.systemBars.asPaddingValues()) {
-            keys.forEach { key ->
+            keys.sortedByDescending(Key::traffic).forEach { key ->
                 item {
                     KeyContent(key)
                 }

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

@@ -8,9 +8,10 @@ import io.ktor.client.request.get
 import io.ktor.client.request.post
 import io.ktor.serialization.kotlinx.json.json
 import kotlinx.serialization.json.Json
-import org.sirekanyan.outline.api.model.AccessKey
 import org.sirekanyan.outline.api.model.AccessKeysResponse
+import org.sirekanyan.outline.api.model.Key
 import org.sirekanyan.outline.api.model.ServerNameResponse
+import org.sirekanyan.outline.api.model.TransferMetricsResponse
 
 val API_URLS: List<String> = listOf(
     // TODO: add api urls
@@ -28,11 +29,19 @@ class OutlineApi {
     suspend fun getServerName(apiUrl: String): String =
         httpClient.get("$apiUrl/server").body<ServerNameResponse>().name
 
-    suspend fun getAccessKeys(index: Int): List<AccessKey> {
+    suspend fun getKeys(index: Int): List<Key> {
         val apiUrl = API_URLS.getOrNull(index) ?: return listOf()
-        return httpClient.get("$apiUrl/access-keys").body<AccessKeysResponse>().accessKeys
+        val accessKeys = getAccessKeys(apiUrl).accessKeys
+        val transferMetrics = getTransferMetrics(apiUrl).bytesTransferredByUserId
+        return accessKeys.map { accessKey -> Key(accessKey, transferMetrics[accessKey.id]) }
     }
 
+    private suspend fun getAccessKeys(apiUrl: String): AccessKeysResponse =
+        httpClient.get("$apiUrl/access-keys").body()
+
+    private suspend fun getTransferMetrics(apiUrl: String): TransferMetricsResponse =
+        httpClient.get("$apiUrl/metrics/transfer").body()
+
     suspend fun createAccessKey(index: Int) {
         val apiUrl = API_URLS.getOrNull(index) ?: return
         httpClient.post("$apiUrl/access-keys")

+ 3 - 0
app/src/main/java/org/sirekanyan/outline/api/model/Key.kt

@@ -0,0 +1,3 @@
+package org.sirekanyan.outline.api.model
+
+class Key(val accessKey: AccessKey, val traffic: Long?)

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

@@ -0,0 +1,6 @@
+package org.sirekanyan.outline.api.model
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class TransferMetricsResponse(val bytesTransferredByUserId: Map<String, Long>)

+ 9 - 0
app/src/main/java/org/sirekanyan/outline/text/TrafficFormatter.kt

@@ -0,0 +1,9 @@
+package org.sirekanyan.outline.text
+
+fun formatTraffic(bytes: Long): String =
+    when {
+        bytes > 1_000_000_000 -> "${bytes / 1000 / 1000 / 1000} GB"
+        bytes > 1_000_000 -> "${bytes / 1000 / 1000} MB"
+        bytes > 1_000 -> "${bytes / 1000} kB"
+        else -> "$bytes B"
+    }

+ 23 - 8
app/src/main/java/org/sirekanyan/outline/ui/KeyContent.kt

@@ -3,29 +3,44 @@ package org.sirekanyan.outline.ui
 import android.widget.Toast
 import android.widget.Toast.LENGTH_SHORT
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.unit.dp
-import org.sirekanyan.outline.api.model.AccessKey
+import org.sirekanyan.outline.api.model.Key
+import org.sirekanyan.outline.text.formatTraffic
 
 @Composable
-fun KeyContent(key: AccessKey) {
+fun KeyContent(key: Key) {
     val localContext = LocalContext.current
     val localClipboard = LocalClipboardManager.current
-    Text(
-        text = key.name.ifEmpty { "Key ${key.id}" },
-        modifier = Modifier
+    Row(
+        Modifier
             .clickable {
-                localClipboard.setText(AnnotatedString(key.accessUrl))
+                localClipboard.setText(AnnotatedString(key.accessKey.accessUrl))
                 Toast.makeText(localContext, "Copied", LENGTH_SHORT).show()
             }
             .fillMaxWidth()
             .padding(16.dp),
-    )
-}
+        Arrangement.SpaceBetween,
+        Alignment.CenterVertically,
+    ) {
+        Text(key.accessKey.name.ifEmpty { "Key ${key.accessKey.id}" })
+        key.traffic?.let { traffic ->
+            Text(
+                text = formatTraffic(traffic),
+                style = MaterialTheme.typography.labelSmall,
+                color = MaterialTheme.colorScheme.secondary,
+            )
+        }
+    }
+}