feat: session settings
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
97c31d0e3e
commit
0245edc8f1
|
|
@ -3,3 +3,4 @@
|
|||
/workspace.xml
|
||||
/kotlinc.xml
|
||||
/appInsightsSettings.xml
|
||||
/other.xml
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import chat.revolt.screens.settings.AppearanceSettingsScreen
|
|||
import chat.revolt.screens.settings.ChangelogsSettingsScreen
|
||||
import chat.revolt.screens.settings.ClosedBetaUpdaterScreen
|
||||
import chat.revolt.screens.settings.DebugSettingsScreen
|
||||
import chat.revolt.screens.settings.SessionSettingsScreen
|
||||
import chat.revolt.screens.settings.SettingsScreen
|
||||
import chat.revolt.ui.theme.RevoltTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -142,6 +143,7 @@ fun AppEntrypoint(windowSizeClass: WindowSizeClass) {
|
|||
composable("chat") { ChatRouterScreen(navController, windowSizeClass) }
|
||||
|
||||
composable("settings") { SettingsScreen(navController) }
|
||||
composable("settings/sessions") { SessionSettingsScreen(navController) }
|
||||
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||
composable("settings/debug") { DebugSettingsScreen(navController) }
|
||||
composable("settings/updater") { ClosedBetaUpdaterScreen(navController) }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import chat.revolt.api.internals.Members
|
|||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.routes.user.fetchSelf
|
||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||
import chat.revolt.api.schemas.Emoji
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.schemas.Server
|
||||
|
|
@ -39,6 +38,7 @@ import kotlinx.coroutines.withContext
|
|||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||
|
||||
const val REVOLT_BASE = "https://api.revolt.chat"
|
||||
const val REVOLT_SUPPORT = "https://support.revolt.chat"
|
||||
|
|
@ -118,6 +118,8 @@ object RevoltAPI {
|
|||
|
||||
var sessionToken: String = ""
|
||||
private set
|
||||
var sessionId: String = ""
|
||||
private set
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||
val realtimeContext = newSingleThreadContext("RealtimeContext")
|
||||
|
|
@ -129,6 +131,10 @@ object RevoltAPI {
|
|||
sessionToken = token
|
||||
}
|
||||
|
||||
fun setSessionId(id: String) {
|
||||
sessionId = id
|
||||
}
|
||||
|
||||
suspend fun loginAs(token: String) {
|
||||
setSessionHeader(token)
|
||||
fetchSelf()
|
||||
|
|
@ -189,6 +195,7 @@ object RevoltAPI {
|
|||
fun logout() {
|
||||
selfId = null
|
||||
sessionToken = ""
|
||||
sessionId = ""
|
||||
|
||||
userCache.clear()
|
||||
serverCache.clear()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package chat.revolt.api.routes.auth
|
||||
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.schemas.Session
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
|
||||
suspend fun fetchAllSessions(): List<Session> {
|
||||
val response = RevoltHttp.get("/auth/session/all")
|
||||
.bodyAsText()
|
||||
|
||||
return RevoltJson.decodeFromString(
|
||||
ListSerializer(Session.serializer()),
|
||||
response
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun logoutSessionById(id: String) {
|
||||
RevoltHttp.delete("/auth/session/$id")
|
||||
}
|
||||
|
||||
suspend fun logoutAllSessions(includingSelf: Boolean = false) {
|
||||
RevoltHttp.delete("/auth/session/all") {
|
||||
parameter("revoke_self", includingSelf)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package chat.revolt.api.schemas
|
||||
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Session(
|
||||
@SerialName("_id") val id: String,
|
||||
val name: String,
|
||||
) {
|
||||
fun isCurrent(): Boolean {
|
||||
return id == RevoltAPI.sessionId
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package chat.revolt.components.settings.sessions
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ElevatedButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.schemas.Session
|
||||
|
||||
@Composable
|
||||
fun SessionItem(
|
||||
session: Session,
|
||||
modifier: Modifier = Modifier,
|
||||
currentSession: Boolean = false,
|
||||
onLogout: (Session) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val decodedUlid by remember(session) { mutableLongStateOf(ULID.asTimestamp(session.id)) }
|
||||
val formattedTimestamp = remember(decodedUlid) {
|
||||
DateUtils.getRelativeTimeSpanString(
|
||||
decodedUlid,
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.MINUTE_IN_MILLIS
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape = MaterialTheme.shapes.medium)
|
||||
.background(
|
||||
color = if (currentSession) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||
}
|
||||
)
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = session.name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.settings_sessions_first_seen, formattedTimestamp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
|
||||
if (!currentSession) {
|
||||
ElevatedButton(onClick = { onLogout(session) }) {
|
||||
Text(stringResource(R.string.logout))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,17 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
|
@ -37,8 +45,8 @@ import chat.revolt.persistence.KVStorage
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.ktor.client.request.get
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
|
|
@ -99,6 +107,7 @@ class SplashScreenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
val token = kvStorage.get("sessionToken") ?: return@launch setNavigateTo("login")
|
||||
val id = kvStorage.get("sessionId") ?: ""
|
||||
|
||||
val canReachRevolt = canReachRevolt()
|
||||
val valid = RevoltAPI.checkSessionToken(token)
|
||||
|
|
@ -110,6 +119,7 @@ class SplashScreenViewModel @Inject constructor(
|
|||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
kvStorage.remove("sessionToken")
|
||||
kvStorage.remove("sessionId")
|
||||
setNavigateTo("login")
|
||||
} else {
|
||||
val onboard = needsOnboarding()
|
||||
|
|
@ -119,6 +129,7 @@ class SplashScreenViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
RevoltAPI.loginAs(token)
|
||||
RevoltAPI.setSessionId(id)
|
||||
loadSettings()
|
||||
setNavigateTo("home")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ import chat.revolt.components.generic.FormTextField
|
|||
import chat.revolt.components.generic.Weblink
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(
|
||||
|
|
@ -102,8 +102,10 @@ class LoginViewModel @Inject constructor(
|
|||
|
||||
try {
|
||||
val token = response.firstUserHints!!.token
|
||||
val id = response.firstUserHints.id
|
||||
|
||||
kvStorage.set("sessionToken", token)
|
||||
kvStorage.set("sessionId", id)
|
||||
|
||||
val onboard = needsOnboarding(token)
|
||||
if (onboard) {
|
||||
|
|
@ -112,6 +114,7 @@ class LoginViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
RevoltAPI.loginAs(token)
|
||||
RevoltAPI.setSessionId(response.firstUserHints.token)
|
||||
loadSettings(token)
|
||||
|
||||
_navigateTo = "home"
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ import chat.revolt.components.generic.CollapsibleCard
|
|||
import chat.revolt.components.generic.FormTextField
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MfaScreenViewModel @Inject constructor(
|
||||
|
|
@ -98,10 +98,13 @@ class MfaScreenViewModel @Inject constructor(
|
|||
|
||||
try {
|
||||
val token = response.firstUserHints!!.token
|
||||
val id = response.firstUserHints.id
|
||||
|
||||
RevoltAPI.loginAs(token)
|
||||
RevoltAPI.setSessionId(id)
|
||||
loadSettings(token)
|
||||
kvStorage.set("sessionToken", token)
|
||||
kvStorage.set("sessionId", id)
|
||||
|
||||
_navigateToHome = true
|
||||
} catch (e: Error) {
|
||||
|
|
@ -126,10 +129,13 @@ class MfaScreenViewModel @Inject constructor(
|
|||
|
||||
try {
|
||||
val token = response.firstUserHints!!.token
|
||||
val id = response.firstUserHints.id
|
||||
|
||||
RevoltAPI.loginAs(token)
|
||||
RevoltAPI.setSessionId(id)
|
||||
loadSettings(token)
|
||||
kvStorage.set("sessionToken", token)
|
||||
kvStorage.set("sessionId", id)
|
||||
|
||||
_navigateToHome = true
|
||||
} catch (e: Error) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
package chat.revolt.screens.settings
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.routes.auth.fetchAllSessions
|
||||
import chat.revolt.api.routes.auth.logoutAllSessions
|
||||
import chat.revolt.api.routes.auth.logoutSessionById
|
||||
import chat.revolt.api.schemas.Session
|
||||
import chat.revolt.components.generic.PageHeader
|
||||
import chat.revolt.components.generic.UIMarkdown
|
||||
import chat.revolt.components.settings.sessions.SessionItem
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SessionSettingsScreenViewModel : ViewModel() {
|
||||
val sessions = mutableStateListOf<Session>()
|
||||
var currentSession by mutableStateOf<Session?>(null)
|
||||
var showLogoutOtherConfirmation by mutableStateOf(false)
|
||||
|
||||
fun fetchSessions() {
|
||||
viewModelScope.launch {
|
||||
sessions.addAll(fetchAllSessions())
|
||||
currentSession = sessions.firstOrNull { it.isCurrent() }
|
||||
Log.d(
|
||||
"SessionSettingsScreen",
|
||||
"Current session: $currentSession. Current session ID: ${RevoltAPI.sessionId}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun logoutSession(id: String) {
|
||||
viewModelScope.launch {
|
||||
logoutSessionById(id)
|
||||
sessions.removeIf { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
fun logoutOtherSessions() {
|
||||
viewModelScope.launch {
|
||||
logoutAllSessions(includingSelf = false)
|
||||
sessions.clear()
|
||||
sessions.addAll(fetchAllSessions())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SessionSettingsScreen(
|
||||
navController: NavController,
|
||||
viewModel: SessionSettingsScreenViewModel = viewModel()
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.fetchSessions()
|
||||
}
|
||||
|
||||
if (viewModel.showLogoutOtherConfirmation) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
viewModel.showLogoutOtherConfirmation = false
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_sessions_log_out_other_confirm)
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
viewModel.showLogoutOtherConfirmation = false
|
||||
viewModel.logoutOtherSessions()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.settings_sessions_log_out_other_confirm_yes))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.showLogoutOtherConfirmation = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.settings_sessions_log_out_other_confirm_no))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.safeDrawingPadding()
|
||||
) {
|
||||
PageHeader(
|
||||
text = stringResource(id = R.string.settings_sessions),
|
||||
showBackButton = true,
|
||||
onBackButtonClicked = {
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
stickyHeader(key = "thisDevice") {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_sessions_this_device),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.currentSession?.let {
|
||||
item(key = it.id) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
SessionItem(
|
||||
session = it,
|
||||
currentSession = true,
|
||||
onLogout = {},
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
item(key = "noCurrentSession") {
|
||||
UIMarkdown(
|
||||
text = stringResource(id = R.string.settings_sessions_this_device_unavailable),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
stickyHeader(key = "otherSessions") {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_sessions_other_sessions),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "logoutOtherSessions") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(shape = MaterialTheme.shapes.medium)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_sessions_log_out_other),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.settings_sessions_log_out_other_description),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
FilledTonalButton(onClick = { viewModel.showLogoutOtherConfirmation = true }) {
|
||||
Text(stringResource(R.string.logout))
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
items(viewModel.sessions.size) {
|
||||
val item = viewModel.sessions[it]
|
||||
|
||||
if (item.isCurrent()) {
|
||||
return@items
|
||||
}
|
||||
|
||||
SessionItem(
|
||||
session = item,
|
||||
onLogout = { session ->
|
||||
viewModel.logoutSession(session.id)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,8 @@ import chat.revolt.components.generic.SheetClickable
|
|||
import chat.revolt.components.screens.settings.SelfUserOverview
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsScreenViewModel @Inject constructor(
|
||||
|
|
@ -79,10 +79,35 @@ fun SettingsScreen(
|
|||
.fillMaxSize()
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_category_account),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(bottom = 10.dp, start = 10.dp, top = 20.dp)
|
||||
)
|
||||
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_tablet_cellphone_24dp),
|
||||
contentDescription = stringResource(id = R.string.settings_sessions),
|
||||
modifier = modifier
|
||||
)
|
||||
},
|
||||
label = { textStyle ->
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_sessions),
|
||||
style = textStyle
|
||||
)
|
||||
},
|
||||
modifier = Modifier.testTag("settings_view_sessions")
|
||||
) {
|
||||
navController.navigate("settings/sessions")
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.settings_category_general),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(bottom = 10.dp, start = 10.dp)
|
||||
modifier = Modifier.padding(bottom = 10.dp, start = 10.dp, top = 20.dp)
|
||||
)
|
||||
|
||||
SheetClickable(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3,4H20A2,2 0 0,1 22,6V8H18V6H5V18H14V20H3A2,2 0 0,1 1,18V6A2,2 0 0,1 3,4M17,10H23A1,1 0 0,1 24,11V21A1,1 0 0,1 23,22H17A1,1 0 0,1 16,21V11A1,1 0 0,1 17,10M18,12V19H22V12H18Z" />
|
||||
</vector>
|
||||
|
|
@ -369,10 +369,22 @@
|
|||
<string name="notice_platform_mod_dm_description">You have received an important notice regarding your account from our moderation team. Please read it carefully.</string>
|
||||
<string name="notice_platform_mod_dm_acknowledge">View</string>
|
||||
|
||||
<string name="settings_category_account">Account</string>
|
||||
<string name="settings_category_general">General</string>
|
||||
<string name="settings_category_miscellaneous">Miscellaneous</string>
|
||||
<string name="settings_category_last" translatable="false">Revolt v%1$s</string>
|
||||
|
||||
<string name="settings_sessions">Sessions</string>
|
||||
<string name="settings_sessions_this_device">This Device</string>
|
||||
<string name="settings_sessions_this_device_unavailable">The session of this device is unavailable. Please **log in again** to view your current session.</string>
|
||||
<string name="settings_sessions_other_sessions">Other Active Sessions</string>
|
||||
<string name="settings_sessions_first_seen">First seen %1$s</string>
|
||||
<string name="settings_sessions_log_out_other">Log out other sessions</string>
|
||||
<string name="settings_sessions_log_out_other_description">This device will stay logged in.</string>
|
||||
<string name="settings_sessions_log_out_other_confirm">Log out all other sessions?</string>
|
||||
<string name="settings_sessions_log_out_other_confirm_yes">Log out</string>
|
||||
<string name="settings_sessions_log_out_other_confirm_no">Keep logged in</string>
|
||||
|
||||
<string name="settings_appearance">Appearance</string>
|
||||
<string name="settings_appearance_theme">Theme</string>
|
||||
<string name="settings_appearance_theme_none">System</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue