feat: theme picker and settings sync 🔥🔥🔥🔥🔥
This commit is contained in:
parent
63227d8d0f
commit
023ac6e5cb
|
|
@ -75,6 +75,7 @@ dependencies {
|
||||||
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
|
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
|
||||||
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
|
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
|
||||||
implementation "com.google.accompanist:accompanist-navigation-animation:$accompanist_version"
|
implementation "com.google.accompanist:accompanist-navigation-animation:$accompanist_version"
|
||||||
|
implementation "com.google.accompanist:accompanist-flowlayout:$accompanist_version"
|
||||||
|
|
||||||
// KTOR - HTTP+WebSocket Library
|
// KTOR - HTTP+WebSocket Library
|
||||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import chat.revolt.api.settings.GlobalState
|
||||||
import chat.revolt.screens.SplashScreen
|
import chat.revolt.screens.SplashScreen
|
||||||
import chat.revolt.screens.about.AboutScreen
|
import chat.revolt.screens.about.AboutScreen
|
||||||
import chat.revolt.screens.about.AttributionScreen
|
import chat.revolt.screens.about.AttributionScreen
|
||||||
|
|
@ -21,6 +22,7 @@ import chat.revolt.screens.chat.ChatRouterScreen
|
||||||
import chat.revolt.screens.login.GreeterScreen
|
import chat.revolt.screens.login.GreeterScreen
|
||||||
import chat.revolt.screens.login.LoginScreen
|
import chat.revolt.screens.login.LoginScreen
|
||||||
import chat.revolt.screens.login.MfaScreen
|
import chat.revolt.screens.login.MfaScreen
|
||||||
|
import chat.revolt.screens.settings.AppearanceSettingsScreen
|
||||||
import chat.revolt.screens.settings.SettingsScreen
|
import chat.revolt.screens.settings.SettingsScreen
|
||||||
import chat.revolt.ui.theme.RevoltTheme
|
import chat.revolt.ui.theme.RevoltTheme
|
||||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||||
|
|
@ -33,14 +35,7 @@ class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
RevoltTheme {
|
AppEntrypoint()
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
AppEntrypoint()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,52 +49,62 @@ val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOut
|
||||||
fun AppEntrypoint() {
|
fun AppEntrypoint() {
|
||||||
val navController = rememberAnimatedNavController()
|
val navController = rememberAnimatedNavController()
|
||||||
|
|
||||||
AnimatedNavHost(
|
RevoltTheme(
|
||||||
navController = navController,
|
requestedTheme = GlobalState.theme,
|
||||||
startDestination = "splash",
|
|
||||||
enterTransition = {
|
|
||||||
slideIntoContainer(
|
|
||||||
AnimatedContentScope.SlideDirection.Left,
|
|
||||||
animationSpec = RevoltTweenInt
|
|
||||||
) + fadeIn(animationSpec = RevoltTweenFloat)
|
|
||||||
},
|
|
||||||
exitTransition = {
|
|
||||||
slideOutOfContainer(
|
|
||||||
AnimatedContentScope.SlideDirection.Left,
|
|
||||||
animationSpec = RevoltTweenInt
|
|
||||||
) + fadeOut(animationSpec = RevoltTweenFloat)
|
|
||||||
},
|
|
||||||
popEnterTransition = {
|
|
||||||
slideIntoContainer(
|
|
||||||
AnimatedContentScope.SlideDirection.Right,
|
|
||||||
animationSpec = RevoltTweenInt
|
|
||||||
) + fadeIn(animationSpec = RevoltTweenFloat)
|
|
||||||
},
|
|
||||||
popExitTransition = {
|
|
||||||
slideOutOfContainer(
|
|
||||||
AnimatedContentScope.SlideDirection.Right,
|
|
||||||
animationSpec = RevoltTweenInt
|
|
||||||
) + fadeOut(animationSpec = RevoltTweenFloat)
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
composable("splash") { SplashScreen(navController) }
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
AnimatedNavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = "splash",
|
||||||
|
enterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
AnimatedContentScope.SlideDirection.Left,
|
||||||
|
animationSpec = RevoltTweenInt
|
||||||
|
) + fadeIn(animationSpec = RevoltTweenFloat)
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
AnimatedContentScope.SlideDirection.Left,
|
||||||
|
animationSpec = RevoltTweenInt
|
||||||
|
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||||
|
},
|
||||||
|
popEnterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
AnimatedContentScope.SlideDirection.Right,
|
||||||
|
animationSpec = RevoltTweenInt
|
||||||
|
) + fadeIn(animationSpec = RevoltTweenFloat)
|
||||||
|
},
|
||||||
|
popExitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
AnimatedContentScope.SlideDirection.Right,
|
||||||
|
animationSpec = RevoltTweenInt
|
||||||
|
) + fadeOut(animationSpec = RevoltTweenFloat)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
composable("splash") { SplashScreen(navController) }
|
||||||
|
|
||||||
composable("login/greeting") { GreeterScreen(navController) }
|
composable("login/greeting") { GreeterScreen(navController) }
|
||||||
composable("login/login") { LoginScreen(navController) }
|
composable("login/login") { LoginScreen(navController) }
|
||||||
composable("login/mfa/{mfaTicket}/{allowedAuthTypes}") { backStackEntry ->
|
composable("login/mfa/{mfaTicket}/{allowedAuthTypes}") { backStackEntry ->
|
||||||
val mfaTicket = backStackEntry.arguments?.getString("mfaTicket") ?: ""
|
val mfaTicket = backStackEntry.arguments?.getString("mfaTicket") ?: ""
|
||||||
val allowedAuthTypes =
|
val allowedAuthTypes =
|
||||||
backStackEntry.arguments?.getString("allowedAuthTypes") ?: ""
|
backStackEntry.arguments?.getString("allowedAuthTypes") ?: ""
|
||||||
|
|
||||||
MfaScreen(navController, allowedAuthTypes, mfaTicket)
|
MfaScreen(navController, allowedAuthTypes, mfaTicket)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable("chat") { ChatRouterScreen(navController) }
|
||||||
|
|
||||||
|
composable("settings") { SettingsScreen(navController) }
|
||||||
|
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
|
||||||
|
|
||||||
|
composable("about") { AboutScreen(navController) }
|
||||||
|
composable("about/oss") { AttributionScreen(navController) }
|
||||||
|
composable("about/placeholder") { PlaceholderScreen(navController) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composable("chat") { ChatRouterScreen(navController) }
|
|
||||||
|
|
||||||
composable("settings") { SettingsScreen(navController) }
|
|
||||||
|
|
||||||
composable("about") { AboutScreen(navController) }
|
|
||||||
composable("about/oss") { AttributionScreen(navController) }
|
|
||||||
composable("about/placeholder") { PlaceholderScreen(navController) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import chat.revolt.api.realtime.frames.sendable.PingFrame
|
||||||
import io.ktor.client.plugins.websocket.*
|
import io.ktor.client.plugins.websocket.*
|
||||||
import io.ktor.websocket.*
|
import io.ktor.websocket.*
|
||||||
import kotlinx.coroutines.channels.consumeEach
|
import kotlinx.coroutines.channels.consumeEach
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
enum class DisconnectionState {
|
enum class DisconnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
|
@ -64,7 +63,7 @@ object RealtimeSocket {
|
||||||
suspend fun sendPing() {
|
suspend fun sendPing() {
|
||||||
if (disconnectionState != DisconnectionState.Connected) return
|
if (disconnectionState != DisconnectionState.Connected) return
|
||||||
|
|
||||||
val pingPacket = PingFrame("Ping", Calendar.getInstance().timeInMillis.toInt())
|
val pingPacket = PingFrame("Ping", System.currentTimeMillis())
|
||||||
socket?.send(RevoltJson.encodeToString(PingFrame.serializer(), pingPacket))
|
socket?.send(RevoltJson.encodeToString(PingFrame.serializer(), pingPacket))
|
||||||
Log.d("RealtimeSocket", "Sent ping frame with ${pingPacket.data}")
|
Log.d("RealtimeSocket", "Sent ping frame with ${pingPacket.data}")
|
||||||
}
|
}
|
||||||
|
|
@ -189,8 +188,8 @@ object RealtimeSocket {
|
||||||
Log.d("RealtimeSocket", "Registered channel callback for $channelId.")
|
Log.d("RealtimeSocket", "Registered channel callback for $channelId.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterChannelCallback(channelId: String, callback: ChannelCallback) {
|
fun unregisterChannelCallback(channelId: String) {
|
||||||
channelCallbacks.remove(channelId, callback)
|
channelCallbacks.remove(channelId)
|
||||||
|
|
||||||
Log.d("RealtimeSocket", "Unregistered channel callback for $channelId")
|
Log.d("RealtimeSocket", "Unregistered channel callback for $channelId")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ data class BulkFrame(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PongFrame(
|
data class PongFrame(
|
||||||
val type: String = "Pong",
|
val type: String = "Pong",
|
||||||
val data: Int
|
val data: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ data class AuthorizationFrame(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PingFrame(
|
data class PingFrame(
|
||||||
val type: String,
|
val type: String,
|
||||||
val data: Int
|
val data: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package chat.revolt.api.routes.sync
|
||||||
|
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.RevoltHttp
|
||||||
|
import chat.revolt.api.RevoltJson
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
import kotlinx.serialization.builtins.MapSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SyncedSetting(val timestamp: Long, val value: String)
|
||||||
|
|
||||||
|
suspend fun getKeys(vararg keys: String): Map<String, SyncedSetting> {
|
||||||
|
val response = RevoltHttp.post("/sync/settings/fetch") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
|
||||||
|
// format: {"keys": ["key1", "key2"]}
|
||||||
|
setBody(
|
||||||
|
RevoltJson.encodeToString(
|
||||||
|
MapSerializer(
|
||||||
|
String.serializer(),
|
||||||
|
ListSerializer(String.serializer())
|
||||||
|
),
|
||||||
|
mapOf("keys" to keys.toList())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.bodyAsText()
|
||||||
|
|
||||||
|
return RevoltJson.decodeFromString(
|
||||||
|
MapSerializer(
|
||||||
|
String.serializer(),
|
||||||
|
JsonArray.serializer()
|
||||||
|
),
|
||||||
|
response
|
||||||
|
).mapValues { (_, value) ->
|
||||||
|
SyncedSetting(
|
||||||
|
timestamp = value[0].toString().toLong(),
|
||||||
|
value = value[1]
|
||||||
|
.toString()
|
||||||
|
.removeSurrounding("\"")
|
||||||
|
.replace("\\\"", "\"")
|
||||||
|
.replace("\\\\", "\\") // the revolt API is so scuffed i can't even make this up
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setKey(key: String, value: String) {
|
||||||
|
RevoltHttp.post("/sync/settings/set") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
|
||||||
|
parameter("timestamp", System.currentTimeMillis())
|
||||||
|
|
||||||
|
// format: {"key": "value"}
|
||||||
|
setBody(
|
||||||
|
RevoltJson.encodeToString(
|
||||||
|
MapSerializer(
|
||||||
|
String.serializer(),
|
||||||
|
String.serializer()
|
||||||
|
),
|
||||||
|
mapOf(key to value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package chat.revolt.api.schemas
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OrderingSettings(
|
||||||
|
val servers: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AndroidSpecificSettings(
|
||||||
|
/**
|
||||||
|
* The theme to use for the app.
|
||||||
|
* Can be one of { None, Revolt, Light, M3Dynamic, Amoled }
|
||||||
|
*/
|
||||||
|
var theme: String? = null,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package chat.revolt.api.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import chat.revolt.ui.theme.Theme
|
||||||
|
|
||||||
|
object GlobalState {
|
||||||
|
private var _theme = mutableStateOf(Theme.Revolt)
|
||||||
|
val theme
|
||||||
|
get() = _theme.value
|
||||||
|
|
||||||
|
fun setTheme(theme: Theme) {
|
||||||
|
_theme.value = theme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package chat.revolt.api.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import chat.revolt.api.RevoltJson
|
||||||
|
import chat.revolt.api.routes.sync.getKeys
|
||||||
|
import chat.revolt.api.routes.sync.setKey
|
||||||
|
import chat.revolt.api.schemas.AndroidSpecificSettings
|
||||||
|
import chat.revolt.api.schemas.OrderingSettings
|
||||||
|
|
||||||
|
object SyncedSettings {
|
||||||
|
private val _ordering = mutableStateOf(OrderingSettings())
|
||||||
|
private val _android = mutableStateOf(AndroidSpecificSettings("None"))
|
||||||
|
|
||||||
|
val ordering: OrderingSettings
|
||||||
|
get() = _ordering.value
|
||||||
|
val android: AndroidSpecificSettings
|
||||||
|
get() = _android.value
|
||||||
|
|
||||||
|
suspend fun fetch() {
|
||||||
|
val settings = getKeys("ordering", "android")
|
||||||
|
|
||||||
|
settings["ordering"]?.let {
|
||||||
|
_ordering.value = RevoltJson.decodeFromString(
|
||||||
|
OrderingSettings.serializer(),
|
||||||
|
it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["android"]?.let {
|
||||||
|
_android.value = RevoltJson.decodeFromString(
|
||||||
|
AndroidSpecificSettings.serializer(),
|
||||||
|
it.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateOrdering(value: OrderingSettings) {
|
||||||
|
_ordering.value = value
|
||||||
|
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateAndroid(value: AndroidSpecificSettings) {
|
||||||
|
_android.value = value
|
||||||
|
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,56 @@
|
||||||
package chat.revolt.components.generic
|
package chat.revolt.components.generic
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.revolt.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PageHeader(
|
fun PageHeader(
|
||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
showBackButton: Boolean = false,
|
||||||
|
onBackButtonClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = text,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
style = MaterialTheme.typography.displaySmall.copy(
|
) {
|
||||||
fontWeight = FontWeight.Bold,
|
if (showBackButton) {
|
||||||
textAlign = TextAlign.Left,
|
IconButton(onClick = onBackButtonClicked) {
|
||||||
fontSize = 24.sp
|
Icon(
|
||||||
),
|
modifier = modifier,
|
||||||
modifier = modifier
|
imageVector = Icons.Default.ArrowBack,
|
||||||
.padding(horizontal = 15.dp, vertical = 15.dp)
|
contentDescription = stringResource(id = R.string.back)
|
||||||
.fillMaxWidth(),
|
)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.displaySmall.copy(
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Left,
|
||||||
|
fontSize = 24.sp
|
||||||
|
),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = 15.dp, vertical = 15.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
|
@ -35,3 +58,9 @@ fun PageHeader(
|
||||||
fun PageHeaderPreview() {
|
fun PageHeaderPreview() {
|
||||||
PageHeader(text = "Page Header")
|
PageHeader(text = "Page Header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PageHeaderPreviewWithBackButton() {
|
||||||
|
PageHeader(text = "Page Header", showBackButton = true)
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -32,7 +31,9 @@ fun Weblink(text: String, url: String, modifier: Modifier = Modifier) {
|
||||||
fun AnyLink(text: String, action: () -> Unit, modifier: Modifier = Modifier) {
|
fun AnyLink(text: String, action: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package chat.revolt.components.screens.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.draw.clip
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsCategory(
|
||||||
|
icon: @Composable (Modifier) -> Unit,
|
||||||
|
label: @Composable (TextStyle) -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.padding(bottom = 8.dp)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
icon(Modifier.padding(end = 16.dp))
|
||||||
|
label(
|
||||||
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SettingsCategoryPreview() {
|
||||||
|
SettingsCategory(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
modifier = modifier,
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
|
contentDescription = "Account"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { textStyle ->
|
||||||
|
Text(text = "Account", style = textStyle)
|
||||||
|
},
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package chat.revolt.components.screens.settings.appearance
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThemeChip(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
color: Color,
|
||||||
|
text: String,
|
||||||
|
selected: Boolean = false,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.then(modifier)
|
||||||
|
.then(
|
||||||
|
if (selected)
|
||||||
|
Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
|
.padding(4.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(color)
|
||||||
|
.height(60.dp)
|
||||||
|
.fillMaxWidth(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -21,8 +22,12 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.settings.GlobalState
|
||||||
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
import chat.revolt.components.screens.splash.DisconnectedScreen
|
import chat.revolt.components.screens.splash.DisconnectedScreen
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
|
import chat.revolt.ui.theme.RevoltColorScheme
|
||||||
|
import chat.revolt.ui.theme.Theme
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -82,18 +87,29 @@ class SplashScreenViewModel @Inject constructor(
|
||||||
setNavigateTo("login")
|
setNavigateTo("login")
|
||||||
} else {
|
} else {
|
||||||
RevoltAPI.loginAs(token)
|
RevoltAPI.loginAs(token)
|
||||||
|
loadSettings()
|
||||||
setNavigateTo("home")
|
setNavigateTo("home")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadSettings() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
SyncedSettings.fetch()
|
||||||
|
SyncedSettings.android.theme?.let { GlobalState.setTheme(Theme.valueOf(it)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkLoggedInState()
|
checkLoggedInState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel = hiltViewModel()) {
|
fun SplashScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: SplashScreenViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
if (!viewModel.isConnected) {
|
if (!viewModel.isConnected) {
|
||||||
DisconnectedScreen(
|
DisconnectedScreen(
|
||||||
onRetry = {
|
onRetry = {
|
||||||
|
|
@ -105,6 +121,7 @@ fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.background(color = RevoltColorScheme.background)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -50,7 +49,9 @@ fun VersionItem(
|
||||||
Row(modifier) {
|
Row(modifier) {
|
||||||
Text(
|
Text(
|
||||||
text = key,
|
text = key,
|
||||||
color = Color(0xccffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
|
|
@ -60,7 +61,9 @@ fun VersionItem(
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = value,
|
text = value,
|
||||||
color = Color(0xccffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal
|
fontWeight = FontWeight.Normal
|
||||||
|
|
@ -117,7 +120,9 @@ fun AboutScreen(
|
||||||
if (viewModel.root.value == null) {
|
if (viewModel.root.value == null) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.loading),
|
text = stringResource(R.string.loading),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal
|
fontWeight = FontWeight.Normal
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.ui.theme.DarkColorScheme
|
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
|
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
|
||||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||||
|
|
||||||
|
|
@ -39,10 +38,10 @@ fun AttributionScreen(navController: NavController) {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
colors = LibraryDefaults.libraryColors(
|
colors = LibraryDefaults.libraryColors(
|
||||||
backgroundColor = DarkColorScheme.background,
|
backgroundColor = MaterialTheme.colorScheme.background,
|
||||||
contentColor = DarkColorScheme.onBackground,
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
badgeBackgroundColor = DarkColorScheme.primary,
|
badgeBackgroundColor = MaterialTheme.colorScheme.primary,
|
||||||
badgeContentColor = DarkColorScheme.onPrimary
|
badgeContentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -49,10 +48,8 @@ fun PlaceholderScreen(
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.comingsoon_body),
|
text = stringResource(R.string.comingsoon_body),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
alpha = 0.5f
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,9 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
val channel: Channel?
|
val channel: Channel?
|
||||||
get() = _channel
|
get() = _channel
|
||||||
|
|
||||||
private var _callbacks = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
|
private var _callback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
|
||||||
val callbacks: RealtimeSocket.ChannelCallback?
|
val callback: RealtimeSocket.ChannelCallback?
|
||||||
get() = _callbacks.value
|
get() = _callback.value
|
||||||
|
|
||||||
private var _renderableMessages = mutableStateListOf<MessageSchema>()
|
private var _renderableMessages = mutableStateListOf<MessageSchema>()
|
||||||
val renderableMessages: List<MessageSchema>
|
val renderableMessages: List<MessageSchema>
|
||||||
|
|
@ -164,8 +164,8 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerCallback() {
|
private fun registerCallback() {
|
||||||
_callbacks.value = ChannelScreenCallback()
|
_callback.value = ChannelScreenCallback()
|
||||||
RealtimeSocket.registerChannelCallback(channel!!.id!!, callbacks!!)
|
RealtimeSocket.registerChannelCallback(channel!!.id!!, callback!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchMessages() {
|
fun fetchMessages() {
|
||||||
|
|
@ -399,9 +399,7 @@ fun ChannelScreen(
|
||||||
|
|
||||||
DisposableEffect(channelId) {
|
DisposableEffect(channelId) {
|
||||||
onDispose {
|
onDispose {
|
||||||
viewModel.callbacks?.let {
|
RealtimeSocket.unregisterChannelCallback(channelId)
|
||||||
RealtimeSocket.unregisterChannelCallback(channelId, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
|
@ -84,7 +83,9 @@ fun GreeterScreen(navController: NavController) {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.login_onboarding_body),
|
text = stringResource(R.string.login_onboarding_body),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
|
@ -169,7 +168,9 @@ fun MfaScreen(
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.mfa_interstitial_lead),
|
text = stringResource(R.string.mfa_interstitial_lead),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
|
|
@ -206,7 +207,9 @@ fun MfaScreen(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.mfa_totp_lead),
|
text = stringResource(R.string.mfa_totp_lead),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
|
|
@ -244,7 +247,9 @@ fun MfaScreen(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.mfa_recovery_lead),
|
text = stringResource(R.string.mfa_recovery_lead),
|
||||||
color = Color(0xaaffffff),
|
color = MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
package chat.revolt.screens.settings
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
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.R
|
||||||
|
import chat.revolt.api.settings.GlobalState
|
||||||
|
import chat.revolt.api.settings.SyncedSettings
|
||||||
|
import chat.revolt.components.generic.PageHeader
|
||||||
|
import chat.revolt.components.screens.settings.appearance.ThemeChip
|
||||||
|
import chat.revolt.ui.theme.Theme
|
||||||
|
import chat.revolt.ui.theme.systemSupportsDynamicColors
|
||||||
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AppearanceSettingsScreenViewModel : ViewModel() {
|
||||||
|
fun saveNewTheme(theme: Theme) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val android = SyncedSettings.android
|
||||||
|
android.theme = theme.toString()
|
||||||
|
SyncedSettings.updateAndroid(android)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppearanceSettingsScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: AppearanceSettingsScreenViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
fun setNewTheme(theme: Theme) {
|
||||||
|
GlobalState.setTheme(theme)
|
||||||
|
viewModel.saveNewTheme(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
PageHeader(
|
||||||
|
text = stringResource(id = R.string.settings_appearance),
|
||||||
|
showBackButton = true,
|
||||||
|
onBackButtonClicked = {
|
||||||
|
navController.popBackStack()
|
||||||
|
})
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(20.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.padding(bottom = 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
mainAxisSpacing = 10.dp,
|
||||||
|
crossAxisSpacing = 10.dp,
|
||||||
|
) {
|
||||||
|
ThemeChip(
|
||||||
|
color = Color(0xff172333),
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_revolt),
|
||||||
|
selected = GlobalState.theme == Theme.Revolt,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
setNewTheme(Theme.Revolt)
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeChip(
|
||||||
|
color = Color(0xfff7f7f7),
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_light),
|
||||||
|
selected = GlobalState.theme == Theme.Light,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
setNewTheme(Theme.Light)
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeChip(
|
||||||
|
color = Color(0xff000000),
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_amoled),
|
||||||
|
selected = GlobalState.theme == Theme.Amoled,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
setNewTheme(Theme.Amoled)
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeChip(
|
||||||
|
color = if (isSystemInDarkTheme()) Color(0xff172333) else Color(0xfff7f7f7),
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_none),
|
||||||
|
selected = GlobalState.theme == Theme.None,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
setNewTheme(Theme.None)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (systemSupportsDynamicColors()) {
|
||||||
|
ThemeChip(
|
||||||
|
color = dynamicDarkColorScheme(LocalContext.current).primary,
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic),
|
||||||
|
selected = GlobalState.theme == Theme.M3Dynamic,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
setNewTheme(Theme.M3Dynamic)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ThemeChip(
|
||||||
|
color = Color(0xffa0a0a0),
|
||||||
|
text = stringResource(id = R.string.settings_appearance_theme_m3dynamic_unsupported),
|
||||||
|
selected = false,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.settings_appearance_theme_m3dynamic_unsupported_toast),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,16 +2,20 @@ package chat.revolt.screens.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.ElevatedButton
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.components.generic.PageHeader
|
import chat.revolt.components.generic.PageHeader
|
||||||
|
import chat.revolt.components.screens.settings.SettingsCategory
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
|
|
@ -21,20 +25,51 @@ fun SettingsScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
PageHeader(stringResource(id = R.string.settings))
|
PageHeader(
|
||||||
ElevatedButton(
|
text = stringResource(id = R.string.settings),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
showBackButton = true,
|
||||||
onClick = {
|
onBackButtonClicked = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}) {
|
})
|
||||||
Text(text = stringResource(id = R.string.back))
|
|
||||||
}
|
Column(
|
||||||
Button(
|
modifier = Modifier
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.fillMaxSize()
|
||||||
onClick = {
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
SettingsCategory(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_palette_24dp),
|
||||||
|
contentDescription =
|
||||||
|
stringResource(id = R.string.settings_appearance),
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { textStyle ->
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.settings_appearance),
|
||||||
|
style = textStyle
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
navController.navigate("settings/appearance")
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCategory(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Info,
|
||||||
|
contentDescription = stringResource(id = R.string.about),
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { textStyle ->
|
||||||
|
Text(text = stringResource(id = R.string.about), style = textStyle)
|
||||||
|
})
|
||||||
|
{
|
||||||
navController.navigate("about")
|
navController.navigate("about")
|
||||||
}) {
|
}
|
||||||
Text(text = stringResource(id = R.string.about))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package chat.revolt.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
|
||||||
val Pink40 = Color(0xFF7D5260)
|
|
||||||
|
|
@ -3,10 +3,7 @@ package chat.revolt.ui.theme
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -15,43 +12,78 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
|
||||||
const val FORCE_ANDROID_DEFAULTS = false
|
val RevoltColorScheme = darkColorScheme(
|
||||||
|
|
||||||
const val FOREGROUND = 0xffffffff
|
|
||||||
|
|
||||||
val DarkColorScheme = darkColorScheme(
|
|
||||||
primary = Color(0xfffe4654),
|
primary = Color(0xfffe4654),
|
||||||
onPrimary = Color(FOREGROUND),
|
onPrimary = Color(0xffffffff),
|
||||||
secondary = Color(0xfffd6671),
|
secondary = Color(0xfffd6671),
|
||||||
onSecondary = Color(FOREGROUND),
|
onSecondary = Color(0xffffffff),
|
||||||
tertiary = Color(0xffff6667),
|
tertiary = Color(0xffff6667),
|
||||||
onTertiary = Color(FOREGROUND),
|
onTertiary = Color(0xffffffff),
|
||||||
background = Color(0xff101823),
|
background = Color(0xff101823),
|
||||||
onBackground = Color(FOREGROUND),
|
onBackground = Color(0xffffffff),
|
||||||
surfaceVariant = Color(0xff172333),
|
surfaceVariant = Color(0xff172333),
|
||||||
onSurfaceVariant = Color(FOREGROUND),
|
onSurfaceVariant = Color(0xffffffff),
|
||||||
surface = Color(0xff111a26),
|
surface = Color(0xff111a26),
|
||||||
onSurface = Color(FOREGROUND),
|
onSurface = Color(0xffffffff),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val AmoledColorScheme = RevoltColorScheme.copy(
|
||||||
|
background = Color(0xff000000),
|
||||||
|
onBackground = Color(0xffffffff),
|
||||||
|
surfaceVariant = Color(0xff131313),
|
||||||
|
onSurfaceVariant = Color(0xffffffff),
|
||||||
|
surface = Color(0xff212121),
|
||||||
|
onSurface = Color(0xffffffff),
|
||||||
|
)
|
||||||
|
|
||||||
|
val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Color(0xfffe4654),
|
||||||
|
onPrimary = Color(0xffffffff),
|
||||||
|
secondary = Color(0xfffd6671),
|
||||||
|
onSecondary = Color(0xffffffff),
|
||||||
|
tertiary = Color(0xffff6667),
|
||||||
|
onTertiary = Color(0xffffffff),
|
||||||
|
background = Color(0xffffffff),
|
||||||
|
onBackground = Color(0xff000000),
|
||||||
|
surfaceVariant = Color(0xffe6e6e6),
|
||||||
|
onSurfaceVariant = Color(0xff000000),
|
||||||
|
surface = Color(0xffdddddd),
|
||||||
|
onSurface = Color(0xff000000),
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Theme {
|
||||||
|
None,
|
||||||
|
Revolt,
|
||||||
|
Light,
|
||||||
|
M3Dynamic,
|
||||||
|
Amoled,
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RevoltTheme(
|
fun RevoltTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
requestedTheme: Theme,
|
||||||
// Dynamic color is available on Android 12+
|
|
||||||
dynamicColor: Boolean = false,
|
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val systemInDarkTheme = isSystemInDarkTheme()
|
||||||
|
val m3Supported = systemSupportsDynamicColors()
|
||||||
|
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
dynamicColor && darkTheme && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
m3Supported && requestedTheme == Theme.M3Dynamic && systemInDarkTheme -> dynamicDarkColorScheme(
|
||||||
val context = LocalContext.current
|
context
|
||||||
dynamicDarkColorScheme(context)
|
)
|
||||||
}
|
m3Supported && requestedTheme == Theme.M3Dynamic && !systemInDarkTheme -> dynamicLightColorScheme(
|
||||||
dynamicColor && !darkTheme && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
context
|
||||||
val context = LocalContext.current
|
)
|
||||||
dynamicLightColorScheme(context)
|
requestedTheme == Theme.Revolt -> RevoltColorScheme
|
||||||
}
|
requestedTheme == Theme.Light -> LightColorScheme
|
||||||
else -> DarkColorScheme
|
requestedTheme == Theme.Amoled -> AmoledColorScheme
|
||||||
|
requestedTheme == Theme.None && systemInDarkTheme -> RevoltColorScheme
|
||||||
|
requestedTheme == Theme.None && !systemInDarkTheme -> LightColorScheme
|
||||||
|
else -> RevoltColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
SideEffect {
|
SideEffect {
|
||||||
|
|
@ -62,15 +94,13 @@ fun RevoltTheme(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FORCE_ANDROID_DEFAULTS) {
|
MaterialTheme(
|
||||||
MaterialTheme(
|
colorScheme = colorScheme,
|
||||||
content = content
|
typography = RevoltTypography,
|
||||||
)
|
content = content
|
||||||
} else {
|
)
|
||||||
MaterialTheme(
|
}
|
||||||
colorScheme = colorScheme,
|
|
||||||
typography = RevoltTypography,
|
fun systemSupportsDynamicColors(): Boolean {
|
||||||
content = content
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ val RevoltTypography = Typography(
|
||||||
),
|
),
|
||||||
displaySmall = TextStyle(
|
displaySmall = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 36.sp
|
fontSize = 36.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -49,39 +49,39 @@ val RevoltTypography = Typography(
|
||||||
),
|
),
|
||||||
headlineSmall = TextStyle(
|
headlineSmall = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 24.sp
|
fontSize = 24.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
titleLarge = TextStyle(
|
titleLarge = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 22.sp
|
fontSize = 22.sp
|
||||||
),
|
),
|
||||||
titleMedium = TextStyle(
|
titleMedium = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
),
|
),
|
||||||
titleSmall = TextStyle(
|
titleSmall = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
labelLarge = TextStyle(
|
labelLarge = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
),
|
),
|
||||||
labelMedium = TextStyle(
|
labelMedium = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 12.sp
|
fontSize = 12.sp
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
labelSmall = TextStyle(
|
||||||
fontFamily = Inter,
|
fontFamily = Inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 11.sp
|
fontSize = 11.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:pathData="M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z"
|
||||||
|
android:fillColor="#ffffff" />
|
||||||
|
</vector>
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
<path
|
<path
|
||||||
android:pathData="M24.98,12.56c0,2.79 -1.52,4.46 -4.76,4.46L14.84,17.02L14.84,8.2L20.22,8.2C23.46,8.2 24.98,9.93 24.98,12.56ZM0.98,1.01 L6.18,8.22L6.18,36.58h8.67L14.84,23.15h2.08l7.4,13.43h9.78l-8.21,-14.09A10.35,10.35 0,0 0,33.8 12.21c0,-6.18 -4.36,-11.2 -13.07,-11.2ZM61.01,1.01L39.22,1.01L39.22,36.58L61.01,36.58L61.01,29.64L47.89,29.64v-7.8L59.49,21.84L59.49,15.15L47.89,15.15L47.89,8.21L61.01,8.21ZM82,27.87 L73.18,1.01L63.95,1.01L76.57,36.58L87.42,36.58L100.03,1.01L90.86,1.01ZM138.65,18.69c0,-10.69 -8.06,-18.19 -18.19,-18.19 -10.09,0 -18.3,7.5 -18.3,18.19a17.9,17.9 0,0 0,18.3 18.24A17.82,17.82 0,0 0,138.65 18.69ZM111.03,18.69c0,-6.34 3.65,-10.34 9.43,-10.34 5.68,0 9.38,4 9.38,10.34 0,6.23 -3.7,10.34 -9.38,10.34C114.68,29.03 111.03,24.93 111.03,18.69ZM143.47,1.01L143.47,36.58L163.49,36.58v-6.95L152.13,29.63L152.13,1.01ZM165.71,8.21h9.43L175.14,36.58h8.67L183.81,8.2h9.43v-7.2L165.71,1.01Z"
|
android:pathData="M24.98,12.56c0,2.79 -1.52,4.46 -4.76,4.46L14.84,17.02L14.84,8.2L20.22,8.2C23.46,8.2 24.98,9.93 24.98,12.56ZM0.98,1.01 L6.18,8.22L6.18,36.58h8.67L14.84,23.15h2.08l7.4,13.43h9.78l-8.21,-14.09A10.35,10.35 0,0 0,33.8 12.21c0,-6.18 -4.36,-11.2 -13.07,-11.2ZM61.01,1.01L39.22,1.01L39.22,36.58L61.01,36.58L61.01,29.64L47.89,29.64v-7.8L59.49,21.84L59.49,15.15L47.89,15.15L47.89,8.21L61.01,8.21ZM82,27.87 L73.18,1.01L63.95,1.01L76.57,36.58L87.42,36.58L100.03,1.01L90.86,1.01ZM138.65,18.69c0,-10.69 -8.06,-18.19 -18.19,-18.19 -10.09,0 -18.3,7.5 -18.3,18.19a17.9,17.9 0,0 0,18.3 18.24A17.82,17.82 0,0 0,138.65 18.69ZM111.03,18.69c0,-6.34 3.65,-10.34 9.43,-10.34 5.68,0 9.38,4 9.38,10.34 0,6.23 -3.7,10.34 -9.38,10.34C114.68,29.03 111.03,24.93 111.03,18.69ZM143.47,1.01L143.47,36.58L163.49,36.58v-6.95L152.13,29.63L152.13,1.01ZM165.71,8.21h9.43L175.14,36.58h8.67L183.81,8.2h9.43v-7.2L165.71,1.01Z"
|
||||||
android:strokeWidth="1"
|
android:strokeWidth="1"
|
||||||
android:fillColor="#fff"
|
android:fillColor="#ffffff"
|
||||||
android:strokeColor="#fff"/>
|
android:strokeColor="#ffffff"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
|
|
@ -107,4 +107,14 @@
|
||||||
|
|
||||||
<string name="scroll_to_bottom">Scroll to bottom</string>
|
<string name="scroll_to_bottom">Scroll to bottom</string>
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
|
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_appearance_theme">Theme</string>
|
||||||
|
<string name="settings_appearance_theme_none">System</string>
|
||||||
|
<string name="settings_appearance_theme_revolt">Revolt</string>
|
||||||
|
<string name="settings_appearance_theme_light">Light</string>
|
||||||
|
<string name="settings_appearance_theme_amoled">Pure Black</string>
|
||||||
|
<string name="settings_appearance_theme_m3dynamic">Material You</string>
|
||||||
|
<string name="settings_appearance_theme_m3dynamic_unsupported">Material You (unsupported)</string>
|
||||||
|
<string name="settings_appearance_theme_m3dynamic_unsupported_toast">Material You is not supported on this device.</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue