diff --git a/app/build.gradle b/app/build.gradle
index a6360534..4286475e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -156,6 +156,9 @@ dependencies {
// AboutLibraries - automated OSS library attribution
implementation "com.mikepenz:aboutlibraries-compose:$aboutlibraries_version"
+ // Lottie - animated vector graphics
+ implementation "com.airbnb.android:lottie-compose:6.0.0"
+
// Sentry - crash reporting
implementation 'io.sentry:sentry-android:6.15.0'
implementation 'io.sentry:sentry-compose-android:6.15.0'
diff --git a/app/src/main/java/chat/revolt/MainActivity.kt b/app/src/main/java/chat/revolt/MainActivity.kt
index 43369d82..d6eec214 100644
--- a/app/src/main/java/chat/revolt/MainActivity.kt
+++ b/app/src/main/java/chat/revolt/MainActivity.kt
@@ -26,6 +26,7 @@ import chat.revolt.screens.login.GreeterScreen
import chat.revolt.screens.login.LoginScreen
import chat.revolt.screens.login.MfaScreen
import chat.revolt.screens.settings.AppearanceSettingsScreen
+import chat.revolt.screens.settings.DebugSettingsScreen
import chat.revolt.screens.settings.SettingsScreen
import chat.revolt.ui.theme.RevoltTheme
import com.google.accompanist.navigation.animation.AnimatedNavHost
@@ -111,6 +112,7 @@ fun AppEntrypoint() {
composable("settings") { SettingsScreen(navController) }
composable("settings/appearance") { AppearanceSettingsScreen(navController) }
+ composable("settings/debug") { DebugSettingsScreen(navController) }
dialog("settings/feedback") { FeedbackDialog(navController) }
composable("about") { AboutScreen(navController) }
diff --git a/app/src/main/java/chat/revolt/persistence/KVStorage.kt b/app/src/main/java/chat/revolt/persistence/KVStorage.kt
index a72b361e..81277345 100644
--- a/app/src/main/java/chat/revolt/persistence/KVStorage.kt
+++ b/app/src/main/java/chat/revolt/persistence/KVStorage.kt
@@ -29,6 +29,16 @@ class KVStorage @Inject constructor(
return dataStore.data.firstOrNull()?.get(stringPreferencesKey(key))
}
+ suspend fun set(key: String, value: Boolean) {
+ dataStore.edit { preferences ->
+ preferences[stringPreferencesKey(key)] = value.toString()
+ }
+ }
+
+ suspend fun getBoolean(key: String): Boolean? {
+ return dataStore.data.firstOrNull()?.get(stringPreferencesKey(key))?.toBoolean()
+ }
+
suspend fun remove(key: String) {
dataStore.edit { preferences ->
preferences.remove(stringPreferencesKey(key))
diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt
index 746adc9c..df9e0d7e 100644
--- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt
+++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -16,13 +17,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -64,6 +69,11 @@ import chat.revolt.screens.chat.sheets.StatusSheet
import chat.revolt.screens.chat.views.HomeScreen
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
import chat.revolt.screens.chat.views.channel.ChannelScreen
+import com.airbnb.lottie.RenderMode
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
import com.google.accompanist.navigation.material.bottomSheet
@@ -85,10 +95,19 @@ class ChatRouterViewModel @Inject constructor(
val currentChannel: String?
get() = _currentChannel.value
+ private var _sidebarSparkDisplayed = mutableStateOf(true)
+ val sidebarSparkDisplayed: Boolean
+ get() = _sidebarSparkDisplayed.value
+
init {
viewModelScope.launch {
_currentServer.value = kvStorage.get("currentServer") ?: "home"
_currentChannel.value = kvStorage.get("currentChannel")
+ _sidebarSparkDisplayed.value = if (kvStorage.getBoolean("sidebarSpark") == null) {
+ false
+ } else {
+ kvStorage.getBoolean("sidebarSpark")!!
+ }
}
}
@@ -108,6 +127,10 @@ class ChatRouterViewModel @Inject constructor(
}
}
+ suspend fun setSettingsHintDisplayed() {
+ kvStorage.set("sidebarSpark", true)
+ }
+
fun navigateToServer(serverId: String, navController: NavController) {
if (serverId == "home") {
navController.navigate("home") {
@@ -155,6 +178,12 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
+ val showSidebarSpark = remember { mutableStateOf(false) }
+ val sidebarSparkComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.open_settings_tutorial))
+ val sidebarSparkProgress by animateLottieCompositionAsState(
+ composition = sidebarSparkComposition,
+ )
+
LaunchedEffect(drawerState) {
snapshotFlow { drawerState.currentValue }
.distinctUntilChanged()
@@ -175,11 +204,52 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
}
}
+ LaunchedEffect(viewModel.sidebarSparkDisplayed) {
+ snapshotFlow { viewModel.sidebarSparkDisplayed }
+ .distinctUntilChanged()
+ .collect { displayed ->
+ showSidebarSpark.value = !displayed
+ }
+ }
+
ModalBottomSheetLayout(
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
bottomSheetNavigator = bottomSheetNavigator,
) {
+ if (showSidebarSpark.value) {
+ AlertDialog(
+ onDismissRequest = {},
+ title = {
+ Text(stringResource(id = R.string.spark_sidebar_settings_tutorial))
+ },
+ text = {
+ Column {
+ LottieAnimation(
+ composition = sidebarSparkComposition,
+ progress = { sidebarSparkProgress },
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(1f),
+ renderMode = RenderMode.HARDWARE
+ )
+ Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_1))
+ Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_description_2))
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ scope.launch {
+ viewModel.setSettingsHintDisplayed()
+ }
+ showSidebarSpark.value = false
+ }) {
+ Text(stringResource(id = R.string.spark_sidebar_settings_tutorial_acknowledge))
+ }
+ }
+ )
+ }
+
Column {
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
DisconnectedNotice(
@@ -255,7 +325,10 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil
}
}
- Crossfade(targetState = viewModel.currentServer) {
+ Crossfade(
+ targetState = viewModel.currentServer,
+ label = "Channel List"
+ ) {
ChannelList(
serverId = it,
drawerState = drawerState,
diff --git a/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt
new file mode 100644
index 00000000..4757ab17
--- /dev/null
+++ b/app/src/main/java/chat/revolt/screens/settings/DebugSettingsScreen.kt
@@ -0,0 +1,82 @@
+package chat.revolt.screens.settings
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ElevatedButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.NavController
+import chat.revolt.components.generic.PageHeader
+import chat.revolt.persistence.KVStorage
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class DebugSettingsScreenViewModel @Inject constructor(
+ private val kvStorage: KVStorage,
+) : ViewModel() {
+ fun forgetSidebarSparkShown() {
+ viewModelScope.launch {
+ kvStorage.remove("sidebarSpark")
+ }
+ }
+
+ fun forgetAllSparks() {
+ this.forgetSidebarSparkShown()
+ }
+}
+
+@Composable
+fun DebugSettingsScreen(
+ navController: NavController,
+ viewModel: DebugSettingsScreenViewModel = hiltViewModel()
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ PageHeader(
+ text = "Debug",
+ showBackButton = true,
+ onBackButtonClicked = {
+ navController.popBackStack()
+ })
+
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(20.dp)
+ ) {
+ Text(
+ text = "Sparks",
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier.padding(bottom = 10.dp)
+ )
+ Row(
+ modifier = Modifier.horizontalScroll(rememberScrollState())
+ ) {
+ TextButton(onClick = { viewModel.forgetSidebarSparkShown() }) {
+ Text("Forget sidebar spark")
+ }
+ ElevatedButton(onClick = { viewModel.forgetAllSparks() }) {
+ Text("Forget all sparks")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt
index abb16071..441cca7c 100644
--- a/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/chat/revolt/screens/settings/SettingsScreen.kt
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -18,6 +19,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
+import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.generic.SheetClickable
@@ -78,6 +80,24 @@ fun SettingsScreen(
navController.navigate("about")
}
+ if (BuildConfig.DEBUG) {
+ SheetClickable(
+ icon = { modifier ->
+ Icon(
+ imageVector = Icons.Default.Settings,
+ contentDescription = "Debug",
+ modifier = modifier
+ )
+ },
+ label = { textStyle ->
+ Text(text = "Debug", style = textStyle)
+ },
+ modifier = Modifier.testTag("settings_view_debug")
+ ) {
+ navController.navigate("settings/debug")
+ }
+ }
+
Divider()
SheetClickable(
@@ -109,7 +129,8 @@ fun SettingsScreen(
},
modifier = Modifier.testTag("settings_view_logout")
) {
- Toast.makeText(navController.context, "Not implemented yet", Toast.LENGTH_SHORT).show()
+ Toast.makeText(navController.context, "Not implemented yet", Toast.LENGTH_SHORT)
+ .show()
}
}
}
diff --git a/app/src/main/res/raw/open_settings_tutorial.lottie b/app/src/main/res/raw/open_settings_tutorial.lottie
new file mode 100644
index 00000000..082eb081
Binary files /dev/null and b/app/src/main/res/raw/open_settings_tutorial.lottie differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 80b06b0c..f0deccbb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -244,6 +244,11 @@
Share video
Share image
+ The settings are in the sidebar
+ You can open the sidebar by swiping from the left edge of the screen.
+ Then long tap your profile picture to open the settings.
+ Got it
+
Appearance
Theme
System