feat: add chat settings and quick reply options
(for future use) Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
72e16cf591
commit
5875f3ded7
|
|
@ -63,6 +63,7 @@ import chat.revolt.screens.register.RegisterVerifyScreen
|
||||||
import chat.revolt.screens.services.DiscoverScreen
|
import chat.revolt.screens.services.DiscoverScreen
|
||||||
import chat.revolt.screens.settings.AppearanceSettingsScreen
|
import chat.revolt.screens.settings.AppearanceSettingsScreen
|
||||||
import chat.revolt.screens.settings.ChangelogsSettingsScreen
|
import chat.revolt.screens.settings.ChangelogsSettingsScreen
|
||||||
|
import chat.revolt.screens.settings.ChatSettingsScreen
|
||||||
import chat.revolt.screens.settings.DebugSettingsScreen
|
import chat.revolt.screens.settings.DebugSettingsScreen
|
||||||
import chat.revolt.screens.settings.ProfileSettingsScreen
|
import chat.revolt.screens.settings.ProfileSettingsScreen
|
||||||
import chat.revolt.screens.settings.SessionSettingsScreen
|
import chat.revolt.screens.settings.SessionSettingsScreen
|
||||||
|
|
@ -377,6 +378,7 @@ fun AppEntrypoint(
|
||||||
composable("settings/profile") { ProfileSettingsScreen(navController) }
|
composable("settings/profile") { ProfileSettingsScreen(navController) }
|
||||||
composable("settings/sessions") { SessionSettingsScreen(navController) }
|
composable("settings/sessions") { SessionSettingsScreen(navController) }
|
||||||
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||||
|
composable("settings/chat") { ChatSettingsScreen(navController) }
|
||||||
composable("settings/debug") { DebugSettingsScreen(navController) }
|
composable("settings/debug") { DebugSettingsScreen(navController) }
|
||||||
composable("settings/changelogs") { ChangelogsSettingsScreen(navController) }
|
composable("settings/changelogs") { ChangelogsSettingsScreen(navController) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,9 @@ data class AndroidSpecificSettings(
|
||||||
* Map of `primary, onPrimary, primaryContainer, onPrimaryContainer, inversePrimary, secondary, onSecondary, secondaryContainer, onSecondaryContainer, tertiary, onTertiary, tertiaryContainer, onTertiaryContainer, background, onBackground, surface, onSurface, surfaceVariant, onSurfaceVariant, surfaceTint, inverseSurface, inverseOnSurface, error, onError, errorContainer, onErrorContainer, outline, outlineVariant, scrim` to int colours.
|
* Map of `primary, onPrimary, primaryContainer, onPrimaryContainer, inversePrimary, secondary, onSecondary, secondaryContainer, onSecondaryContainer, tertiary, onTertiary, tertiaryContainer, onTertiaryContainer, background, onBackground, surface, onSurface, surfaceVariant, onSurfaceVariant, surfaceTint, inverseSurface, inverseOnSurface, error, onError, errorContainer, onErrorContainer, outline, outlineVariant, scrim` to int colours.
|
||||||
*/
|
*/
|
||||||
var colourOverrides: OverridableColourScheme? = null,
|
var colourOverrides: OverridableColourScheme? = null,
|
||||||
|
/**
|
||||||
|
* Message reply style.
|
||||||
|
* Can be one of `{ None, SwipeFromEnd, DoubleTap }`
|
||||||
|
*/
|
||||||
|
var messageReplyStyle: String? = null,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,25 @@ import androidx.compose.runtime.setValue
|
||||||
import chat.revolt.ui.theme.Theme
|
import chat.revolt.ui.theme.Theme
|
||||||
import chat.revolt.ui.theme.getDefaultTheme
|
import chat.revolt.ui.theme.getDefaultTheme
|
||||||
|
|
||||||
|
enum class MessageReplyStyle {
|
||||||
|
None,
|
||||||
|
SwipeFromEnd,
|
||||||
|
DoubleTap
|
||||||
|
}
|
||||||
|
|
||||||
object GlobalState {
|
object GlobalState {
|
||||||
var theme by mutableStateOf(getDefaultTheme())
|
var theme by mutableStateOf(getDefaultTheme())
|
||||||
|
var messageReplyStyle by mutableStateOf(MessageReplyStyle.SwipeFromEnd)
|
||||||
|
|
||||||
fun hydrateWithSettings(settings: SyncedSettings) {
|
fun hydrateWithSettings(settings: SyncedSettings) {
|
||||||
settings.android.theme?.let { this.theme = Theme.valueOf(it) }
|
this.theme = settings.android.theme?.let { Theme.valueOf(it) } ?: getDefaultTheme()
|
||||||
|
this.messageReplyStyle =
|
||||||
|
settings.android.messageReplyStyle?.let { MessageReplyStyle.valueOf(it) }
|
||||||
|
?: MessageReplyStyle.SwipeFromEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
theme = getDefaultTheme()
|
theme = getDefaultTheme()
|
||||||
|
messageReplyStyle = MessageReplyStyle.SwipeFromEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,13 @@ import chat.revolt.api.schemas.OrderingSettings
|
||||||
|
|
||||||
object SyncedSettings {
|
object SyncedSettings {
|
||||||
private val _ordering = mutableStateOf(OrderingSettings())
|
private val _ordering = mutableStateOf(OrderingSettings())
|
||||||
private val _android = mutableStateOf(AndroidSpecificSettings("None"))
|
private val _android = mutableStateOf(
|
||||||
|
AndroidSpecificSettings(
|
||||||
|
theme = "None",
|
||||||
|
colourOverrides = null,
|
||||||
|
messageReplyStyle = "None"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val ordering: OrderingSettings
|
val ordering: OrderingSettings
|
||||||
get() = _ordering.value
|
get() = _ordering.value
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package chat.revolt.components.generic
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
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.width
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RadioItem(
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
label: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(56.dp)
|
||||||
|
.selectable(
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick,
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selected,
|
||||||
|
onClick = null
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodyLarge) {
|
||||||
|
label()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package chat.revolt.screens.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
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.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.settings.GlobalState
|
||||||
|
import chat.revolt.api.settings.MessageReplyStyle
|
||||||
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
|
import chat.revolt.components.generic.ListHeader
|
||||||
|
import chat.revolt.components.generic.RadioItem
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ChatSettingsScreenViewModel : ViewModel() {
|
||||||
|
fun updateMessageReplyStyle(next: MessageReplyStyle) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
SyncedSettings.updateAndroid(SyncedSettings.android.copy(messageReplyStyle = next.name))
|
||||||
|
GlobalState.messageReplyStyle = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ChatSettingsScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: ChatSettingsScreenViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
LargeTopAppBar(
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_chat),
|
||||||
|
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)
|
||||||
|
.imePadding()
|
||||||
|
) {
|
||||||
|
ListHeader {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_chat_quick_reply)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.selectableGroup()) {
|
||||||
|
RadioItem(
|
||||||
|
selected = GlobalState.messageReplyStyle == MessageReplyStyle.None,
|
||||||
|
onClick = { viewModel.updateMessageReplyStyle(MessageReplyStyle.None) },
|
||||||
|
label = { Text(text = stringResource(R.string.settings_chat_quick_reply_none)) }
|
||||||
|
)
|
||||||
|
RadioItem(
|
||||||
|
selected = GlobalState.messageReplyStyle == MessageReplyStyle.SwipeFromEnd,
|
||||||
|
onClick = { viewModel.updateMessageReplyStyle(MessageReplyStyle.SwipeFromEnd) },
|
||||||
|
label = { Text(text = stringResource(R.string.settings_chat_quick_reply_swipe_from_end)) }
|
||||||
|
)
|
||||||
|
RadioItem(
|
||||||
|
selected = GlobalState.messageReplyStyle == MessageReplyStyle.DoubleTap,
|
||||||
|
onClick = { viewModel.updateMessageReplyStyle(MessageReplyStyle.DoubleTap) },
|
||||||
|
label = { Text(text = stringResource(R.string.settings_chat_quick_reply_double_tap)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -178,6 +178,25 @@ fun SettingsScreen(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.settings_chat)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_message_text_24dp),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag("settings_view_chat")
|
||||||
|
.clickable {
|
||||||
|
navController.navigate("settings/chat")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ListHeader {
|
ListHeader {
|
||||||
Text(stringResource(R.string.settings_category_miscellaneous))
|
Text(stringResource(R.string.settings_category_miscellaneous))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M6,9H18V11H6M14,14H6V12H14M18,8H6V6H18" />
|
||||||
|
</vector>
|
||||||
|
|
@ -536,6 +536,12 @@
|
||||||
<string name="settings_appearance_colour_overrides_import">Import</string>
|
<string name="settings_appearance_colour_overrides_import">Import</string>
|
||||||
<string name="settings_appearance_colour_overrides_import_error">This file is not a valid colour override file.</string>
|
<string name="settings_appearance_colour_overrides_import_error">This file is not a valid colour override file.</string>
|
||||||
|
|
||||||
|
<string name="settings_chat">Chat</string>
|
||||||
|
<string name="settings_chat_quick_reply">Quick Reply</string>
|
||||||
|
<string name="settings_chat_quick_reply_none">Long press to reply</string>
|
||||||
|
<string name="settings_chat_quick_reply_swipe_from_end">Swipe to reply</string>
|
||||||
|
<string name="settings_chat_quick_reply_double_tap">Double tap to reply</string>
|
||||||
|
|
||||||
<string name="settings_feedback">Feedback</string>
|
<string name="settings_feedback">Feedback</string>
|
||||||
<string name="settings_feedback_description">Join the Revolt server to give feedback or suggestions and report bugs.</string>
|
<string name="settings_feedback_description">Join the Revolt server to give feedback or suggestions and report bugs.</string>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue