feat: retire PageHeader in favour of TopAppBar

- also make DisconnectionNotice borderless

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-01-28 17:50:54 +01:00
parent 452d2b164d
commit 5736aec64d
26 changed files with 1879 additions and 1553 deletions

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -70,6 +71,7 @@ class InviteActivity : ComponentActivity() {
val inviteCode = intent.data?.lastPathSegment val inviteCode = intent.data?.lastPathSegment
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {

View File

@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@ -212,6 +213,7 @@ class MainActivity : FragmentActivity() {
super.onResume() super.onResume()
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance) DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance)
window.statusBarColor = Color.Transparent.toArgb()
} }
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@ -223,6 +225,7 @@ class MainActivity : FragmentActivity() {
options.release = BuildConfig.VERSION_NAME options.release = BuildConfig.VERSION_NAME
} }
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
installSplashScreen().apply { installSplashScreen().apply {
@ -273,7 +276,8 @@ fun AppEntrypoint(
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
NavHost( NavHost(

View File

@ -12,12 +12,13 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -28,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -38,6 +40,7 @@ import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.REVOLT_FILES import chat.revolt.api.REVOLT_FILES
@ -45,7 +48,6 @@ import chat.revolt.api.RevoltHttp
import chat.revolt.api.schemas.AutumnResource import chat.revolt.api.schemas.AutumnResource
import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.PageHeader
import chat.revolt.provider.getAttachmentContentUri import chat.revolt.provider.getAttachmentContentUri
import chat.revolt.ui.theme.RevoltTheme import chat.revolt.ui.theme.RevoltTheme
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
@ -82,7 +84,7 @@ class ImageViewActivity : ComponentActivity() {
} }
} }
@OptIn(ExperimentalGlideComposeApi::class) @OptIn(ExperimentalGlideComposeApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) { fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}" val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
@ -187,6 +189,81 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
Scaffold( Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(
id = R.string.media_viewer_title_image,
resource.filename ?: resource.id!!
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
onClose()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
actions = {
IconButton(onClick = {
shareSubmenuIsOpen.value = true
}) {
Icon(
painter = painterResource(id = R.drawable.ic_share_24dp),
contentDescription = stringResource(id = R.string.share)
)
}
DropdownMenu(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
onClick = {
shareImage()
},
text = {
Text(
stringResource(
id = R.string.media_viewer_share_image
)
)
}
)
}
IconButton(onClick = {
saveToGallery()
}) {
Icon(
painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(
id = R.string.media_viewer_save
)
)
}
}
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) } snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv -> ) { pv ->
Surface( Surface(
@ -195,87 +272,22 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.fillMaxSize() .fillMaxSize()
) { ) {
Column { Box(
PageHeader( modifier = Modifier
text = stringResource( .clip(RectangleShape)
id = R.string.media_viewer_title_image, .fillMaxSize()
resource.filename ?: resource.id!! ) {
ZoomableGlideImage(
model = resourceUrl,
contentDescription = null,
state = rememberZoomableImageState(
rememberZoomableState(
zoomSpec = ZoomSpec(maxZoomFactor = 10f)
)
), ),
showBackButton = true,
onBackButtonClicked = onClose,
maxLines = 1,
additionalButtons = {
Row {
IconButton(onClick = {
shareSubmenuIsOpen.value = true
}) {
Icon(
painter = painterResource(id = R.drawable.ic_share_24dp),
contentDescription = stringResource(id = R.string.share)
)
}
DropdownMenu(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
onClick = {
shareImage()
},
text = {
Text(
stringResource(
id = R.string.media_viewer_share_image
)
)
}
)
}
IconButton(onClick = {
saveToGallery()
}) {
Icon(
painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(
id = R.string.media_viewer_save
)
)
}
}
}
)
Box(
modifier = Modifier modifier = Modifier
.clip(RectangleShape)
.fillMaxSize() .fillMaxSize()
) { )
ZoomableGlideImage(
model = resourceUrl,
contentDescription = null,
state = rememberZoomableImageState(
rememberZoomableState(
zoomSpec = ZoomSpec(maxZoomFactor = 10f)
)
),
modifier = Modifier
.fillMaxSize()
)
}
} }
} }
} }

View File

@ -12,12 +12,13 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -28,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -39,6 +41,7 @@ import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -50,7 +53,6 @@ import chat.revolt.api.RevoltHttp
import chat.revolt.api.schemas.AutumnResource import chat.revolt.api.schemas.AutumnResource
import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.PageHeader
import chat.revolt.provider.getAttachmentContentUri import chat.revolt.provider.getAttachmentContentUri
import chat.revolt.ui.theme.RevoltTheme import chat.revolt.ui.theme.RevoltTheme
import io.ktor.client.request.get import io.ktor.client.request.get
@ -82,6 +84,7 @@ class VideoViewActivity : ComponentActivity() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@Composable @Composable
fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) { fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
@ -199,6 +202,81 @@ fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
Scaffold( Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(
id = R.string.media_viewer_title_video,
resource.filename ?: resource.id!!
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
onClose()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
actions = {
IconButton(onClick = {
shareSubmenuIsOpen.value = true
}) {
Icon(
painter = painterResource(id = R.drawable.ic_share_24dp),
contentDescription = stringResource(id = R.string.share)
)
}
DropdownMenu(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
onClick = {
shareVideo()
},
text = {
Text(
stringResource(
id = R.string.media_viewer_share_video
)
)
}
)
}
IconButton(onClick = {
saveToGallery()
}) {
Icon(
painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(
id = R.string.media_viewer_save
)
)
}
}
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) } snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { pv -> ) { pv ->
Surface( Surface(
@ -207,89 +285,24 @@ fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.fillMaxSize() .fillMaxSize()
) { ) {
Column { Box(
PageHeader( modifier = Modifier
text = stringResource( .clip(RectangleShape)
id = R.string.media_viewer_title_video, .fillMaxSize()
resource.filename ?: resource.id!! ) {
), AndroidView(
showBackButton = true, factory = { context ->
onBackButtonClicked = onClose, PlayerView(context).apply {
maxLines = 1, setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
additionalButtons = {
Row {
IconButton(onClick = {
shareSubmenuIsOpen.value = true
}) {
Icon(
painter = painterResource(id = R.drawable.ic_share_24dp),
contentDescription = stringResource(id = R.string.share)
)
}
DropdownMenu(
expanded = shareSubmenuIsOpen.value,
onDismissRequest = {
shareSubmenuIsOpen.value = false
}
) {
DropdownMenuItem(
onClick = {
shareUrl()
},
text = {
Text(
stringResource(id = R.string.media_viewer_share_url)
)
}
)
DropdownMenuItem(
onClick = {
shareVideo()
},
text = {
Text(
stringResource(
id = R.string.media_viewer_share_video
)
)
}
)
}
IconButton(onClick = {
saveToGallery()
}) {
Icon(
painter = painterResource(id = R.drawable.ic_download_24dp),
contentDescription = stringResource(
id = R.string.media_viewer_save
)
)
}
} }
} },
) update = {
it.player = player
Box( },
modifier = Modifier modifier = Modifier
.clip(RectangleShape)
.fillMaxSize() .fillMaxSize()
) { .background(MaterialTheme.colorScheme.background)
AndroidView( )
factory = { context ->
PlayerView(context).apply {
setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
}
},
update = {
it.player = player
},
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
)
}
} }
} }
} }

View File

@ -2,9 +2,13 @@ package chat.revolt.components.chat
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
@ -44,32 +48,35 @@ private fun DisconnectedNoticeBase(
canTapToRetry: Boolean = false, canTapToRetry: Boolean = false,
onRetry: () -> Unit = {} onRetry: () -> Unit = {}
) { ) {
Row( Column {
modifier = Modifier Row(
.clickable(enabled = canTapToRetry, onClick = onRetry) modifier = Modifier
.fillMaxWidth() .clickable(enabled = canTapToRetry, onClick = onRetry)
.background(background) .fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp), .background(background)
verticalAlignment = Alignment.CenterVertically .windowInsetsPadding(WindowInsets.statusBars)
) { .padding(vertical = 8.dp, horizontal = 16.dp),
Icon( verticalAlignment = Alignment.CenterVertically
modifier = Modifier.padding(end = 8.dp), ) {
imageVector = icon, Icon(
tint = foreground, modifier = Modifier.padding(end = 8.dp),
contentDescription = null imageVector = icon,
) tint = foreground,
Text( contentDescription = null
text = text,
color = foreground,
fontWeight = FontWeight.Bold
)
if (canTapToRetry) {
Text(
text = stringResource(R.string.tap_to_reconnect),
color = foreground,
modifier = Modifier.padding(start = 8.dp),
fontWeight = FontWeight.Normal
) )
Text(
text = text,
color = foreground,
fontWeight = FontWeight.Bold
)
if (canTapToRetry) {
Text(
text = stringResource(R.string.tap_to_reconnect),
color = foreground,
modifier = Modifier.padding(start = 8.dp),
fontWeight = FontWeight.Normal
)
}
} }
} }
} }

View File

@ -1,88 +0,0 @@
package chat.revolt.components.generic
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.revolt.R
@Composable
fun PageHeader(
text: String,
modifier: Modifier = Modifier,
showBackButton: Boolean = false,
onBackButtonClicked: () -> Unit = {},
startButtons: @Composable () -> Unit = {},
additionalButtons: @Composable () -> Unit = {},
maxLines: Int = Int.MAX_VALUE
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (showBackButton) {
IconButton(onClick = onBackButtonClicked) {
Icon(
modifier = modifier,
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
}
startButtons()
Text(
text = text,
maxLines = maxLines,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.displaySmall.copy(
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Left,
fontSize = 24.sp
),
modifier = modifier
.padding(horizontal = 15.dp, vertical = 15.dp)
.weight(1f)
)
additionalButtons()
}
}
@Preview(showBackground = true)
@Composable
fun PageHeaderPreview() {
PageHeader(text = "Page Header")
}
@Preview(showBackground = true)
@Composable
fun PageHeaderPreviewWithBackButton() {
PageHeader(text = "Page Header", showBackButton = true)
}
@Preview(showBackground = true)
@Composable
fun PageHeaderPreviewWithAdditionalButtons() {
PageHeader(text = "Page Header", showBackButton = true, additionalButtons = {
IconButton(onClick = {}) {
Icon(
modifier = Modifier,
imageVector = Icons.Default.ArrowForward,
contentDescription = null
)
}
})
}

View File

@ -0,0 +1,10 @@
package chat.revolt.internals.extensions
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
val WindowInsets.Companion.zero: WindowInsets
@Composable
@NonRestartableComposable
get() = WindowInsets(left = 0, right = 0, top = 0, bottom = 0)

View File

@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.RevoltApplication import chat.revolt.RevoltApplication
@ -35,8 +37,10 @@ fun DefaultDestinationScreen(
nextDestination?.let { nextDestination?.let {
// Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours // Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours
// See the other one in MainActivity.kt // See the other one in MainActivity.kt
DynamicColors.applyToActivityIfAvailable(context.getComponentActivity() as Activity) val activity = context.getComponentActivity() as Activity
DynamicColors.applyToActivityIfAvailable(activity)
DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance) DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance)
activity.window.statusBarColor = Color.Transparent.toArgb()
navController.popBackStack(navController.graph.startDestinationRoute!!, true) navController.popBackStack(navController.graph.startDestinationRoute!!, true)
navController.navigate(it) navController.navigate(it)

View File

@ -4,6 +4,7 @@ import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -12,15 +13,21 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -38,6 +45,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -49,7 +57,6 @@ import chat.revolt.api.REVOLT_BASE
import chat.revolt.api.RevoltJson import chat.revolt.api.RevoltJson
import chat.revolt.api.routes.misc.Root import chat.revolt.api.routes.misc.Root
import chat.revolt.api.routes.misc.getRootRoute import chat.revolt.api.routes.misc.getRootRoute
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.PrimaryTabs import chat.revolt.components.generic.PrimaryTabs
import chat.revolt.internals.Platform import chat.revolt.internals.Platform
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -126,6 +133,7 @@ fun DebugInfo(viewModel: AboutViewModel) {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) { fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
val context = LocalContext.current val context = LocalContext.current
@ -146,107 +154,126 @@ fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewMo
} }
} }
Column( Scaffold(
modifier = Modifier topBar = {
.fillMaxSize() TopAppBar(
.safeDrawingPadding(), title = {
verticalArrangement = Arrangement.Center, Text(
horizontalAlignment = Alignment.CenterHorizontally text = stringResource(R.string.about),
) { maxLines = 1,
PageHeader( overflow = TextOverflow.Ellipsis,
text = stringResource(R.string.about), )
showBackButton = true, },
onBackButtonClicked = { navController.popBackStack() } navigationIcon = {
) IconButton(onClick = {
navController.popBackStack()
PrimaryTabs( }) {
tabs = listOf( Icon(
stringResource(R.string.about_tab_version), imageVector = Icons.Default.ArrowBack,
stringResource(R.string.about_tab_details) contentDescription = stringResource(id = R.string.back)
),
currentIndex = viewModel.selectedTabIndex,
onTabSelected = { viewModel.selectedTabIndex = it }
)
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.weight(1f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (viewModel.root == null) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
)
} else {
when (viewModel.selectedTabIndex) {
0 -> {
Image(
painter = painterResource(R.drawable.revolt_logo_wide),
contentDescription = stringResource(R.string.about_full_name),
colorFilter = ColorFilter.tint(LocalContentColor.current),
modifier = Modifier
.width(250.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.about_full_name),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = BuildConfig.VERSION_NAME,
style = MaterialTheme.typography.labelMedium.copy(
fontWeight = FontWeight.Normal
),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = stringResource(R.string.about_brought_to_you_by),
style = MaterialTheme.typography.labelSmall.copy(
fontWeight = FontWeight.Light
),
color = LocalContentColor.current.copy(
alpha = 0.5f
),
textAlign = TextAlign.Center
) )
} }
}
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
PrimaryTabs(
tabs = listOf(
stringResource(R.string.about_tab_version),
stringResource(R.string.about_tab_details)
),
currentIndex = viewModel.selectedTabIndex,
onTabSelected = { viewModel.selectedTabIndex = it }
)
1 -> { Column(
DebugInfo(viewModel) modifier = Modifier
TextButton(onClick = ::copyDebugInformation) { .fillMaxWidth()
Text(text = stringResource(id = R.string.copy)) .fillMaxHeight()
.weight(1f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (viewModel.root == null) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
)
} else {
when (viewModel.selectedTabIndex) {
0 -> {
Image(
painter = painterResource(R.drawable.revolt_logo_wide),
contentDescription = stringResource(R.string.about_full_name),
colorFilter = ColorFilter.tint(LocalContentColor.current),
modifier = Modifier
.width(250.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.about_full_name),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = BuildConfig.VERSION_NAME,
style = MaterialTheme.typography.labelMedium.copy(
fontWeight = FontWeight.Normal
),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = stringResource(R.string.about_brought_to_you_by),
style = MaterialTheme.typography.labelSmall.copy(
fontWeight = FontWeight.Light
),
color = LocalContentColor.current.copy(
alpha = 0.5f
),
textAlign = TextAlign.Center
)
}
1 -> {
DebugInfo(viewModel)
TextButton(onClick = ::copyDebugInformation) {
Text(text = stringResource(id = R.string.copy))
}
}
} }
} }
} }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ElevatedButton(
onClick = { navController.navigate("about/oss") },
modifier = Modifier
.testTag("view_oss_attribution")
) {
Text(text = stringResource(id = R.string.oss_attribution))
}
}
} }
} }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ElevatedButton(
onClick = { navController.navigate("about/oss") },
modifier = Modifier
.testTag("view_oss_attribution")
) {
Text(text = stringResource(id = R.string.oss_attribution))
}
}
} }
} }

View File

@ -2,21 +2,28 @@ package chat.revolt.screens.about
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -27,13 +34,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.AttributionItem import chat.revolt.components.screens.settings.AttributionItem
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -144,66 +152,85 @@ fun AttributionScreen(navController: NavController) {
} }
} }
Column( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
modifier = Modifier
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(R.string.oss_attribution),
showBackButton = true,
onBackButtonClicked = { navController.popBackStack() }
)
libraries?.let { Scaffold(
LazyColumn { modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
item { topBar = {
Column( LargeTopAppBar(
modifier = Modifier scrollBehavior = scrollBehavior,
.padding(16.dp) title = {
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.oss_attribution_body)
)
Text(
text = stringResource(R.string.oss_attribution_body_2)
)
Text(
text = stringResource(R.string.oss_attribution_warning),
color = MaterialTheme.colorScheme.error
)
Text(
text = stringResource(
R.string.oss_attribution_generation_date,
libraries?.metadata?.generated ?: ""
),
color = LocalContentColor.current.copy(alpha = 0.6f)
)
}
}
items(
items = it.libraries.sortedBy { library -> library.name }
) { library ->
AttributionItem(library = library) {
licenceSheetOpen = true
licenseSheetTarget = library.licenses.first()
}
}
item(key = "cat") {
Text( Text(
text = "🐈", text = stringResource(R.string.oss_attribution),
modifier = Modifier maxLines = 1,
.fillMaxWidth() overflow = TextOverflow.Ellipsis,
.padding(16.dp),
textAlign = TextAlign.Center
) )
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
libraries?.let {
LazyColumn {
item {
Column(
modifier = Modifier
.padding(16.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.oss_attribution_body)
)
Text(
text = stringResource(R.string.oss_attribution_body_2)
)
Text(
text = stringResource(R.string.oss_attribution_warning),
color = MaterialTheme.colorScheme.error
)
Text(
text = stringResource(
R.string.oss_attribution_generation_date,
libraries?.metadata?.generated ?: ""
),
color = LocalContentColor.current.copy(alpha = 0.6f)
)
}
}
items(
items = it.libraries.sortedBy { library -> library.name }
) { library ->
AttributionItem(library = library) {
licenceSheetOpen = true
licenseSheetTarget = library.licenses.first()
}
}
item(key = "cat") {
Text(
text = "🐈",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textAlign = TextAlign.Center
)
}
} }
} }
} }

View File

@ -12,13 +12,17 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -723,7 +727,6 @@ fun ChatRouterScreen(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.safeDrawingPadding()
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected
@ -737,10 +740,17 @@ fun ChatRouterScreen(
) )
} }
AnimatedVisibility(
visible = RealtimeSocket.disconnectionState == DisconnectionState.Connected
) {
Spacer(Modifier.windowInsetsPadding(WindowInsets.statusBars))
}
if (useTabletAwareUI) { if (useTabletAwareUI) {
Row { Row {
DismissibleDrawerSheet( DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent drawerContainerColor = Color.Transparent,
windowInsets = WindowInsets.navigationBars
) { ) {
Sidebar( Sidebar(
viewModel = viewModel, viewModel = viewModel,
@ -781,7 +791,8 @@ fun ChatRouterScreen(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
DismissibleDrawerSheet( DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent drawerContainerColor = Color.Transparent,
windowInsets = WindowInsets.navigationBars
) { ) {
Sidebar( Sidebar(
viewModel = viewModel, viewModel = viewModel,
@ -1044,7 +1055,6 @@ fun Sidebar(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ChannelNavigator( fun ChannelNavigator(
navController: NavHostController, navController: NavHostController,
@ -1111,7 +1121,7 @@ fun ChannelNavigator(
toggleDrawer() toggleDrawer()
} }
NoCurrentChannelScreen() NoCurrentChannelScreen(useDrawer = useDrawer, onDrawerClicked = toggleDrawer)
} }
dialog("report/message/{messageId}") { backStackEntry -> dialog("report/message/{messageId}") { backStackEntry ->

View File

@ -3,7 +3,9 @@ package chat.revolt.screens.chat.views
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
@ -12,7 +14,9 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -23,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.internals.FriendRequests import chat.revolt.api.internals.FriendRequests
import chat.revolt.api.routes.user.unfriendUser import chat.revolt.api.routes.user.unfriendUser
@ -30,8 +35,8 @@ import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.chat.MemberListItem import chat.revolt.components.chat.MemberListItem
import chat.revolt.components.generic.CountableListHeader import chat.revolt.components.generic.CountableListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.SheetClickable import chat.revolt.components.generic.SheetClickable
import chat.revolt.internals.extensions.zero
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -86,154 +91,168 @@ fun FriendsScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) {
} }
} }
Column( Scaffold(
modifier = Modifier topBar = {
.fillMaxHeight() TopAppBar(
) { title = {
PageHeader( Text(
text = stringResource(R.string.friends), text = stringResource(R.string.friends),
startButtons = { maxLines = 1,
if (useDrawer) { overflow = TextOverflow.Ellipsis,
IconButton(onClick = onDrawerClicked) { )
},
navigationIcon = {
if (useDrawer) {
IconButton(onClick = {
onDrawerClicked()
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(id = R.string.menu)
)
}
}
},
actions = {
IconButton(onClick = {
optionsSheetShown = true
}) {
Icon( Icon(
imageVector = Icons.Default.Menu, imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.menu) contentDescription = stringResource(R.string.menu)
) )
} }
} },
}, windowInsets = WindowInsets.zero
additionalButtons = { )
IconButton(onClick = { },
optionsSheetShown = true ) { pv ->
}) { Column(
Icon( modifier = Modifier
imageVector = Icons.Default.MoreVert, .padding(pv)
contentDescription = stringResource(R.string.menu) .fillMaxHeight()
) {
LazyColumn {
stickyHeader(key = "incoming") {
CountableListHeader(
text = stringResource(id = R.string.friends_incoming_requests),
count = FriendRequests.getIncoming().size
) )
} }
}
)
LazyColumn { items(FriendRequests.getIncoming().size) {
stickyHeader(key = "incoming") { val item = FriendRequests.getIncoming()[it]
CountableListHeader( MemberListItem(
text = stringResource(id = R.string.friends_incoming_requests), member = null,
count = FriendRequests.getIncoming().size user = item,
) serverId = null,
} userId = item.id ?: "",
modifier = Modifier.clickable {
items(FriendRequests.getIncoming().size) { scope.launch {
val item = FriendRequests.getIncoming()[it] item.id?.let { userId ->
MemberListItem( ActionChannel.send(Action.OpenUserSheet(userId, null))
member = null, }
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
} }
} }
} )
) }
}
stickyHeader(key = "outgoing") { stickyHeader(key = "outgoing") {
CountableListHeader( CountableListHeader(
text = stringResource(id = R.string.friends_outgoing_requests), text = stringResource(id = R.string.friends_outgoing_requests),
count = FriendRequests.getOutgoing().size count = FriendRequests.getOutgoing().size
) )
} }
items(FriendRequests.getOutgoing().size) { items(FriendRequests.getOutgoing().size) {
val item = FriendRequests.getOutgoing()[it] val item = FriendRequests.getOutgoing()[it]
MemberListItem( MemberListItem(
member = null, member = null,
user = item, user = item,
serverId = null, serverId = null,
userId = item.id ?: "", userId = item.id ?: "",
modifier = Modifier.clickable { modifier = Modifier.clickable {
scope.launch { scope.launch {
item.id?.let { userId -> item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null)) ActionChannel.send(Action.OpenUserSheet(userId, null))
}
} }
} }
} )
) }
}
stickyHeader(key = "online") { stickyHeader(key = "online") {
CountableListHeader( CountableListHeader(
text = stringResource(id = R.string.status_online), text = stringResource(id = R.string.status_online),
count = FriendRequests.getOnlineFriends().size count = FriendRequests.getOnlineFriends().size
) )
} }
items(FriendRequests.getOnlineFriends().size) { items(FriendRequests.getOnlineFriends().size) {
val item = FriendRequests.getOnlineFriends()[it] val item = FriendRequests.getOnlineFriends()[it]
MemberListItem( MemberListItem(
member = null, member = null,
user = item, user = item,
serverId = null, serverId = null,
userId = item.id ?: "", userId = item.id ?: "",
modifier = Modifier.clickable { modifier = Modifier.clickable {
scope.launch { scope.launch {
item.id?.let { userId -> item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null)) ActionChannel.send(Action.OpenUserSheet(userId, null))
}
} }
} }
} )
) }
}
stickyHeader(key = "not_online") { stickyHeader(key = "not_online") {
CountableListHeader( CountableListHeader(
text = stringResource(id = R.string.friends_all), text = stringResource(id = R.string.friends_all),
count = FriendRequests.getFriends(true).size count = FriendRequests.getFriends(true).size
) )
} }
items(FriendRequests.getFriends(true).size) { items(FriendRequests.getFriends(true).size) {
val item = FriendRequests.getFriends(true)[it] val item = FriendRequests.getFriends(true)[it]
MemberListItem( MemberListItem(
member = null, member = null,
user = item, user = item,
serverId = null, serverId = null,
userId = item.id ?: "", userId = item.id ?: "",
modifier = Modifier.clickable { modifier = Modifier.clickable {
scope.launch { scope.launch {
item.id?.let { userId -> item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null)) ActionChannel.send(Action.OpenUserSheet(userId, null))
}
} }
} }
} )
) }
}
stickyHeader(key = "blocked") { stickyHeader(key = "blocked") {
CountableListHeader( CountableListHeader(
text = stringResource(id = R.string.friends_blocked), text = stringResource(id = R.string.friends_blocked),
count = FriendRequests.getBlocked().size count = FriendRequests.getBlocked().size
) )
} }
items(FriendRequests.getBlocked().size) { items(FriendRequests.getBlocked().size) {
val item = FriendRequests.getBlocked()[it] val item = FriendRequests.getBlocked()[it]
MemberListItem( MemberListItem(
member = null, member = null,
user = item, user = item,
serverId = null, serverId = null,
userId = item.id ?: "", userId = item.id ?: "",
modifier = Modifier.clickable { modifier = Modifier.clickable {
scope.launch { scope.launch {
item.id?.let { userId -> item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null)) ActionChannel.send(Action.OpenUserSheet(userId, null))
}
} }
} }
} )
) }
} }
} }
} }

View File

@ -10,15 +10,19 @@ import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -26,14 +30,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.activities.InviteActivity import chat.revolt.activities.InviteActivity
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.home.LinkOnHome import chat.revolt.components.screens.home.LinkOnHome
import chat.revolt.internals.extensions.zero
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) { fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
@ -49,59 +55,75 @@ fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked
label = "catRotation" label = "catRotation"
) )
Column( Scaffold(
modifier = Modifier.safeDrawingPadding() topBar = {
) { TopAppBar(
PageHeader( title = {
text = stringResource(id = R.string.home), Text(
startButtons = { text = stringResource(R.string.home),
if (useDrawer) { maxLines = 1,
IconButton(onClick = onDrawerClicked) { overflow = TextOverflow.Ellipsis,
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(R.string.menu)
)
}
}
}
)
Box(
modifier = Modifier
.weight(1f)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "🐈",
fontSize = 100.sp,
modifier = Modifier.rotate(catRotation)
)
}
Column(Modifier.padding(16.dp)) {
LinkOnHome(
icon = { modifier ->
Icon(
imageVector = Icons.Default.Star,
contentDescription = stringResource(id = R.string.home_join_jenvolt),
modifier = modifier
) )
}, },
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) }, navigationIcon = {
description = { if (useDrawer) {
Text( IconButton(onClick = {
text = stringResource(id = R.string.home_join_jenvolt_description) onDrawerClicked()
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(id = R.string.menu)
)
}
}
},
windowInsets = WindowInsets.zero
)
},
) { pv ->
Column(
modifier = Modifier
.padding(pv)
.fillMaxHeight()
) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "🐈",
fontSize = 100.sp,
modifier = Modifier.rotate(catRotation)
)
}
Column(Modifier.padding(16.dp)) {
LinkOnHome(
icon = { modifier ->
Icon(
imageVector = Icons.Default.Star,
contentDescription = stringResource(id = R.string.home_join_jenvolt),
modifier = modifier
)
},
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
description = {
Text(
text = stringResource(id = R.string.home_join_jenvolt_description)
)
}
) {
context.startActivity(
Intent(
context,
InviteActivity::class.java
)
.setData(Uri.parse("https://rvlt.gg/jen"))
.setAction(Intent.ACTION_VIEW)
) )
} }
) {
context.startActivity(
Intent(
context,
InviteActivity::class.java
)
.setData(Uri.parse("https://rvlt.gg/jen"))
.setAction(Intent.ACTION_VIEW)
)
} }
} }
} }

View File

@ -2,10 +2,18 @@ package chat.revolt.screens.chat.views
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -14,27 +22,51 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.revolt.R
import chat.revolt.internals.extensions.zero
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun NoCurrentChannelScreen() { fun NoCurrentChannelScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) {
Column( Scaffold(
modifier = Modifier topBar = {
.fillMaxSize() TopAppBar(
.padding(64.dp), title = {},
verticalArrangement = Arrangement.Center, navigationIcon = {
horizontalAlignment = Alignment.CenterHorizontally if (useDrawer) {
) { IconButton(onClick = {
Text( onDrawerClicked()
text = stringResource(R.string.no_active_channel), }) {
style = MaterialTheme.typography.labelLarge, Icon(
textAlign = TextAlign.Center, imageVector = Icons.Default.Menu,
fontSize = 24.sp, contentDescription = stringResource(id = R.string.menu)
modifier = Modifier.padding(bottom = 16.dp) )
) }
Text( }
text = stringResource(R.string.no_active_channel_body), },
style = MaterialTheme.typography.bodyMedium, windowInsets = WindowInsets.zero
textAlign = TextAlign.Center )
) },
) { pv ->
Column(
modifier = Modifier
.padding(pv)
.fillMaxSize()
.padding(64.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.no_active_channel),
style = MaterialTheme.typography.labelLarge,
textAlign = TextAlign.Center,
fontSize = 24.sp,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = stringResource(R.string.no_active_channel_body),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
}
} }
} }

View File

@ -20,12 +20,14 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
@ -258,7 +260,7 @@ fun ChannelScreen(
Column( Column(
modifier = Modifier modifier = Modifier
.imePadding() .imePadding()
.safeDrawingPadding() .windowInsetsPadding(WindowInsets.navigationBars)
) { ) {
ChannelHeader( ChannelHeader(
channel = channel ?: Channel( channel = channel ?: Channel(

View File

@ -2,29 +2,55 @@ package chat.revolt.screens.services
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.services.DiscoverView import chat.revolt.components.screens.services.DiscoverView
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DiscoverScreen(navController: NavController) { fun DiscoverScreen(navController: NavController) {
Column( Scaffold(
Modifier topBar = {
.fillMaxSize() TopAppBar(
.safeDrawingPadding() title = {
) { Text(
PageHeader( text = stringResource(R.string.discover),
text = stringResource(R.string.discover), maxLines = 1,
showBackButton = true, overflow = TextOverflow.Ellipsis,
onBackButtonClicked = { )
navController.popBackStack() },
} navigationIcon = {
) IconButton(onClick = {
DiscoverView() navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
}
)
}
) { pv ->
Column(
Modifier
.padding(pv)
.fillMaxSize()
) {
DiscoverView()
}
} }
} }

View File

@ -10,6 +10,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@ -19,11 +20,11 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
@ -33,10 +34,14 @@ import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -51,11 +56,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
@ -68,7 +75,6 @@ import chat.revolt.api.RevoltJson
import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.ListHeader import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.appearance.ColourChip import chat.revolt.components.screens.settings.appearance.ColourChip
import chat.revolt.ui.theme.ClearRippleTheme import chat.revolt.ui.theme.ClearRippleTheme
import chat.revolt.ui.theme.OverridableColourScheme import chat.revolt.ui.theme.OverridableColourScheme
@ -263,218 +269,234 @@ fun AppearanceSettingsScreen(
} }
} }
Column( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings_appearance),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
Column( Scaffold(
modifier = Modifier modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxSize() topBar = {
.verticalScroll(rememberScrollState()) LargeTopAppBar(
) { scrollBehavior = scrollBehavior,
ListHeader { title = {
Text(stringResource(R.string.settings_appearance_theme)) Text(
} text = stringResource(R.string.settings_appearance),
maxLines = 1,
FlowRow( overflow = TextOverflow.Ellipsis,
horizontalArrangement = Arrangement.spacedBy(10.dp), )
verticalArrangement = Arrangement.spacedBy(10.dp), },
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.padding(start = 20.dp, end = 20.dp) .verticalScroll(rememberScrollState())
) { ) {
ColourChip( ListHeader {
color = Color(0xff191919), Text(stringResource(R.string.settings_appearance_theme))
text = stringResource(id = R.string.settings_appearance_theme_revolt),
selected = GlobalState.theme == Theme.Revolt,
modifier = Modifier
.weight(1f)
.testTag("set_theme_revolt")
) {
viewModel.saveNewTheme(Theme.Revolt)
} }
ColourChip( FlowRow(
color = Color(0xfff7f7f7), horizontalArrangement = Arrangement.spacedBy(10.dp),
text = stringResource(id = R.string.settings_appearance_theme_light), verticalArrangement = Arrangement.spacedBy(10.dp),
selected = GlobalState.theme == Theme.Light,
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.testTag("set_theme_light") .padding(start = 20.dp, end = 20.dp)
) { ) {
viewModel.saveNewTheme(Theme.Light)
}
ColourChip(
color = Color(0xff000000),
text = stringResource(id = R.string.settings_appearance_theme_amoled),
selected = GlobalState.theme == Theme.Amoled,
modifier = Modifier
.weight(1f)
.testTag("set_theme_amoled")
) {
viewModel.saveNewTheme(Theme.Amoled)
}
ColourChip(
color = if (isSystemInDarkTheme()) Color(0xff191919) else Color(0xfff7f7f7),
text = stringResource(id = R.string.settings_appearance_theme_none),
selected = GlobalState.theme == Theme.None,
modifier = Modifier
.weight(1f)
.testTag("set_theme_none")
) {
viewModel.saveNewTheme(Theme.None)
}
if (systemSupportsDynamicColors()) {
ColourChip( ColourChip(
color = dynamicDarkColorScheme(LocalContext.current).primary, color = Color(0xff191919),
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic), text = stringResource(id = R.string.settings_appearance_theme_revolt),
selected = GlobalState.theme == Theme.M3Dynamic, selected = GlobalState.theme == Theme.Revolt,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.testTag("set_theme_m3dynamic") .testTag("set_theme_revolt")
) { ) {
viewModel.saveNewTheme(Theme.M3Dynamic) viewModel.saveNewTheme(Theme.Revolt)
} }
} else {
ColourChip( ColourChip(
color = Color(0xffa0a0a0), color = Color(0xfff7f7f7),
text = stringResource( text = stringResource(id = R.string.settings_appearance_theme_light),
id = R.string.settings_appearance_theme_m3dynamic_unsupported selected = GlobalState.theme == Theme.Light,
),
selected = false,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.testTag("set_theme_m3dynamic_unsupported") .testTag("set_theme_light")
) { ) {
Toast.makeText( viewModel.saveNewTheme(Theme.Light)
context,
context.getString(
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
),
Toast.LENGTH_SHORT
).show()
} }
}
}
Spacer(modifier = Modifier.height(20.dp)) ColourChip(
color = Color(0xff000000),
Row( text = stringResource(id = R.string.settings_appearance_theme_amoled),
modifier = Modifier selected = GlobalState.theme == Theme.Amoled,
.clickable {
viewModel.showColourOverrides = !viewModel.showColourOverrides
}
.fillMaxWidth()
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = null,
modifier = Modifier modifier = Modifier
.padding(start = 20.dp, end = 4.dp) .weight(1f)
.rotate(colourOverridesOpenerArrowRotation) .testTag("set_theme_amoled")
)
}
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides),
style = MaterialTheme.typography.labelLarge,
modifier = Modifier
.weight(1f)
)
if (LocalLayoutDirection.current == LayoutDirection.Rtl) {
Icon(
imageVector = Icons.Default.KeyboardArrowLeft,
contentDescription = null,
modifier = Modifier
.padding(start = 4.dp, end = 20.dp)
.rotate(colourOverridesOpenerArrowRotation)
)
}
}
AnimatedVisibility(viewModel.showColourOverrides) {
Column {
Spacer(modifier = Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
) { ) {
TextButton( viewModel.saveNewTheme(Theme.Amoled)
onClick = {
filePicker.launch(arrayOf("*/*"))
},
modifier = Modifier.weight(1f)
) {
Icon(
painter = painterResource(R.drawable.ic_folder_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides_import)
)
}
TextButton(
onClick = {
fileSaver.launch("${SyncedSettings.android.theme}-colours.rato")
},
modifier = Modifier.weight(1f)
) {
Icon(
painter = painterResource(R.drawable.ic_content_save_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides_export)
)
}
} }
Spacer(modifier = Modifier.height(10.dp)) ColourChip(
color = if (isSystemInDarkTheme()) Color(0xff191919) else Color(0xfff7f7f7),
OverridableColourScheme.fieldNames.forEach { fieldName -> text = stringResource(id = R.string.settings_appearance_theme_none),
val value = selected = GlobalState.theme == Theme.None,
SyncedSettings.android.colourOverrides?.getFieldByName(fieldName) modifier = Modifier
?: MaterialTheme.colorScheme.getFieldByName(fieldName) .weight(1f)
.testTag("set_theme_none")
) {
viewModel.saveNewTheme(Theme.None)
}
if (systemSupportsDynamicColors()) {
ColourChip( ColourChip(
color = Color(value ?: 0), color = dynamicDarkColorScheme(LocalContext.current).primary,
text = OverridableColourScheme.fieldNameToResource[fieldName] text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
?.let { context.getString(it) } selected = GlobalState.theme == Theme.M3Dynamic,
?: fieldName, modifier = Modifier
.weight(1f)
.testTag("set_theme_m3dynamic")
) {
viewModel.saveNewTheme(Theme.M3Dynamic)
}
} else {
ColourChip(
color = Color(0xffa0a0a0),
text = stringResource(
id = R.string.settings_appearance_theme_m3dynamic_unsupported
),
selected = false,
modifier = Modifier
.weight(1f)
.testTag("set_theme_m3dynamic_unsupported")
) {
Toast.makeText(
context,
context.getString(
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
),
Toast.LENGTH_SHORT
).show()
}
}
}
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier
.clickable {
viewModel.showColourOverrides = !viewModel.showColourOverrides
}
.fillMaxWidth()
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = null,
modifier = Modifier
.padding(start = 20.dp, end = 4.dp)
.rotate(colourOverridesOpenerArrowRotation)
)
}
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides),
style = MaterialTheme.typography.labelLarge,
modifier = Modifier
.weight(1f)
)
if (LocalLayoutDirection.current == LayoutDirection.Rtl) {
Icon(
imageVector = Icons.Default.KeyboardArrowLeft,
contentDescription = null,
modifier = Modifier
.padding(start = 4.dp, end = 20.dp)
.rotate(colourOverridesOpenerArrowRotation)
)
}
}
AnimatedVisibility(viewModel.showColourOverrides) {
Column {
Spacer(modifier = Modifier.height(10.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 20.dp, end = 20.dp) .padding(horizontal = 20.dp)
.testTag("set_colour_override_$fieldName")
) { ) {
viewModel.selectedOverrideName = fieldName TextButton(
viewModel.selectedOverrideInitialValue = value onClick = {
viewModel.overridePickerSheetVisible = true filePicker.launch(arrayOf("*/*"))
},
modifier = Modifier.weight(1f)
) {
Icon(
painter = painterResource(R.drawable.ic_folder_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides_import)
)
}
TextButton(
onClick = {
fileSaver.launch("${SyncedSettings.android.theme}-colours.rato")
},
modifier = Modifier.weight(1f)
) {
Icon(
painter = painterResource(R.drawable.ic_content_save_24dp),
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_appearance_colour_overrides_export)
)
}
}
Spacer(modifier = Modifier.height(10.dp))
OverridableColourScheme.fieldNames.forEach { fieldName ->
val value =
SyncedSettings.android.colourOverrides?.getFieldByName(fieldName)
?: MaterialTheme.colorScheme.getFieldByName(fieldName)
ColourChip(
color = Color(value ?: 0),
text = OverridableColourScheme.fieldNameToResource[fieldName]
?.let { context.getString(it) }
?: fieldName,
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp)
.testTag("set_colour_override_$fieldName")
) {
viewModel.selectedOverrideName = fieldName
viewModel.selectedOverrideInitialValue = value
viewModel.overridePickerSheetVisible = true
}
} }
} }
} }

View File

@ -5,14 +5,21 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -20,12 +27,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.R import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.internals.Changelogs import chat.revolt.internals.Changelogs
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import chat.revolt.sheets.ChangelogSheet import chat.revolt.sheets.ChangelogSheet
@ -64,50 +72,70 @@ fun ChangelogsSettingsScreen(
ChangelogSheet(version = currentChangelog) ChangelogSheet(version = currentChangelog)
} }
} }
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Column( Scaffold(
modifier = Modifier modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxSize() topBar = {
.safeDrawingPadding() LargeTopAppBar(
) { scrollBehavior = scrollBehavior,
PageHeader( title = {
text = stringResource(R.string.settings_changelogs), Text(
showBackButton = true, text = stringResource(R.string.settings_changelogs),
onBackButtonClicked = { maxLines = 1,
navController.popBackStack() overflow = TextOverflow.Ellipsis,
}
)
LazyColumn {
items(
viewModel.list.size,
key = { viewModel.list.keys.elementAt(it) }
) { index ->
val version = viewModel.list.keys.elementAt(index)
val changelog = viewModel.list[version]!!
Column(
modifier = Modifier
.clickable {
currentChangelog = version
sheetOpen = true
}
.fillMaxWidth()
) {
ListItem(
headlineContent = {
Text(
text = changelog.version,
style = MaterialTheme.typography.headlineSmall
)
},
supportingContent = {
Text(
text = changelog.summary,
)
}
) )
Divider() },
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Column(
modifier = Modifier
.padding(pv)
.fillMaxSize()
) {
LazyColumn {
items(
viewModel.list.size,
key = { viewModel.list.keys.elementAt(it) }
) { index ->
val version = viewModel.list.keys.elementAt(index)
val changelog = viewModel.list[version]!!
Column(
modifier = Modifier
.clickable {
currentChangelog = version
sheetOpen = true
}
.fillMaxWidth()
) {
ListItem(
headlineContent = {
Text(
text = changelog.version,
style = MaterialTheme.typography.headlineSmall
)
},
supportingContent = {
Text(
text = changelog.summary,
)
}
)
Divider()
}
} }
} }
} }

View File

@ -5,15 +5,16 @@ import android.util.Log
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
@ -21,9 +22,14 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -32,21 +38,24 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.BuildConfig import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.api.RevoltHttp import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson import chat.revolt.api.RevoltJson
import chat.revolt.components.generic.PageHeader
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -135,6 +144,7 @@ class ClosedBetaUpdaterScreenViewModel : ViewModel() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ClosedBetaUpdaterScreen( fun ClosedBetaUpdaterScreen(
navController: NavController, navController: NavController,
@ -142,165 +152,181 @@ fun ClosedBetaUpdaterScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column( val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = "Closed Beta Updater",
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
Column( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
horizontalAlignment = Alignment.CenterHorizontally topBar = {
) { TopAppBar(
Column( scrollBehavior = scrollBehavior,
modifier = Modifier title = {
.weight(1f) Text(
.fillMaxWidth(), text = "Closed Beta Updater",
verticalArrangement = Arrangement.Center, maxLines = 1,
horizontalAlignment = Alignment.CenterHorizontally overflow = TextOverflow.Ellipsis,
) { )
Text( },
text = "Revolt ${BuildConfig.VERSION_NAME}/${BuildConfig.VERSION_CODE}", navigationIcon = {
style = MaterialTheme.typography.headlineSmall, IconButton(onClick = {
modifier = Modifier.padding(bottom = 10.dp) navController.popBackStack()
) }) {
when (viewModel.updateState) {
UpdateState.NotChecked -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon( Icon(
imageVector = Icons.Default.Search, imageVector = Icons.Default.ArrowBack,
contentDescription = null, contentDescription = stringResource(id = R.string.back)
tint = Color(0xFF585858),
modifier = Modifier.size(100.dp)
)
Text(
text = "Not yet checked for updates",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
) )
} }
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Revolt ${BuildConfig.VERSION_NAME}/${BuildConfig.VERSION_CODE}",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp)
)
UpdateState.UpdateAvailable -> Column( when (viewModel.updateState) {
horizontalAlignment = Alignment.CenterHorizontally UpdateState.NotChecked -> Column(
) { horizontalAlignment = Alignment.CenterHorizontally
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
tint = Color(0xFFFF5252),
modifier = Modifier.size(100.dp)
)
Text(
text = AnnotatedString.Builder().apply {
append("You are out of date\n\nBuild ")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("${viewModel.newestBuild}")
pop()
append(" is available")
}.toAnnotatedString(),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 10.dp)
)
AnimatedVisibility(
visible = viewModel.updateState == UpdateState.UpdateAvailable
) { ) {
ElevatedButton(onClick = { Icon(
viewUrlInBrowser( imageVector = Icons.Default.Search,
ctx = context, contentDescription = null,
url = "${BuildConfig.ANALYSIS_BASEURL}/api/distribution/android/download?build=${viewModel.newestBuild}&token=${viewModel.newestDownloadToken}" tint = Color(0xFF585858),
) modifier = Modifier.size(100.dp)
}) { )
Text(text = "Download") Text(
text = "Not yet checked for updates",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.UpdateAvailable -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
tint = Color(0xFFFF5252),
modifier = Modifier.size(100.dp)
)
Text(
text = AnnotatedString.Builder().apply {
append("You are out of date\n\nBuild ")
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("${viewModel.newestBuild}")
pop()
append(" is available")
}.toAnnotatedString(),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 10.dp)
)
AnimatedVisibility(
visible = viewModel.updateState == UpdateState.UpdateAvailable
) {
ElevatedButton(onClick = {
viewUrlInBrowser(
ctx = context,
url = "${BuildConfig.ANALYSIS_BASEURL}/api/distribution/android/download?build=${viewModel.newestBuild}&token=${viewModel.newestDownloadToken}"
)
}) {
Text(text = "Download")
}
} }
} }
UpdateState.Checking -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = null,
tint = Color(0xFFEEFF41),
modifier = Modifier.size(100.dp)
)
Text(
text = "Checking for updates...",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.UpToDate -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = Color(0xFF00E676),
modifier = Modifier.size(100.dp)
)
Text(
text = "Up to date",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.ErrorChecking -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
tint = Color(0xFFFF5252),
modifier = Modifier.size(100.dp)
)
Text(
text = "Error checking for updates",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.RequestingUpdateToken -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
tint = Color(0xFFEEFF41),
modifier = Modifier.size(100.dp)
)
Text(
text = "Requesting update token...",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
} }
UpdateState.Checking -> Column( Spacer(modifier = Modifier.height(10.dp))
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = null,
tint = Color(0xFFEEFF41),
modifier = Modifier.size(100.dp)
)
Text(
text = "Checking for updates...",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.UpToDate -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = Color(0xFF00E676),
modifier = Modifier.size(100.dp)
)
Text(
text = "Up to date",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.ErrorChecking -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
tint = Color(0xFFFF5252),
modifier = Modifier.size(100.dp)
)
Text(
text = "Error checking for updates",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
UpdateState.RequestingUpdateToken -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
tint = Color(0xFFEEFF41),
modifier = Modifier.size(100.dp)
)
Text(
text = "Requesting update token...",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 10.dp)
)
}
} }
Spacer(modifier = Modifier.height(10.dp)) Button(
} onClick = {
viewModel.checkForUpdates()
Button( },
onClick = { modifier = Modifier
viewModel.checkForUpdates() .padding(bottom = 10.dp)
}, ) {
modifier = Modifier Text(text = "Check for updates")
.padding(bottom = 10.dp) }
) {
Text(text = "Check for updates")
} }
} }
} }

View File

@ -5,25 +5,35 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.components.generic.PageHeader import chat.revolt.R
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class DebugSettingsScreenViewModel @Inject constructor( class DebugSettingsScreenViewModel @Inject constructor(
@ -46,56 +56,77 @@ class DebugSettingsScreenViewModel @Inject constructor(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DebugSettingsScreen( fun DebugSettingsScreen(
navController: NavController, navController: NavController,
viewModel: DebugSettingsScreenViewModel = hiltViewModel() viewModel: DebugSettingsScreenViewModel = hiltViewModel()
) { ) {
Column( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = "Debug",
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = "Debug",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(pv)
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(20.dp)
) { ) {
Text( Column(
text = "Sparks", modifier = Modifier
style = MaterialTheme.typography.headlineSmall, .fillMaxSize()
modifier = Modifier.padding(bottom = 10.dp) .verticalScroll(rememberScrollState())
) .padding(20.dp)
Row(
modifier = Modifier.horizontalScroll(rememberScrollState())
) { ) {
TextButton(onClick = { viewModel.forgetSidebarSparkShown() }) { Text(
Text("Forget sidebar spark") text = "Sparks",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp)
)
Row(
modifier = Modifier.horizontalScroll(rememberScrollState())
) {
TextButton(onClick = { viewModel.forgetSidebarSparkShown() }) {
Text("Forget sidebar spark")
}
ElevatedButton(onClick = { viewModel.forgetAllSparks() }) {
Text("Forget all sparks")
}
} }
ElevatedButton(onClick = { viewModel.forgetAllSparks() }) {
Text("Forget all sparks")
}
}
Text( Text(
text = "Changelogs", text = "Changelogs",
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp) modifier = Modifier.padding(bottom = 10.dp)
) )
Row( Row(
modifier = Modifier.horizontalScroll(rememberScrollState()) modifier = Modifier.horizontalScroll(rememberScrollState())
) { ) {
ElevatedButton(onClick = { viewModel.forgetLatestChangelog() }) { ElevatedButton(onClick = { viewModel.forgetLatestChangelog() }) {
Text("Mark latest changelog as unread") Text("Mark latest changelog as unread")
}
} }
} }
} }

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@ -12,20 +13,25 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
@ -33,7 +39,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -46,7 +54,6 @@ import chat.revolt.api.routes.user.fetchUserProfile
import chat.revolt.api.routes.user.patchSelf import chat.revolt.api.routes.user.patchSelf
import chat.revolt.api.schemas.Profile import chat.revolt.api.schemas.Profile
import chat.revolt.components.generic.InlineMediaPicker import chat.revolt.components.generic.InlineMediaPicker
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.RawUserOverview import chat.revolt.components.screens.settings.RawUserOverview
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -226,159 +233,90 @@ class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val
} }
} }
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun ProfileSettingsScreen( fun ProfileSettingsScreen(
navController: NavController, navController: NavController,
viewModel: ProfileSettingsScreenViewModel = hiltViewModel() viewModel: ProfileSettingsScreenViewModel = hiltViewModel()
) { ) {
Column( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings_profile),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
val scrollState = rememberScrollState() Scaffold(
Column( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
modifier = Modifier topBar = {
.fillMaxSize() LargeTopAppBar(
.then( scrollBehavior = scrollBehavior,
if (viewModel.isLoading) { title = {
Modifier
} else {
Modifier.verticalScroll(scrollState)
}
),
verticalArrangement = if (viewModel.isLoading) {
Arrangement.Center
} else {
Arrangement.Top
},
horizontalAlignment = if (viewModel.isLoading) {
Alignment.CenterHorizontally
} else {
Alignment.Start
}
) {
if (viewModel.isLoading) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
)
} else {
RevoltAPI.userCache[RevoltAPI.selfId]?.let {
RawUserOverview(
it,
viewModel.pendingProfile,
viewModel.pfpModel?.toString(),
viewModel.backgroundModel?.toString()
)
}
AnimatedVisibility(visible = viewModel.uploadProgress > 0f) {
LinearProgressIndicator(
progress = viewModel.uploadProgress,
modifier = Modifier
.fillMaxSize()
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
)
}
AnimatedVisibility(visible = viewModel.uploadError != null) {
Text( Text(
text = viewModel.uploadError ?: "", text = stringResource(R.string.settings_profile),
style = MaterialTheme.typography.labelLarge.copy( maxLines = 1,
color = MaterialTheme.colorScheme.error overflow = TextOverflow.Ellipsis,
),
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
) )
} },
navigationIcon = {
FlowRow( IconButton(onClick = {
horizontalArrangement = Arrangement.spacedBy(10.dp), navController.popBackStack()
) { }) {
Column( Icon(
modifier = Modifier imageVector = Icons.Default.ArrowBack,
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp) contentDescription = stringResource(id = R.string.back)
) {
Text(
text = stringResource(id = R.string.settings_profile_profile_picture),
style = MaterialTheme.typography.labelLarge
) )
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Spacer(Modifier.height(10.dp)) val scrollState = rememberScrollState()
Column(
InlineMediaPicker( modifier = Modifier
currentModel = viewModel.pfpModel, .fillMaxSize()
circular = true, .then(
onPick = { if (viewModel.isLoading) {
viewModel.pfpModel = it.toString() Modifier
viewModel.saveNewPfp() } else {
}, Modifier.verticalScroll(scrollState)
canRemove = true, }
onRemove = { ),
viewModel.removePfp() verticalArrangement = if (viewModel.isLoading) {
} Arrangement.Center
} else {
Arrangement.Top
},
horizontalAlignment = if (viewModel.isLoading) {
Alignment.CenterHorizontally
} else {
Alignment.Start
}
) {
if (viewModel.isLoading) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
)
} else {
RevoltAPI.userCache[RevoltAPI.selfId]?.let {
RawUserOverview(
it,
viewModel.pendingProfile,
viewModel.pfpModel?.toString(),
viewModel.backgroundModel?.toString()
) )
} }
Column( AnimatedVisibility(visible = viewModel.uploadProgress > 0f) {
modifier = Modifier LinearProgressIndicator(
.padding(20.dp) progress = viewModel.uploadProgress,
) { modifier = Modifier
Text( .fillMaxSize()
text = stringResource(id = R.string.settings_profile_custom_background), .padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
style = MaterialTheme.typography.labelLarge,
)
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.backgroundModel,
onPick = {
viewModel.backgroundModel = it.toString()
viewModel.saveNewBackground()
},
canRemove = true,
onRemove = {
viewModel.removeBackground()
}
) )
} }
}
Column(
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 20.dp)
) {
OutlinedTextField(
value = viewModel.pendingProfile?.content ?: "",
onValueChange = { value ->
viewModel.pendingProfile?.let {
viewModel.pendingProfile = it.copy(content = value)
}
},
label = {
Text(
text = stringResource(id = R.string.user_info_sheet_category_bio),
style = MaterialTheme.typography.labelLarge,
)
},
modifier = Modifier.fillMaxWidth(),
)
AnimatedVisibility(visible = viewModel.bioError != null) {
Spacer(Modifier.height(8.dp))
AnimatedVisibility(visible = viewModel.uploadError != null) {
Text( Text(
text = viewModel.bioError ?: "", text = viewModel.uploadError ?: "",
style = MaterialTheme.typography.labelLarge.copy( style = MaterialTheme.typography.labelLarge.copy(
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
), ),
@ -387,26 +325,112 @@ fun ProfileSettingsScreen(
) )
} }
Spacer(Modifier.height(8.dp)) FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
TextButton(
onClick = {
viewModel.saveBio()
},
enabled = viewModel.pendingProfile?.content != viewModel.currentProfile?.content,
modifier = Modifier.fillMaxWidth()
) { ) {
Icon( Column(
imageVector = Icons.Default.Check, modifier = Modifier
contentDescription = null .padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
) {
Text(
text = stringResource(id = R.string.settings_profile_profile_picture),
style = MaterialTheme.typography.labelLarge
)
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.pfpModel,
circular = true,
onPick = {
viewModel.pfpModel = it.toString()
viewModel.saveNewPfp()
},
canRemove = true,
onRemove = {
viewModel.removePfp()
}
)
}
Column(
modifier = Modifier
.padding(20.dp)
) {
Text(
text = stringResource(id = R.string.settings_profile_custom_background),
style = MaterialTheme.typography.labelLarge,
)
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.backgroundModel,
onPick = {
viewModel.backgroundModel = it.toString()
viewModel.saveNewBackground()
},
canRemove = true,
onRemove = {
viewModel.removeBackground()
}
)
}
}
Column(
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 20.dp)
) {
OutlinedTextField(
value = viewModel.pendingProfile?.content ?: "",
onValueChange = { value ->
viewModel.pendingProfile?.let {
viewModel.pendingProfile = it.copy(content = value)
}
},
label = {
Text(
text = stringResource(id = R.string.user_info_sheet_category_bio),
style = MaterialTheme.typography.labelLarge,
)
},
modifier = Modifier.fillMaxWidth(),
) )
Spacer(modifier = Modifier.width(8.dp)) AnimatedVisibility(visible = viewModel.bioError != null) {
Spacer(Modifier.height(8.dp))
Text( Text(
text = stringResource(id = R.string.settings_profile_save), text = viewModel.bioError ?: "",
style = MaterialTheme.typography.bodySmall style = MaterialTheme.typography.labelLarge.copy(
) color = MaterialTheme.colorScheme.error
),
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
)
}
Spacer(Modifier.height(8.dp))
TextButton(
onClick = {
viewModel.saveBio()
},
enabled = viewModel.pendingProfile?.content != viewModel.currentProfile?.content,
modifier = Modifier.fillMaxWidth()
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_profile_save),
style = MaterialTheme.typography.bodySmall
)
}
} }
} }
} }

View File

@ -4,6 +4,7 @@ import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -11,16 +12,23 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -31,8 +39,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -45,7 +55,6 @@ import chat.revolt.api.routes.auth.logoutAllSessions
import chat.revolt.api.routes.auth.logoutSessionById import chat.revolt.api.routes.auth.logoutSessionById
import chat.revolt.api.schemas.Session import chat.revolt.api.schemas.Session
import chat.revolt.components.generic.ListHeader import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.UIMarkdown import chat.revolt.components.generic.UIMarkdown
import chat.revolt.components.settings.sessions.SessionItem import chat.revolt.components.settings.sessions.SessionItem
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -84,7 +93,7 @@ class SessionSettingsScreenViewModel : ViewModel() {
} }
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun SessionSettingsScreen( fun SessionSettingsScreen(
navController: NavController, navController: NavController,
@ -126,125 +135,147 @@ fun SessionSettingsScreen(
) )
} }
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings_sessions),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
if (viewModel.isLoading) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = stringResource(R.string.settings_sessions),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize()
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
CircularProgressIndicator( if (viewModel.isLoading) {
modifier = Modifier Column(
.size(48.dp)
)
}
} else {
LazyColumn {
stickyHeader(key = "thisDevice") {
ListHeader {
Text(stringResource(id = R.string.settings_sessions_this_device))
}
}
viewModel.currentSession?.let {
item(key = it.id) {
Spacer(Modifier.height(8.dp))
SessionItem(
session = it,
currentSession = true,
onLogout = {},
modifier = Modifier.padding(horizontal = 8.dp)
)
Spacer(Modifier.height(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") {
ListHeader {
Text(stringResource(id = R.string.settings_sessions_other_sessions))
}
}
item(key = "logoutOtherSessions") {
Row(
modifier = Modifier modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp) .fillMaxSize(),
.fillMaxWidth() horizontalAlignment = Alignment.CenterHorizontally,
.clip(shape = MaterialTheme.shapes.medium) verticalArrangement = Arrangement.Center
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)
)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Column( CircularProgressIndicator(
modifier = Modifier modifier = Modifier
.weight(1f) .size(48.dp)
.padding(end = 16.dp) )
) { }
Text( } else {
text = stringResource(R.string.settings_sessions_log_out_other), LazyColumn {
style = MaterialTheme.typography.labelLarge stickyHeader(key = "thisDevice") {
) ListHeader {
Text(stringResource(id = R.string.settings_sessions_this_device))
}
}
Text( viewModel.currentSession?.let {
text = stringResource( item(key = it.id) {
R.string.settings_sessions_log_out_other_description Spacer(Modifier.height(8.dp))
), SessionItem(
style = MaterialTheme.typography.bodySmall.copy( session = it,
fontWeight = FontWeight.Normal currentSession = true,
onLogout = {},
modifier = Modifier.padding(horizontal = 8.dp)
) )
Spacer(Modifier.height(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") {
ListHeader {
Text(stringResource(id = R.string.settings_sessions_other_sessions))
}
}
item(key = "logoutOtherSessions") {
Row(
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.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))
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))
} }
} }
} }

View File

@ -1,29 +1,37 @@
package chat.revolt.screens.settings package chat.revolt.screens.settings
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowForward import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -35,7 +43,6 @@ import chat.revolt.api.settings.FeatureFlags
import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.LabsAccessControlVariates import chat.revolt.api.settings.LabsAccessControlVariates
import chat.revolt.components.generic.ListHeader import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.SelfUserOverview import chat.revolt.components.screens.settings.SelfUserOverview
import chat.revolt.persistence.KVStorage import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -55,152 +62,188 @@ class SettingsScreenViewModel @Inject constructor(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
viewModel: SettingsScreenViewModel = hiltViewModel() viewModel: SettingsScreenViewModel = hiltViewModel()
) { ) {
Column( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
SelfUserOverview()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = stringResource(R.string.settings),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(vertical = 10.dp) .verticalScroll(rememberScrollState())
) { ) {
ListHeader { SelfUserOverview()
Text(stringResource(R.string.settings_category_account))
}
ListItem( Column(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_profile)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_card_account_details_24dp),
contentDescription = null,
)
},
modifier = Modifier modifier = Modifier
.testTag("settings_view_profile") .fillMaxSize()
.clickable { .padding(vertical = 10.dp)
navController.navigate("settings/profile") ) {
} ListHeader {
) Text(stringResource(R.string.settings_category_account))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_sessions)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_tablet_cellphone_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_sessions")
.clickable {
navController.navigate("settings/sessions")
}
)
ListHeader {
Text(stringResource(R.string.settings_category_general))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_appearance)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_palette_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_appearance")
.clickable {
navController.navigate("settings/appearance")
}
)
ListHeader {
Text(stringResource(R.string.settings_category_miscellaneous))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.about)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_about")
.clickable {
navController.navigate("about")
}
)
if (BuildConfig.DEBUG) {
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
text = "Debug" text = stringResource(id = R.string.settings_profile)
) )
}, },
leadingContent = { leadingContent = {
Icon( Icon(
imageVector = Icons.Default.Settings, painter = painterResource(R.drawable.ic_card_account_details_24dp),
contentDescription = null, contentDescription = null,
) )
}, },
modifier = Modifier modifier = Modifier
.testTag("settings_view_debug") .testTag("settings_view_profile")
.clickable { .clickable {
navController.navigate("settings/debug") navController.navigate("settings/profile")
} }
) )
}
if (FeatureFlags.labsAccessControl is LabsAccessControlVariates.Restricted &&
(FeatureFlags.labsAccessControl as LabsAccessControlVariates.Restricted).predicate()
) {
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
text = "Labs" text = stringResource(id = R.string.settings_sessions)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_tablet_cellphone_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_sessions")
.clickable {
navController.navigate("settings/sessions")
}
)
ListHeader {
Text(stringResource(R.string.settings_category_general))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_appearance)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_palette_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_appearance")
.clickable {
navController.navigate("settings/appearance")
}
)
ListHeader {
Text(stringResource(R.string.settings_category_miscellaneous))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.about)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_about")
.clickable {
navController.navigate("about")
}
)
if (BuildConfig.DEBUG) {
ListItem(
headlineContent = {
Text(
text = "Debug"
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_debug")
.clickable {
navController.navigate("settings/debug")
}
)
}
if (FeatureFlags.labsAccessControl is LabsAccessControlVariates.Restricted &&
(FeatureFlags.labsAccessControl as LabsAccessControlVariates.Restricted).predicate()
) {
ListItem(
headlineContent = {
Text(
text = "Labs"
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_labs")
.clickable {
navController.navigate("labs")
}
)
}
ListItem(
headlineContent = {
Text(
text = "Closed Beta Updater"
) )
}, },
leadingContent = { leadingContent = {
@ -210,101 +253,87 @@ fun SettingsScreen(
) )
}, },
modifier = Modifier modifier = Modifier
.testTag("settings_view_labs") .testTag("settings_view_updater")
.clickable { .clickable {
navController.navigate("labs") navController.navigate("settings/updater")
}
)
ListHeader {
Text(
stringResource(
R.string.settings_category_last,
BuildConfig.VERSION_NAME
)
)
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_changelogs)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_changelogs")
.clickable {
navController.navigate("settings/changelogs")
}
)
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_feedback)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_feedback")
.clickable {
navController.navigate("settings/feedback")
}
)
ListItem(
headlineContent = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) {
Text(
text = stringResource(id = R.string.logout)
)
}
},
leadingContent = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
)
}
},
modifier = Modifier
.testTag("settings_view_logout")
.clickable {
viewModel.logout()
navController.navigate("login/greeting") {
popUpTo("chat") {
inclusive = true
}
}
} }
) )
} }
ListItem(
headlineContent = {
Text(
text = "Closed Beta Updater"
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_updater")
.clickable {
navController.navigate("settings/updater")
}
)
ListHeader {
Text(stringResource(R.string.settings_category_last, BuildConfig.VERSION_NAME))
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_changelogs)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_changelogs")
.clickable {
navController.navigate("settings/changelogs")
}
)
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_feedback)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_feedback")
.clickable {
navController.navigate("settings/feedback")
}
)
ListItem(
headlineContent = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) {
Text(
text = stringResource(id = R.string.logout)
)
}
},
leadingContent = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
)
}
},
modifier = Modifier
.testTag("settings_view_logout")
.clickable {
viewModel.logout()
navController.navigate("login/greeting") {
popUpTo("chat") {
inclusive = true
}
}
}
)
} }
} }
} }

View File

@ -17,6 +17,7 @@ import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -31,7 +32,6 @@ import chat.revolt.R
import chat.revolt.activities.InviteActivity import chat.revolt.activities.InviteActivity
import chat.revolt.api.REVOLT_APP import chat.revolt.api.REVOLT_APP
import chat.revolt.components.generic.FormTextField import chat.revolt.components.generic.FormTextField
import chat.revolt.components.generic.PageHeader
@Composable @Composable
fun AddServerSheet() { fun AddServerSheet() {
@ -53,8 +53,9 @@ fun AddServerSheet() {
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
PageHeader( Text(
text = stringResource(id = R.string.add_server_sheet_title) text = stringResource(id = R.string.add_server_sheet_title),
style = MaterialTheme.typography.headlineMedium
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))

View File

@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -23,7 +25,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import chat.revolt.R import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.WebMarkdown import chat.revolt.components.generic.WebMarkdown
import chat.revolt.internals.Changelog import chat.revolt.internals.Changelog
import chat.revolt.internals.Changelogs import chat.revolt.internals.Changelogs
@ -67,8 +68,8 @@ fun ChangelogSheet(
} }
Column { Column {
PageHeader( Text(
if (new) { text = if (new) {
stringResource(R.string.settings_changelogs_new_header) stringResource(R.string.settings_changelogs_new_header)
} else { } else {
stringResource( stringResource(
@ -76,7 +77,8 @@ fun ChangelogSheet(
viewModel.changelog?.version viewModel.changelog?.version
?: stringResource(R.string.settings_changelogs_historical_version_header_placeholder) ?: stringResource(R.string.settings_changelogs_historical_version_header_placeholder)
) )
} },
style = MaterialTheme.typography.headlineMedium
) )
if (viewModel.changelogContents == null) { if (viewModel.changelogContents == null) {

View File

@ -14,6 +14,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -42,7 +43,6 @@ import chat.revolt.api.schemas.Member
import chat.revolt.api.schemas.User import chat.revolt.api.schemas.User
import chat.revolt.components.chat.MemberListItem import chat.revolt.components.chat.MemberListItem
import chat.revolt.components.generic.CountableListHeader import chat.revolt.components.generic.CountableListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.Presence import chat.revolt.components.generic.Presence
import chat.revolt.components.generic.presenceFromStatus import chat.revolt.components.generic.presenceFromStatus
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -254,7 +254,10 @@ fun MemberListSheet(
} }
Column { Column {
PageHeader(text = stringResource(R.string.channel_info_sheet_options_members)) Text(
text = stringResource(R.string.channel_info_sheet_options_members),
style = MaterialTheme.typography.headlineMedium
)
LazyColumn { LazyColumn {
viewModel.fullItemList.forEachIndexed { index, item -> viewModel.fullItemList.forEachIndexed { index, item ->