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

View File

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

View File

@ -12,12 +12,13 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
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.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -28,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.core.view.WindowCompat
import chat.revolt.R
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.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.PageHeader
import chat.revolt.provider.getAttachmentContentUri
import chat.revolt.ui.theme.RevoltTheme
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
@ -82,7 +84,7 @@ class ImageViewActivity : ComponentActivity() {
}
}
@OptIn(ExperimentalGlideComposeApi::class)
@OptIn(ExperimentalGlideComposeApi::class, ExperimentalMaterial3Api::class)
@Composable
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}"
@ -187,6 +189,81 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
colourOverrides = SyncedSettings.android.colourOverrides
) {
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) }
) { pv ->
Surface(
@ -195,87 +272,22 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Column {
PageHeader(
text = stringResource(
id = R.string.media_viewer_title_image,
resource.filename ?: resource.id!!
Box(
modifier = Modifier
.clip(RectangleShape)
.fillMaxSize()
) {
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
.clip(RectangleShape)
.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.compose.foundation.background
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.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -28,6 +29,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat
import androidx.media3.common.MediaItem
@ -50,7 +53,6 @@ import chat.revolt.api.RevoltHttp
import chat.revolt.api.schemas.AutumnResource
import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.PageHeader
import chat.revolt.provider.getAttachmentContentUri
import chat.revolt.ui.theme.RevoltTheme
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)
@Composable
fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
@ -199,6 +202,81 @@ fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
colourOverrides = SyncedSettings.android.colourOverrides
) {
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) }
) { pv ->
Surface(
@ -207,89 +285,24 @@ fun VideoViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Column {
PageHeader(
text = stringResource(
id = R.string.media_viewer_title_video,
resource.filename ?: resource.id!!
),
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 = {
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
)
)
}
Box(
modifier = Modifier
.clip(RectangleShape)
.fillMaxSize()
) {
AndroidView(
factory = { context ->
PlayerView(context).apply {
setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
}
}
)
Box(
},
update = {
it.player = player
},
modifier = Modifier
.clip(RectangleShape)
.fillMaxSize()
) {
AndroidView(
factory = { context ->
PlayerView(context).apply {
setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
}
},
update = {
it.player = player
},
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
)
}
.background(MaterialTheme.colorScheme.background)
)
}
}
}

View File

@ -2,9 +2,13 @@ package chat.revolt.components.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
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.filled.Done
import androidx.compose.material.icons.filled.Refresh
@ -44,32 +48,35 @@ private fun DisconnectedNoticeBase(
canTapToRetry: Boolean = false,
onRetry: () -> Unit = {}
) {
Row(
modifier = Modifier
.clickable(enabled = canTapToRetry, onClick = onRetry)
.fillMaxWidth()
.background(background)
.padding(vertical = 8.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.padding(end = 8.dp),
imageVector = icon,
tint = foreground,
contentDescription = null
)
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
Column {
Row(
modifier = Modifier
.clickable(enabled = canTapToRetry, onClick = onRetry)
.fillMaxWidth()
.background(background)
.windowInsetsPadding(WindowInsets.statusBars)
.padding(vertical = 8.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.padding(end = 8.dp),
imageVector = icon,
tint = foreground,
contentDescription = null
)
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.LaunchedEffect
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.navigation.NavController
import chat.revolt.RevoltApplication
@ -35,8 +37,10 @@ fun DefaultDestinationScreen(
nextDestination?.let {
// Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours
// 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)
activity.window.statusBarColor = Color.Transparent.toArgb()
navController.popBackStack(navController.graph.startDestinationRoute!!, true)
navController.navigate(it)

View File

@ -4,6 +4,7 @@ import android.os.Build
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
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.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.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -49,7 +57,6 @@ import chat.revolt.api.REVOLT_BASE
import chat.revolt.api.RevoltJson
import chat.revolt.api.routes.misc.Root
import chat.revolt.api.routes.misc.getRootRoute
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.PrimaryTabs
import chat.revolt.internals.Platform
import kotlinx.coroutines.launch
@ -126,6 +133,7 @@ fun DebugInfo(viewModel: AboutViewModel) {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewModel()) {
val context = LocalContext.current
@ -146,107 +154,126 @@ fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewMo
}
}
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
PageHeader(
text = stringResource(R.string.about),
showBackButton = true,
onBackButtonClicked = { navController.popBackStack() }
)
PrimaryTabs(
tabs = listOf(
stringResource(R.string.about_tab_version),
stringResource(R.string.about_tab_details)
),
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
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.about),
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(
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 -> {
DebugInfo(viewModel)
TextButton(onClick = ::copyDebugInformation) {
Text(text = stringResource(id = R.string.copy))
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
)
}
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.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
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.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
@ -27,13 +34,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.AttributionItem
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -144,66 +152,85 @@ fun AttributionScreen(navController: NavController) {
}
}
Column(
modifier = Modifier
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(R.string.oss_attribution),
showBackButton = true,
onBackButtonClicked = { navController.popBackStack() }
)
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
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") {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = "🐈",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textAlign = TextAlign.Center
text = stringResource(R.string.oss_attribution),
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)) {
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.foundation.layout.Column
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
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.verticalScroll
import androidx.compose.material.icons.Icons
@ -723,7 +727,6 @@ fun ChatRouterScreen(
Column(
modifier = Modifier
.fillMaxWidth()
.safeDrawingPadding()
) {
AnimatedVisibility(
visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected
@ -737,10 +740,17 @@ fun ChatRouterScreen(
)
}
AnimatedVisibility(
visible = RealtimeSocket.disconnectionState == DisconnectionState.Connected
) {
Spacer(Modifier.windowInsetsPadding(WindowInsets.statusBars))
}
if (useTabletAwareUI) {
Row {
DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent
drawerContainerColor = Color.Transparent,
windowInsets = WindowInsets.navigationBars
) {
Sidebar(
viewModel = viewModel,
@ -781,7 +791,8 @@ fun ChatRouterScreen(
drawerState = drawerState,
drawerContent = {
DismissibleDrawerSheet(
drawerContainerColor = Color.Transparent
drawerContainerColor = Color.Transparent,
windowInsets = WindowInsets.navigationBars
) {
Sidebar(
viewModel = viewModel,
@ -1044,7 +1055,6 @@ fun Sidebar(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChannelNavigator(
navController: NavHostController,
@ -1111,7 +1121,7 @@ fun ChannelNavigator(
toggleDrawer()
}
NoCurrentChannelScreen()
NoCurrentChannelScreen(useDrawer = useDrawer, onDrawerClicked = toggleDrawer)
}
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.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
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.IconButton
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -23,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import chat.revolt.R
import chat.revolt.api.internals.FriendRequests
import chat.revolt.api.routes.user.unfriendUser
@ -30,8 +35,8 @@ import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.chat.MemberListItem
import chat.revolt.components.generic.CountableListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.SheetClickable
import chat.revolt.internals.extensions.zero
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -86,154 +91,168 @@ fun FriendsScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) {
}
}
Column(
modifier = Modifier
.fillMaxHeight()
) {
PageHeader(
text = stringResource(R.string.friends),
startButtons = {
if (useDrawer) {
IconButton(onClick = onDrawerClicked) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.friends),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
if (useDrawer) {
IconButton(onClick = {
onDrawerClicked()
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(id = R.string.menu)
)
}
}
},
actions = {
IconButton(onClick = {
optionsSheetShown = true
}) {
Icon(
imageVector = Icons.Default.Menu,
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.menu)
)
}
}
},
additionalButtons = {
IconButton(onClick = {
optionsSheetShown = true
}) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.menu)
},
windowInsets = WindowInsets.zero
)
},
) { pv ->
Column(
modifier = Modifier
.padding(pv)
.fillMaxHeight()
) {
LazyColumn {
stickyHeader(key = "incoming") {
CountableListHeader(
text = stringResource(id = R.string.friends_incoming_requests),
count = FriendRequests.getIncoming().size
)
}
}
)
LazyColumn {
stickyHeader(key = "incoming") {
CountableListHeader(
text = stringResource(id = R.string.friends_incoming_requests),
count = FriendRequests.getIncoming().size
)
}
items(FriendRequests.getIncoming().size) {
val item = FriendRequests.getIncoming()[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
items(FriendRequests.getIncoming().size) {
val item = FriendRequests.getIncoming()[it]
MemberListItem(
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") {
CountableListHeader(
text = stringResource(id = R.string.friends_outgoing_requests),
count = FriendRequests.getOutgoing().size
)
}
stickyHeader(key = "outgoing") {
CountableListHeader(
text = stringResource(id = R.string.friends_outgoing_requests),
count = FriendRequests.getOutgoing().size
)
}
items(FriendRequests.getOutgoing().size) {
val item = FriendRequests.getOutgoing()[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
items(FriendRequests.getOutgoing().size) {
val item = FriendRequests.getOutgoing()[it]
MemberListItem(
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 = "online") {
CountableListHeader(
text = stringResource(id = R.string.status_online),
count = FriendRequests.getOnlineFriends().size
)
}
stickyHeader(key = "online") {
CountableListHeader(
text = stringResource(id = R.string.status_online),
count = FriendRequests.getOnlineFriends().size
)
}
items(FriendRequests.getOnlineFriends().size) {
val item = FriendRequests.getOnlineFriends()[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
items(FriendRequests.getOnlineFriends().size) {
val item = FriendRequests.getOnlineFriends()[it]
MemberListItem(
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 = "not_online") {
CountableListHeader(
text = stringResource(id = R.string.friends_all),
count = FriendRequests.getFriends(true).size
)
}
stickyHeader(key = "not_online") {
CountableListHeader(
text = stringResource(id = R.string.friends_all),
count = FriendRequests.getFriends(true).size
)
}
items(FriendRequests.getFriends(true).size) {
val item = FriendRequests.getFriends(true)[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
items(FriendRequests.getFriends(true).size) {
val item = FriendRequests.getFriends(true)[it]
MemberListItem(
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 = "blocked") {
CountableListHeader(
text = stringResource(id = R.string.friends_blocked),
count = FriendRequests.getBlocked().size
)
}
stickyHeader(key = "blocked") {
CountableListHeader(
text = stringResource(id = R.string.friends_blocked),
count = FriendRequests.getBlocked().size
)
}
items(FriendRequests.getBlocked().size) {
val item = FriendRequests.getBlocked()[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
ActionChannel.send(Action.OpenUserSheet(userId, null))
items(FriendRequests.getBlocked().size) {
val item = FriendRequests.getBlocked()[it]
MemberListItem(
member = null,
user = item,
serverId = null,
userId = item.id ?: "",
modifier = Modifier.clickable {
scope.launch {
item.id?.let { userId ->
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.foundation.layout.Box
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.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Star
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.getValue
import androidx.compose.ui.Alignment
@ -26,14 +30,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.activities.InviteActivity
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.home.LinkOnHome
import chat.revolt.internals.extensions.zero
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) {
val context = LocalContext.current
@ -49,59 +55,75 @@ fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked
label = "catRotation"
)
Column(
modifier = Modifier.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.home),
startButtons = {
if (useDrawer) {
IconButton(onClick = onDrawerClicked) {
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
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.home),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
heading = { Text(text = stringResource(id = R.string.home_join_jenvolt)) },
description = {
Text(
text = stringResource(id = R.string.home_join_jenvolt_description)
navigationIcon = {
if (useDrawer) {
IconButton(onClick = {
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.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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.sp
import chat.revolt.R
import chat.revolt.internals.extensions.zero
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoCurrentChannelScreen() {
Column(
modifier = Modifier
.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
)
fun NoCurrentChannelScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = {},
navigationIcon = {
if (useDrawer) {
IconButton(onClick = {
onDrawerClicked()
}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = stringResource(id = R.string.menu)
)
}
}
},
windowInsets = WindowInsets.zero
)
},
) { 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.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
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.items
import androidx.compose.foundation.lazy.rememberLazyListState
@ -258,7 +260,7 @@ fun ChannelScreen(
Column(
modifier = Modifier
.imePadding()
.safeDrawingPadding()
.windowInsetsPadding(WindowInsets.navigationBars)
) {
ChannelHeader(
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.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.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.services.DiscoverView
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DiscoverScreen(navController: NavController) {
Column(
Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(R.string.discover),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
DiscoverView()
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.discover),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
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.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.Close
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.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.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@ -51,11 +56,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
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.SyncedSettings
import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.appearance.ColourChip
import chat.revolt.ui.theme.ClearRippleTheme
import chat.revolt.ui.theme.OverridableColourScheme
@ -263,218 +269,234 @@ fun AppearanceSettingsScreen(
}
}
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings_appearance),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
ListHeader {
Text(stringResource(R.string.settings_appearance_theme))
}
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = stringResource(R.string.settings_appearance),
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(
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
ColourChip(
color = Color(0xff191919),
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)
ListHeader {
Text(stringResource(R.string.settings_appearance_theme))
}
ColourChip(
color = Color(0xfff7f7f7),
text = stringResource(id = R.string.settings_appearance_theme_light),
selected = GlobalState.theme == Theme.Light,
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.weight(1f)
.testTag("set_theme_light")
.fillMaxWidth()
.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(
color = dynamicDarkColorScheme(LocalContext.current).primary,
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
selected = GlobalState.theme == Theme.M3Dynamic,
color = Color(0xff191919),
text = stringResource(id = R.string.settings_appearance_theme_revolt),
selected = GlobalState.theme == Theme.Revolt,
modifier = Modifier
.weight(1f)
.testTag("set_theme_m3dynamic")
.testTag("set_theme_revolt")
) {
viewModel.saveNewTheme(Theme.M3Dynamic)
viewModel.saveNewTheme(Theme.Revolt)
}
} else {
ColourChip(
color = Color(0xffa0a0a0),
text = stringResource(
id = R.string.settings_appearance_theme_m3dynamic_unsupported
),
selected = false,
color = Color(0xfff7f7f7),
text = stringResource(id = R.string.settings_appearance_theme_light),
selected = GlobalState.theme == Theme.Light,
modifier = Modifier
.weight(1f)
.testTag("set_theme_m3dynamic_unsupported")
.testTag("set_theme_light")
) {
Toast.makeText(
context,
context.getString(
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
),
Toast.LENGTH_SHORT
).show()
viewModel.saveNewTheme(Theme.Light)
}
}
}
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,
ColourChip(
color = Color(0xff000000),
text = stringResource(id = R.string.settings_appearance_theme_amoled),
selected = GlobalState.theme == Theme.Amoled,
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
.fillMaxWidth()
.padding(horizontal = 20.dp)
.weight(1f)
.testTag("set_theme_amoled")
) {
TextButton(
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)
)
}
viewModel.saveNewTheme(Theme.Amoled)
}
Spacer(modifier = Modifier.height(10.dp))
OverridableColourScheme.fieldNames.forEach { fieldName ->
val value =
SyncedSettings.android.colourOverrides?.getFieldByName(fieldName)
?: MaterialTheme.colorScheme.getFieldByName(fieldName)
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(
color = Color(value ?: 0),
text = OverridableColourScheme.fieldNameToResource[fieldName]
?.let { context.getString(it) }
?: fieldName,
color = dynamicDarkColorScheme(LocalContext.current).primary,
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
selected = GlobalState.theme == Theme.M3Dynamic,
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
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp)
.testTag("set_colour_override_$fieldName")
.padding(horizontal = 20.dp)
) {
viewModel.selectedOverrideName = fieldName
viewModel.selectedOverrideInitialValue = value
viewModel.overridePickerSheetVisible = true
TextButton(
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))
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.fillMaxSize
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.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Divider
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.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -20,12 +27,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.internals.Changelogs
import chat.revolt.persistence.KVStorage
import chat.revolt.sheets.ChangelogSheet
@ -64,50 +72,70 @@ fun ChangelogsSettingsScreen(
ChangelogSheet(version = currentChangelog)
}
}
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(R.string.settings_changelogs),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
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,
)
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = stringResource(R.string.settings_changelogs),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
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.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
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.Close
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.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@ -32,21 +38,24 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
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.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.components.generic.PageHeader
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
@ -135,6 +144,7 @@ class ClosedBetaUpdaterScreenViewModel : ViewModel() {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ClosedBetaUpdaterScreen(
navController: NavController,
@ -142,165 +152,181 @@ fun ClosedBetaUpdaterScreen(
) {
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = "Closed Beta Updater",
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
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)
)
when (viewModel.updateState) {
UpdateState.NotChecked -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = "Closed Beta Updater",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
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)
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { 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(
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
when (viewModel.updateState) {
UpdateState.NotChecked -> Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
ElevatedButton(onClick = {
viewUrlInBrowser(
ctx = context,
url = "${BuildConfig.ANALYSIS_BASEURL}/api/distribution/android/download?build=${viewModel.newestBuild}&token=${viewModel.newestDownloadToken}"
)
}) {
Text(text = "Download")
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
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)
)
}
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(
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))
}
Spacer(modifier = Modifier.height(10.dp))
}
Button(
onClick = {
viewModel.checkForUpdates()
},
modifier = Modifier
.padding(bottom = 10.dp)
) {
Text(text = "Check for updates")
Button(
onClick = {
viewModel.checkForUpdates()
},
modifier = Modifier
.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.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState
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.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.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import chat.revolt.components.generic.PageHeader
import chat.revolt.R
import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class DebugSettingsScreenViewModel @Inject constructor(
@ -46,56 +56,77 @@ class DebugSettingsScreenViewModel @Inject constructor(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DebugSettingsScreen(
navController: NavController,
viewModel: DebugSettingsScreenViewModel = hiltViewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = "Debug",
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
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(
modifier = Modifier
.padding(pv)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(20.dp)
) {
Text(
text = "Sparks",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp)
)
Row(
modifier = Modifier.horizontalScroll(rememberScrollState())
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(20.dp)
) {
TextButton(onClick = { viewModel.forgetSidebarSparkShown() }) {
Text("Forget sidebar spark")
Text(
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 = "Changelogs",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp)
)
Row(
modifier = Modifier.horizontalScroll(rememberScrollState())
) {
ElevatedButton(onClick = { viewModel.forgetLatestChangelog() }) {
Text("Mark latest changelog as unread")
Text(
text = "Changelogs",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 10.dp)
)
Row(
modifier = Modifier.horizontalScroll(rememberScrollState())
) {
ElevatedButton(onClick = { viewModel.forgetLatestChangelog() }) {
Text("Mark latest changelog as unread")
}
}
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
@ -33,7 +39,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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.hilt.navigation.compose.hiltViewModel
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.schemas.Profile
import chat.revolt.components.generic.InlineMediaPicker
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.RawUserOverview
import dagger.hilt.android.lifecycle.HiltViewModel
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
fun ProfileSettingsScreen(
navController: NavController,
viewModel: ProfileSettingsScreenViewModel = hiltViewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings_profile),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.then(
if (viewModel.isLoading) {
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) {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
title = {
Text(
text = viewModel.uploadError ?: "",
style = MaterialTheme.typography.labelLarge.copy(
color = MaterialTheme.colorScheme.error
),
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
text = stringResource(R.string.settings_profile),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Column(
modifier = Modifier
.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
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { pv ->
Box(Modifier.padding(pv)) {
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.pfpModel,
circular = true,
onPick = {
viewModel.pfpModel = it.toString()
viewModel.saveNewPfp()
},
canRemove = true,
onRemove = {
viewModel.removePfp()
}
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.then(
if (viewModel.isLoading) {
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()
)
}
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()
}
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)
)
}
}
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 = viewModel.bioError ?: "",
text = viewModel.uploadError ?: "",
style = MaterialTheme.typography.labelLarge.copy(
color = MaterialTheme.colorScheme.error
),
@ -387,26 +325,112 @@ fun ProfileSettingsScreen(
)
}
Spacer(Modifier.height(8.dp))
TextButton(
onClick = {
viewModel.saveBio()
},
enabled = viewModel.pendingProfile?.content != viewModel.currentProfile?.content,
modifier = Modifier.fillMaxWidth()
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
Column(
modifier = Modifier
.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 = stringResource(id = R.string.settings_profile_save),
style = MaterialTheme.typography.bodySmall
)
Text(
text = viewModel.bioError ?: "",
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.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
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.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
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.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -31,8 +39,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
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.schemas.Session
import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.UIMarkdown
import chat.revolt.components.settings.sessions.SessionItem
import kotlinx.coroutines.launch
@ -84,7 +93,7 @@ class SessionSettingsScreenViewModel : ViewModel() {
}
}
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun SessionSettingsScreen(
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(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier
.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(
if (viewModel.isLoading) {
Column(
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
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
CircularProgressIndicator(
modifier = Modifier
.weight(1f)
.padding(end = 16.dp)
) {
Text(
text = stringResource(R.string.settings_sessions_log_out_other),
style = MaterialTheme.typography.labelLarge
)
.size(48.dp)
)
}
} else {
LazyColumn {
stickyHeader(key = "thisDevice") {
ListHeader {
Text(stringResource(id = R.string.settings_sessions_this_device))
}
}
Text(
text = stringResource(
R.string.settings_sessions_log_out_other_description
),
style = MaterialTheme.typography.bodySmall.copy(
fontWeight = FontWeight.Normal
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
.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)
)
}
FilledTonalButton(onClick = {
viewModel.showLogoutOtherConfirmation = true
}) {
Text(stringResource(R.string.logout))
Spacer(Modifier.height(16.dp))
}
}
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
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.Close
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings
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.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
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.LabsAccessControlVariates
import chat.revolt.components.generic.ListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.SelfUserOverview
import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
@ -55,152 +62,188 @@ class SettingsScreenViewModel @Inject constructor(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
navController: NavController,
viewModel: SettingsScreenViewModel = hiltViewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
PageHeader(
text = stringResource(id = R.string.settings),
showBackButton = true,
onBackButtonClicked = {
navController.popBackStack()
}
)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
SelfUserOverview()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
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(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 10.dp)
.verticalScroll(rememberScrollState())
) {
ListHeader {
Text(stringResource(R.string.settings_category_account))
}
SelfUserOverview()
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.settings_profile)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.ic_card_account_details_24dp),
contentDescription = null,
)
},
Column(
modifier = Modifier
.testTag("settings_view_profile")
.clickable {
navController.navigate("settings/profile")
}
)
.fillMaxSize()
.padding(vertical = 10.dp)
) {
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(
headlineContent = {
Text(
text = "Debug"
text = stringResource(id = R.string.settings_profile)
)
},
leadingContent = {
Icon(
imageVector = Icons.Default.Settings,
painter = painterResource(R.drawable.ic_card_account_details_24dp),
contentDescription = null,
)
},
modifier = Modifier
.testTag("settings_view_debug")
.testTag("settings_view_profile")
.clickable {
navController.navigate("settings/debug")
navController.navigate("settings/profile")
}
)
}
if (FeatureFlags.labsAccessControl is LabsAccessControlVariates.Restricted &&
(FeatureFlags.labsAccessControl as LabsAccessControlVariates.Restricted).predicate()
) {
ListItem(
headlineContent = {
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 = {
@ -210,101 +253,87 @@ fun SettingsScreen(
)
},
modifier = Modifier
.testTag("settings_view_labs")
.testTag("settings_view_updater")
.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.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -31,7 +32,6 @@ import chat.revolt.R
import chat.revolt.activities.InviteActivity
import chat.revolt.api.REVOLT_APP
import chat.revolt.components.generic.FormTextField
import chat.revolt.components.generic.PageHeader
@Composable
fun AddServerSheet() {
@ -53,8 +53,9 @@ fun AddServerSheet() {
Spacer(modifier = Modifier.height(4.dp))
PageHeader(
text = stringResource(id = R.string.add_server_sheet_title)
Text(
text = stringResource(id = R.string.add_server_sheet_title),
style = MaterialTheme.typography.headlineMedium
)
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.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -23,7 +25,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.WebMarkdown
import chat.revolt.internals.Changelog
import chat.revolt.internals.Changelogs
@ -67,8 +68,8 @@ fun ChangelogSheet(
}
Column {
PageHeader(
if (new) {
Text(
text = if (new) {
stringResource(R.string.settings_changelogs_new_header)
} else {
stringResource(
@ -76,7 +77,8 @@ fun ChangelogSheet(
viewModel.changelog?.version
?: stringResource(R.string.settings_changelogs_historical_version_header_placeholder)
)
}
},
style = MaterialTheme.typography.headlineMedium
)
if (viewModel.changelogContents == null) {

View File

@ -14,6 +14,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
@ -42,7 +43,6 @@ import chat.revolt.api.schemas.Member
import chat.revolt.api.schemas.User
import chat.revolt.components.chat.MemberListItem
import chat.revolt.components.generic.CountableListHeader
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.Presence
import chat.revolt.components.generic.presenceFromStatus
import dagger.hilt.android.lifecycle.HiltViewModel
@ -254,7 +254,10 @@ fun MemberListSheet(
}
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 {
viewModel.fullItemList.forEachIndexed { index, item ->