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