feat: muted channels and servers
this also adds a handler for malformed settings because this is basically required for parsing notifications Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
38b171bd97
commit
0660771dd3
|
|
@ -14,21 +14,27 @@ object DirectMessages {
|
||||||
) && it.active == true && it.lastMessageID != null
|
) && it.active == true && it.lastMessageID != null
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
it.id?.let { id -> RevoltAPI.unreads.hasUnread(id, it.lastMessageID!!) } ?: false
|
it.id?.let { id ->
|
||||||
|
RevoltAPI.unreads.hasUnread(
|
||||||
|
id,
|
||||||
|
it.lastMessageID!!,
|
||||||
|
serverId = null
|
||||||
|
)
|
||||||
|
} ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasPlatformModerationDM(): Boolean {
|
fun hasPlatformModerationDM(): Boolean {
|
||||||
return unreadDMs().any {
|
return unreadDMs().any {
|
||||||
it.channelType == ChannelType.DirectMessage &&
|
it.channelType == ChannelType.DirectMessage &&
|
||||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlatformModerationDM(): Channel? {
|
fun getPlatformModerationDM(): Channel? {
|
||||||
return unreadDMs().firstOrNull {
|
return unreadDMs().firstOrNull {
|
||||||
it.channelType == ChannelType.DirectMessage &&
|
it.channelType == ChannelType.DirectMessage &&
|
||||||
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
it.recipients?.contains(PLATFORM_MODERATION_USER) ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,26 @@ package chat.revolt.api.schemas
|
||||||
|
|
||||||
import chat.revolt.ui.theme.OverridableColourScheme
|
import chat.revolt.ui.theme.OverridableColourScheme
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OrderingSettings(
|
data class OrderingSettings(
|
||||||
val servers: List<String> = emptyList()
|
val servers: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NotificationSettings(
|
||||||
|
val channel: Map<String, String> = emptyMap(),
|
||||||
|
val server: Map<String, String> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class _NotificationSettingsToParse( // quirk
|
||||||
|
val channel: Map<String, JsonElement?> = emptyMap(),
|
||||||
|
val server: Map<String, JsonElement?> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AndroidSpecificSettingsSpecialEmbedSettings(
|
data class AndroidSpecificSettingsSpecialEmbedSettings(
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ object LoadedSettings {
|
||||||
var avatarRadius by mutableIntStateOf(50)
|
var avatarRadius by mutableIntStateOf(50)
|
||||||
var experimentsEnabled by mutableStateOf(false)
|
var experimentsEnabled by mutableStateOf(false)
|
||||||
var specialEmbedSettings by mutableStateOf(SpecialEmbedSettings())
|
var specialEmbedSettings by mutableStateOf(SpecialEmbedSettings())
|
||||||
|
var poorlyFormedSettingsKeys by mutableStateOf(emptySet<String>())
|
||||||
|
|
||||||
fun hydrateWithSettings(settings: SyncedSettings) {
|
fun hydrateWithSettings(settings: SyncedSettings) {
|
||||||
this.theme = settings.android.theme?.let { Theme.valueOf(it) } ?: getDefaultTheme()
|
this.theme = settings.android.theme?.let { Theme.valueOf(it) } ?: getDefaultTheme()
|
||||||
|
|
@ -37,5 +38,6 @@ object LoadedSettings {
|
||||||
messageReplyStyle = MessageReplyStyle.SwipeFromEnd
|
messageReplyStyle = MessageReplyStyle.SwipeFromEnd
|
||||||
avatarRadius = 50
|
avatarRadius = 50
|
||||||
specialEmbedSettings = SpecialEmbedSettings()
|
specialEmbedSettings = SpecialEmbedSettings()
|
||||||
|
poorlyFormedSettingsKeys = emptySet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package chat.revolt.api.settings
|
||||||
|
|
||||||
|
object NotificationSettingsProvider {
|
||||||
|
fun isChannelMuted(channelId: String, serverId: String?): Boolean {
|
||||||
|
if (serverId != null) {
|
||||||
|
// When the server is muted, all channels are muted
|
||||||
|
if (SyncedSettings.notifications.server[serverId] == "muted") return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return SyncedSettings.notifications.channel[channelId] == "muted"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,21 @@ import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.routes.sync.getKeys
|
import chat.revolt.api.routes.sync.getKeys
|
||||||
import chat.revolt.api.routes.sync.setKey
|
import chat.revolt.api.routes.sync.setKey
|
||||||
import chat.revolt.api.schemas.AndroidSpecificSettings
|
import chat.revolt.api.schemas.AndroidSpecificSettings
|
||||||
|
import chat.revolt.api.schemas.NotificationSettings
|
||||||
import chat.revolt.api.schemas.OrderingSettings
|
import chat.revolt.api.schemas.OrderingSettings
|
||||||
|
import chat.revolt.api.schemas._NotificationSettingsToParse
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import logcat.LogPriority
|
||||||
|
import logcat.asLog
|
||||||
|
import logcat.logcat
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Note: When adding a new key -
|
||||||
|
* 1. Add corresponding methods and fields here
|
||||||
|
* 2. Add strings for poorly formed keys hint
|
||||||
|
* 3. Add UI for resetting the key if it's poorly formed
|
||||||
|
*/
|
||||||
|
|
||||||
object SyncedSettings {
|
object SyncedSettings {
|
||||||
private val _ordering = mutableStateOf(OrderingSettings())
|
private val _ordering = mutableStateOf(OrderingSettings())
|
||||||
|
|
@ -17,34 +31,81 @@ object SyncedSettings {
|
||||||
messageReplyStyle = "None"
|
messageReplyStyle = "None"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
private val _notifications = mutableStateOf(NotificationSettings())
|
||||||
|
|
||||||
val ordering: OrderingSettings
|
val ordering: OrderingSettings
|
||||||
get() = _ordering.value
|
get() = _ordering.value
|
||||||
val android: AndroidSpecificSettings
|
val android: AndroidSpecificSettings
|
||||||
get() = _android.value
|
get() = _android.value
|
||||||
|
val notifications: NotificationSettings
|
||||||
|
get() = _notifications.value
|
||||||
|
|
||||||
suspend fun fetch(revoltToken: String = RevoltAPI.sessionToken) {
|
suspend fun fetch(revoltToken: String = RevoltAPI.sessionToken) {
|
||||||
try {
|
try {
|
||||||
val settings = getKeys("ordering", "android", revoltToken = revoltToken)
|
val settings =
|
||||||
|
getKeys("ordering", "android", "notifications", revoltToken = revoltToken)
|
||||||
|
|
||||||
settings["ordering"]?.let {
|
settings["ordering"]?.let {
|
||||||
_ordering.value = RevoltJson.decodeFromString(
|
try {
|
||||||
OrderingSettings.serializer(),
|
_ordering.value = RevoltJson.decodeFromString(
|
||||||
it.value
|
OrderingSettings.serializer(),
|
||||||
)
|
it.value
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadedSettings.poorlyFormedSettingsKeys += "ordering"
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings["android"]?.let {
|
settings["android"]?.let {
|
||||||
_android.value = RevoltJson.decodeFromString(
|
try {
|
||||||
AndroidSpecificSettings.serializer(),
|
_android.value = RevoltJson.decodeFromString(
|
||||||
it.value
|
AndroidSpecificSettings.serializer(),
|
||||||
)
|
it.value
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadedSettings.poorlyFormedSettingsKeys += "android"
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["notifications"]?.let {
|
||||||
|
// This is to fix a quirk where the web client sometimes leaves sub-objects in one of the objects
|
||||||
|
// Because it is written in typescript and does what it wants
|
||||||
|
_notifications.value = parseNotificationSettings(it.value)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseNotificationSettings(value: String): NotificationSettings {
|
||||||
|
return try {
|
||||||
|
var intermediate =
|
||||||
|
RevoltJson.decodeFromString(_NotificationSettingsToParse.serializer(), value)
|
||||||
|
|
||||||
|
// Throw out any value of intermediate.server and .channel that isn't a string
|
||||||
|
intermediate = intermediate.copy(
|
||||||
|
server = intermediate.server.filterValues { it != null }
|
||||||
|
.filterValues { it is JsonPrimitive }
|
||||||
|
.filterValues { it!!.jsonPrimitive.isString },
|
||||||
|
channel = intermediate.channel.filterValues { it != null }
|
||||||
|
.filterValues { it is JsonPrimitive }
|
||||||
|
.filterValues { it!!.jsonPrimitive.isString }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Convert the intermediate to a NotificationSettings
|
||||||
|
NotificationSettings(
|
||||||
|
server = intermediate.server.mapValues { it.value!!.jsonPrimitive.content },
|
||||||
|
channel = intermediate.channel.mapValues { it.value!!.jsonPrimitive.content }
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR) { e.asLog() }
|
||||||
|
LoadedSettings.poorlyFormedSettingsKeys += "notifications"
|
||||||
|
NotificationSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateOrdering(value: OrderingSettings) {
|
suspend fun updateOrdering(value: OrderingSettings) {
|
||||||
_ordering.value = value
|
_ordering.value = value
|
||||||
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), value))
|
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), value))
|
||||||
|
|
@ -54,4 +115,34 @@ object SyncedSettings {
|
||||||
_android.value = value
|
_android.value = value
|
||||||
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value))
|
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateNotifications(value: NotificationSettings) {
|
||||||
|
_notifications.value = value
|
||||||
|
setKey("notifications", RevoltJson.encodeToString(NotificationSettings.serializer(), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resetOrdering() {
|
||||||
|
val default = OrderingSettings()
|
||||||
|
_ordering.value = default
|
||||||
|
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), default))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resetAndroid() {
|
||||||
|
val default = AndroidSpecificSettings(
|
||||||
|
theme = "None",
|
||||||
|
colourOverrides = null,
|
||||||
|
messageReplyStyle = "None"
|
||||||
|
)
|
||||||
|
_android.value = default
|
||||||
|
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), default))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resetNotifications() {
|
||||||
|
val default = NotificationSettings()
|
||||||
|
_notifications.value = default
|
||||||
|
setKey(
|
||||||
|
"notifications",
|
||||||
|
RevoltJson.encodeToString(NotificationSettings.serializer(), default)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import chat.revolt.api.routes.server.ackServer
|
||||||
import chat.revolt.api.routes.sync.syncUnreads
|
import chat.revolt.api.routes.sync.syncUnreads
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.api.schemas.ChannelUnread
|
import chat.revolt.api.schemas.ChannelUnread
|
||||||
|
import chat.revolt.api.settings.NotificationSettingsProvider
|
||||||
|
|
||||||
class Unreads {
|
class Unreads {
|
||||||
private val hasLoaded = mutableStateOf(false)
|
private val hasLoaded = mutableStateOf(false)
|
||||||
|
|
@ -34,13 +35,15 @@ class Unreads {
|
||||||
hasLoaded.value = true
|
hasLoaded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getForChannel(channelId: String): ChannelUnread? {
|
fun getForChannel(channelId: String, serverId: String?): ChannelUnread? {
|
||||||
if (!hasLoaded.value) return null
|
if (!hasLoaded.value) return null
|
||||||
|
if (NotificationSettingsProvider.isChannelMuted(channelId, serverId)) return null
|
||||||
return channels[channelId]
|
return channels[channelId]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasUnread(channelId: String, lastMessageId: String): Boolean {
|
fun hasUnread(channelId: String, lastMessageId: String, serverId: String?): Boolean {
|
||||||
if (!hasLoaded.value) return false
|
if (!hasLoaded.value) return false
|
||||||
|
if (NotificationSettingsProvider.isChannelMuted(channelId, serverId)) return false
|
||||||
return (channels[channelId]?.last_id?.compareTo(lastMessageId) ?: 0) < 0
|
return (channels[channelId]?.last_id?.compareTo(lastMessageId) ?: 0) < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,11 +51,15 @@ class Unreads {
|
||||||
if (!hasLoaded.value) return false
|
if (!hasLoaded.value) return false
|
||||||
|
|
||||||
return RevoltAPI.serverCache[serverId]?.channels?.any {
|
return RevoltAPI.serverCache[serverId]?.channels?.any {
|
||||||
val channel = RevoltAPI.channelCache[it] ?: return@any false
|
val channel = RevoltAPI.channelCache[it] ?: return@any false // Channel not found
|
||||||
if (channel.channelType == ChannelType.VoiceChannel) return@any false // TODO remove this when text in voice channels is implemented
|
if (channel.channelType == ChannelType.VoiceChannel) return@any false // Channel is voice
|
||||||
hasUnread(it, channel.lastMessageID ?: "")
|
if (NotificationSettingsProvider.isChannelMuted(
|
||||||
}
|
it,
|
||||||
?: false
|
serverId
|
||||||
|
)
|
||||||
|
) return@any false // Channel is muted
|
||||||
|
hasUnread(it, channel.lastMessageID ?: "", serverId) // Channel has unread
|
||||||
|
} == true // Null guard
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsRead(channelId: String, messageId: String, sync: Boolean = true) {
|
suspend fun markAsRead(channelId: String, messageId: String, sync: Boolean = true) {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.api.schemas.ServerFlags
|
import chat.revolt.api.schemas.ServerFlags
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.api.schemas.has
|
import chat.revolt.api.schemas.has
|
||||||
|
import chat.revolt.api.settings.NotificationSettingsProvider
|
||||||
import chat.revolt.api.settings.SyncedSettings
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
import chat.revolt.components.generic.GroupIcon
|
import chat.revolt.components.generic.GroupIcon
|
||||||
import chat.revolt.components.generic.IconPlaceholder
|
import chat.revolt.components.generic.IconPlaceholder
|
||||||
|
|
@ -552,7 +553,8 @@ fun ChannelSideDrawer(
|
||||||
onDestinationChanged,
|
onDestinationChanged,
|
||||||
drawerState,
|
drawerState,
|
||||||
channelListState,
|
channelListState,
|
||||||
onOpenChannelContextSheet = { channelContextSheetTarget = it }
|
onOpenChannelContextSheet = { channelContextSheetTarget = it },
|
||||||
|
serverId = currentServer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -687,9 +689,12 @@ fun ColumnScope.DirectMessagesChannelListRenderer(
|
||||||
},
|
},
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
channel.id!!, lastMessageID
|
channel.id!!,
|
||||||
|
lastMessageID,
|
||||||
|
serverId = null
|
||||||
)
|
)
|
||||||
} ?: false,
|
} ?: false,
|
||||||
|
isMuted = NotificationSettingsProvider.isChannelMuted(channel.id!!, null),
|
||||||
onDestinationChanged = { dest ->
|
onDestinationChanged = { dest ->
|
||||||
onDestinationChanged(dest)
|
onDestinationChanged(dest)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -719,6 +724,7 @@ fun ColumnScope.ServerChannelListRenderer(
|
||||||
drawerState: DrawerState?,
|
drawerState: DrawerState?,
|
||||||
channelListState: LazyListState,
|
channelListState: LazyListState,
|
||||||
onOpenChannelContextSheet: (String) -> Unit,
|
onOpenChannelContextSheet: (String) -> Unit,
|
||||||
|
serverId: String
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
@ -772,9 +778,15 @@ fun ColumnScope.ServerChannelListRenderer(
|
||||||
},
|
},
|
||||||
hasUnread = channelOrCat.channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channelOrCat.channel.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
channelOrCat.channel.id!!, lastMessageID
|
channelOrCat.channel.id!!,
|
||||||
|
lastMessageID,
|
||||||
|
serverId
|
||||||
)
|
)
|
||||||
} ?: false,
|
} ?: false,
|
||||||
|
isMuted = NotificationSettingsProvider.isChannelMuted(
|
||||||
|
channelOrCat.channel.id!!,
|
||||||
|
serverId
|
||||||
|
),
|
||||||
onOpenChannelContextSheet = onOpenChannelContextSheet
|
onOpenChannelContextSheet = onOpenChannelContextSheet
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -812,6 +824,7 @@ fun ChannelItem(
|
||||||
channel.channelType ?: ChannelType.TextChannel
|
channel.channelType ?: ChannelType.TextChannel
|
||||||
),
|
),
|
||||||
hasUnread: Boolean = false,
|
hasUnread: Boolean = false,
|
||||||
|
isMuted: Boolean = false,
|
||||||
appendServerName: Boolean = false,
|
appendServerName: Boolean = false,
|
||||||
onDestinationChanged: (ChatRouterDestination) -> Unit,
|
onDestinationChanged: (ChatRouterDestination) -> Unit,
|
||||||
onOpenChannelContextSheet: (String) -> Unit
|
onOpenChannelContextSheet: (String) -> Unit
|
||||||
|
|
@ -854,6 +867,13 @@ fun ChannelItem(
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.then(
|
||||||
|
if (isMuted) {
|
||||||
|
Modifier.alpha(0.5f)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.fillMaxWidth()) {
|
.fillMaxWidth()) {
|
||||||
when (iconType) {
|
when (iconType) {
|
||||||
|
|
@ -910,6 +930,7 @@ fun DMOrGroupItem(
|
||||||
partner: User?,
|
partner: User?,
|
||||||
isCurrent: Boolean,
|
isCurrent: Boolean,
|
||||||
hasUnread: Boolean,
|
hasUnread: Boolean,
|
||||||
|
isMuted: Boolean = false,
|
||||||
onDestinationChanged: (ChatRouterDestination) -> Unit,
|
onDestinationChanged: (ChatRouterDestination) -> Unit,
|
||||||
onOpenChannelContextSheet: (String) -> Unit
|
onOpenChannelContextSheet: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -941,6 +962,13 @@ fun DMOrGroupItem(
|
||||||
)
|
)
|
||||||
.padding(vertical = 16.dp)
|
.padding(vertical = 16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.then(
|
||||||
|
if (isMuted) {
|
||||||
|
Modifier.alpha(0.5f)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package chat.revolt.screens.settings
|
package chat.revolt.screens.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -10,16 +13,20 @@ import androidx.compose.foundation.selection.selectableGroup
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -54,13 +61,14 @@ class ChatSettingsScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatSettingsScreen(
|
fun ChatSettingsScreen(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: ChatSettingsScreenViewModel = viewModel()
|
viewModel: ChatSettingsScreenViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
|
@ -95,6 +103,73 @@ fun ChatSettingsScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
) {
|
) {
|
||||||
|
if (LoadedSettings.poorlyFormedSettingsKeys.isNotEmpty()) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_description),
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
for (key in LoadedSettings.poorlyFormedSettingsKeys) {
|
||||||
|
Text(
|
||||||
|
text = " • " + when (key) {
|
||||||
|
"ordering" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_ordering)
|
||||||
|
"android" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_android)
|
||||||
|
"notifications" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_notifications)
|
||||||
|
else -> stringResource(
|
||||||
|
R.string.settings_chat_hint_poorly_formed_settings_keys_key_unknown,
|
||||||
|
key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.padding(top = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
for (key in LoadedSettings.poorlyFormedSettingsKeys.filter {
|
||||||
|
it in setOf("ordering", "android", "notifications")
|
||||||
|
}) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
when (key) {
|
||||||
|
"ordering" -> SyncedSettings.resetOrdering()
|
||||||
|
"android" -> SyncedSettings.resetAndroid()
|
||||||
|
"notifications" -> SyncedSettings.resetNotifications()
|
||||||
|
}
|
||||||
|
LoadedSettings.poorlyFormedSettingsKeys -= key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
R.string.settings_chat_hint_poorly_formed_settings_keys_reset,
|
||||||
|
when (key) {
|
||||||
|
"ordering" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_ordering)
|
||||||
|
"android" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_android)
|
||||||
|
"notifications" -> stringResource(R.string.settings_chat_hint_poorly_formed_settings_keys_key_notifications)
|
||||||
|
else -> key
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListHeader {
|
ListHeader {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.settings_chat_quick_reply)
|
text = stringResource(R.string.settings_chat_quick_reply)
|
||||||
|
|
|
||||||
|
|
@ -660,6 +660,13 @@
|
||||||
<string name="settings_chat_interactive_embeds_description">Interactive Embeds allow you to interact with some types of links directly inside chat. For example, you can play YouTube videos or preview albums from Apple Music.</string>
|
<string name="settings_chat_interactive_embeds_description">Interactive Embeds allow you to interact with some types of links directly inside chat. For example, you can play YouTube videos or preview albums from Apple Music.</string>
|
||||||
<string name="settings_chat_interactive_embeds_youtube">YouTube</string>
|
<string name="settings_chat_interactive_embeds_youtube">YouTube</string>
|
||||||
<string name="settings_chat_interactive_embeds_apple_music">Apple Music</string>
|
<string name="settings_chat_interactive_embeds_apple_music">Apple Music</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys">Hint</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_description">Some of your Revolt settings are corrupted. This may be due to the use of third-party clients. The affected settings are:</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_key_notifications">Notifications</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_key_ordering">Server Ordering</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_key_android">Revolt for Android</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_key_unknown">%1$s (unknown)</string>
|
||||||
|
<string name="settings_chat_hint_poorly_formed_settings_keys_reset">Reset %1$s settings</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