OXYGENHUB
# 23279b95b5b2 oxygenhub-android / app / src / main / java / io / oxygenhub / android / ui / account / AccountScreen.kt
5.8 KiB · last changed The app says when a later release is out: Account shows its version and a later release's APK to download (closes #7) 8 days ago source blame history raw
1
package io.oxygenhub.android.ui.account
2
3
import androidx.compose.foundation.layout.Arrangement
4
import androidx.compose.foundation.layout.Box
5
import androidx.compose.foundation.layout.Column
6
import androidx.compose.foundation.layout.Spacer
7
import androidx.compose.foundation.layout.fillMaxWidth
8
import androidx.compose.foundation.layout.height
9
import androidx.compose.foundation.layout.padding
10
import androidx.compose.foundation.layout.size
11
import androidx.compose.foundation.layout.widthIn
12
import androidx.compose.foundation.rememberScrollState
13
import androidx.compose.foundation.verticalScroll
14
import androidx.compose.material.icons.Icons
15
import androidx.compose.material.icons.automirrored.outlined.Logout
16
import androidx.compose.material.icons.outlined.Dns
17
import androidx.compose.material.icons.outlined.Key
18
import androidx.compose.material.icons.outlined.Memory
19
import androidx.compose.material3.AlertDialog
20
import androidx.compose.material3.Card
21
import androidx.compose.material3.CircularProgressIndicator
22
import androidx.compose.material3.Icon
23
import androidx.compose.material3.ListItem
24
import androidx.compose.material3.MaterialTheme
25
import androidx.compose.material3.OutlinedButton
26
import androidx.compose.material3.Text
27
import androidx.compose.material3.TextButton
28
import androidx.compose.runtime.Composable
29
import androidx.compose.runtime.getValue
30
import androidx.compose.runtime.mutableStateOf
31
import androidx.compose.runtime.remember
32
import androidx.compose.runtime.rememberCoroutineScope
33
import androidx.compose.runtime.setValue
34
import androidx.compose.ui.Alignment
35
import androidx.compose.ui.Modifier
36
import androidx.compose.ui.text.font.FontWeight
37
import androidx.compose.ui.unit.dp
38
import io.oxygenhub.android.auth.Session
39
import io.oxygenhub.android.api.Avatar
40
import io.oxygenhub.android.api.HubApi
41
import io.oxygenhub.android.alerts.LandAlertsCard
42
import io.oxygenhub.android.runner.RunnerCard
43
import io.oxygenhub.android.ui.common.AppScreen
44
import io.oxygenhub.android.ui.common.AvatarImage
45
import io.oxygenhub.android.update.UpdateCard
46
import kotlinx.coroutines.launch
47
48
/** Who is signed in, to which hub, with what; and signing out. */
49
@Composable
50
fun AccountScreen(session: Session, avatar: Avatar?, api: HubApi, nativeCore: () -> String, onSignOut: suspend () -> Unit) {
51
    var confirming by remember { mutableStateOf(false) }
52
    var signingOut by remember { mutableStateOf(false) }
53
    val scope = rememberCoroutineScope()
54
    val core = remember { runCatching(nativeCore).getOrElse { "unavailable: ${it.message}" } }
55
56
    AppScreen(title = "Account") {
57
        Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.TopCenter) {
58
            Column(
59
                Modifier.widthIn(max = 600.dp).verticalScroll(rememberScrollState()).padding(16.dp),
60
                verticalArrangement = Arrangement.spacedBy(16.dp),
61
            ) {
62
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
63
                    AvatarImage(avatar, session.username, size = 72.dp)
64
                    Spacer(Modifier.height(8.dp))
65
                    Text(session.username, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
66
                }
67
                LandAlertsCard()
68
                RunnerCard(api, session.hub, session.canWrite)
69
                UpdateCard(api, session.hub)
70
                Card(Modifier.fillMaxWidth()) {
71
                    ListItem(
72
                        leadingContent = { Icon(Icons.Outlined.Dns, contentDescription = null) },
73
                        headlineContent = { Text("Hub") },
74
                        supportingContent = { Text(session.hub) },
75
                    )
76
                    ListItem(
77
                        leadingContent = { Icon(Icons.Outlined.Key, contentDescription = null) },
78
                        headlineContent = { Text("Allowed") },
79
                        supportingContent = { Text(session.scopes.ifEmpty { listOf("(the hub did not say)") }.joinToString(", ")) },
80
                    )
81
                    ListItem(
82
                        leadingContent = { Icon(Icons.Outlined.Memory, contentDescription = null) },
83
                        headlineContent = { Text("Native core") },
84
                        supportingContent = { Text(core) },
85
                    )
86
                }
87
                Text(
88
                    "This phone's sign-in is a device token, kept encrypted with a key in the Android Keystore. " +
89
                        "It lasts while it is used, and ends after 90 days without use. Signing out revokes it on the hub.",
90
                    style = MaterialTheme.typography.bodySmall,
91
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
92
                )
93
                OutlinedButton(onClick = { confirming = true }, enabled = !signingOut, modifier = Modifier.fillMaxWidth()) {
94
                    if (signingOut) {
95
                        CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp)
96
                    } else {
97
                        Icon(Icons.AutoMirrored.Outlined.Logout, contentDescription = null)
98
                    }
99
                    Spacer(Modifier.size(8.dp))
100
                    Text("Sign out")
101
                }
102
            }
103
        }
104
    }
105
106
    if (confirming) {
107
        AlertDialog(
108
            onDismissRequest = { confirming = false },
109
            title = { Text("Sign out?") },
110
            text = { Text("The token this phone uses is revoked on ${session.hub}. You can sign in again at any time.") },
111
            confirmButton = {
112
                TextButton(onClick = {
113
                    confirming = false
114
                    signingOut = true
115
                    scope.launch { onSignOut() }
116
                }) { Text("Sign out") }
117
            },
118
            dismissButton = { TextButton(onClick = { confirming = false }) { Text("Cancel") } },
119
        )
120
    }
121
}