diff --git a/app/src/main/java/chat/revolt/activities/InviteActivity.kt b/app/src/main/java/chat/revolt/activities/InviteActivity.kt index 15ef797d..6e3dd9c8 100644 --- a/app/src/main/java/chat/revolt/activities/InviteActivity.kt +++ b/app/src/main/java/chat/revolt/activities/InviteActivity.kt @@ -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 { diff --git a/app/src/main/java/chat/revolt/activities/MainActivity.kt b/app/src/main/java/chat/revolt/activities/MainActivity.kt index 32fbddf9..f6bf6296 100644 --- a/app/src/main/java/chat/revolt/activities/MainActivity.kt +++ b/app/src/main/java/chat/revolt/activities/MainActivity.kt @@ -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( diff --git a/app/src/main/java/chat/revolt/activities/media/ImageViewActivity.kt b/app/src/main/java/chat/revolt/activities/media/ImageViewActivity.kt index 96217ff5..1fe8103b 100644 --- a/app/src/main/java/chat/revolt/activities/media/ImageViewActivity.kt +++ b/app/src/main/java/chat/revolt/activities/media/ImageViewActivity.kt @@ -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() - ) - } + ) } } } diff --git a/app/src/main/java/chat/revolt/activities/media/VideoViewActivity.kt b/app/src/main/java/chat/revolt/activities/media/VideoViewActivity.kt index f165c9fd..9b0c78d2 100644 --- a/app/src/main/java/chat/revolt/activities/media/VideoViewActivity.kt +++ b/app/src/main/java/chat/revolt/activities/media/VideoViewActivity.kt @@ -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) + ) } } } diff --git a/app/src/main/java/chat/revolt/components/chat/DisconnectedNotice.kt b/app/src/main/java/chat/revolt/components/chat/DisconnectedNotice.kt index cab70a1b..5175663b 100644 --- a/app/src/main/java/chat/revolt/components/chat/DisconnectedNotice.kt +++ b/app/src/main/java/chat/revolt/components/chat/DisconnectedNotice.kt @@ -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 + ) + } } } } diff --git a/app/src/main/java/chat/revolt/components/generic/PageHeader.kt b/app/src/main/java/chat/revolt/components/generic/PageHeader.kt deleted file mode 100644 index f8c0eece..00000000 --- a/app/src/main/java/chat/revolt/components/generic/PageHeader.kt +++ /dev/null @@ -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 - ) - } - }) -} diff --git a/app/src/main/java/chat/revolt/internals/extensions/Insets.kt b/app/src/main/java/chat/revolt/internals/extensions/Insets.kt new file mode 100644 index 00000000..023e3f5d --- /dev/null +++ b/app/src/main/java/chat/revolt/internals/extensions/Insets.kt @@ -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) \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/DefaultDestinationScreen.kt b/app/src/main/java/chat/revolt/screens/DefaultDestinationScreen.kt index ba0e2b34..16c2b0d0 100644 --- a/app/src/main/java/chat/revolt/screens/DefaultDestinationScreen.kt +++ b/app/src/main/java/chat/revolt/screens/DefaultDestinationScreen.kt @@ -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) diff --git a/app/src/main/java/chat/revolt/screens/about/AboutScreen.kt b/app/src/main/java/chat/revolt/screens/about/AboutScreen.kt index e8f62e1d..8b78689c 100644 --- a/app/src/main/java/chat/revolt/screens/about/AboutScreen.kt +++ b/app/src/main/java/chat/revolt/screens/about/AboutScreen.kt @@ -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)) - } - } } } diff --git a/app/src/main/java/chat/revolt/screens/about/AttributionScreen.kt b/app/src/main/java/chat/revolt/screens/about/AttributionScreen.kt index 83bf6734..7e063d49 100644 --- a/app/src/main/java/chat/revolt/screens/about/AttributionScreen.kt +++ b/app/src/main/java/chat/revolt/screens/about/AttributionScreen.kt @@ -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 + ) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index 275bef4c..457cef51 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -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 -> diff --git a/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt index 16b90c86..148beaf7 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/FriendsScreen.kt @@ -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)) + } } } - } - ) + ) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/HomeScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/HomeScreen.kt index 46c6a5e6..8b16e4ec 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/HomeScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/HomeScreen.kt @@ -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) - ) } } } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt index 62b7c62d..acb45ba4 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/NoCurrentChannelScreen.kt @@ -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 + ) + } } } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index e4d6e615..89440f03 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -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( diff --git a/app/src/main/java/chat/revolt/screens/services/DiscoverScreen.kt b/app/src/main/java/chat/revolt/screens/services/DiscoverScreen.kt index 65254205..b833e94e 100644 --- a/app/src/main/java/chat/revolt/screens/services/DiscoverScreen.kt +++ b/app/src/main/java/chat/revolt/screens/services/DiscoverScreen.kt @@ -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() + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt index 1df6feea..5d35ee43 100644 --- a/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/AppearanceSettingsScreen.kt @@ -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 + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/ChangelogsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/ChangelogsScreen.kt index 59cc676e..35579911 100644 --- a/app/src/main/java/chat/revolt/screens/settings/ChangelogsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/ChangelogsScreen.kt @@ -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() + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/ClosedBetaUpdaterScreen.kt b/app/src/main/java/chat/revolt/screens/settings/ClosedBetaUpdaterScreen.kt index 0013554c..3790d98e 100644 --- a/app/src/main/java/chat/revolt/screens/settings/ClosedBetaUpdaterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/ClosedBetaUpdaterScreen.kt @@ -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") + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt index e5115603..12713603 100644 --- a/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt @@ -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") + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/ProfileSettngsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/ProfileSettngsScreen.kt index 0bb2733e..9adefc5e 100644 --- a/app/src/main/java/chat/revolt/screens/settings/ProfileSettngsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/ProfileSettngsScreen.kt @@ -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 + ) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/SessionSettngsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/SessionSettngsScreen.kt index 0b6bc664..0c08d8cb 100644 --- a/app/src/main/java/chat/revolt/screens/settings/SessionSettngsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/SessionSettngsScreen.kt @@ -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)) } } } diff --git a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt index 3eb8572d..039fb0d3 100644 --- a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt @@ -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 - } - } - } - ) } } } diff --git a/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt b/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt index df73230d..93642d80 100644 --- a/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/AddServerSheet.kt @@ -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)) diff --git a/app/src/main/java/chat/revolt/sheets/ChangelogSheet.kt b/app/src/main/java/chat/revolt/sheets/ChangelogSheet.kt index 50e7e189..4a74d659 100644 --- a/app/src/main/java/chat/revolt/sheets/ChangelogSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/ChangelogSheet.kt @@ -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) { diff --git a/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt b/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt index 4345ab49..c3d13170 100644 --- a/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt @@ -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 ->