From 39010f882d3d123e968802a46e206beaa17d650c Mon Sep 17 00:00:00 2001 From: mrfluffy Date: Tue, 24 Feb 2026 18:03:22 +0000 Subject: [PATCH] settings --- .../data/local/PreferencesManager.kt | 11 ++ .../ui/navigation/FluffytrixNavigation.kt | 16 +- .../fluffytrix/ui/navigation/Screen.kt | 1 + .../fluffytrix/ui/screens/main/MainScreen.kt | 41 +++- .../ui/screens/main/components/SpaceList.kt | 20 +- .../ui/screens/settings/SettingsScreen.kt | 175 ++++++++++++++++++ 6 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt diff --git a/app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt b/app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt index 1866171..6ea7ab2 100644 --- a/app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt +++ b/app/src/main/java/com/example/fluffytrix/data/local/PreferencesManager.kt @@ -29,6 +29,7 @@ class PreferencesManager(private val context: Context) { private val KEY_IS_LOGGED_IN = booleanPreferencesKey("is_logged_in") private val KEY_CHANNEL_ORDER = stringPreferencesKey("channel_order") private val KEY_CHILD_SPACE_ORDER = stringPreferencesKey("child_space_order") + private val KEY_HIDE_SPACES_WHEN_CLOSED = booleanPreferencesKey("hide_spaces_when_closed") } val isLoggedIn: Flow = context.dataStore.data.map { prefs -> @@ -117,6 +118,16 @@ class PreferencesManager(private val context: Context) { } } + val hideSpacesWhenClosed: Flow = context.dataStore.data.map { prefs -> + prefs[KEY_HIDE_SPACES_WHEN_CLOSED] == true + } + + suspend fun setHideSpacesWhenClosed(enabled: Boolean) { + context.dataStore.edit { prefs -> + prefs[KEY_HIDE_SPACES_WHEN_CLOSED] = enabled + } + } + suspend fun clearSession() { context.dataStore.edit { it.clear() } } diff --git a/app/src/main/java/com/example/fluffytrix/ui/navigation/FluffytrixNavigation.kt b/app/src/main/java/com/example/fluffytrix/ui/navigation/FluffytrixNavigation.kt index d580d6f..c1cb6ee 100644 --- a/app/src/main/java/com/example/fluffytrix/ui/navigation/FluffytrixNavigation.kt +++ b/app/src/main/java/com/example/fluffytrix/ui/navigation/FluffytrixNavigation.kt @@ -29,6 +29,7 @@ import com.example.fluffytrix.data.local.PreferencesManager import com.example.fluffytrix.data.repository.AuthRepository import com.example.fluffytrix.ui.screens.login.LoginScreen import com.example.fluffytrix.ui.screens.main.MainScreen +import com.example.fluffytrix.ui.screens.settings.SettingsScreen import com.example.fluffytrix.ui.screens.verification.VerificationScreen import kotlinx.coroutines.flow.firstOrNull import org.koin.compose.koinInject @@ -143,7 +144,20 @@ fun FluffytrixNavigation() { navController.navigate(Screen.Login.route) { popUpTo(Screen.Main.route) { inclusive = true } } - } + }, + onSettingsClick = { + navController.navigate(Screen.Settings.route) + }, + ) + } + composable(Screen.Settings.route) { + SettingsScreen( + onBack = { navController.popBackStack() }, + onLogout = { + navController.navigate(Screen.Login.route) { + popUpTo(0) { inclusive = true } + } + }, ) } } diff --git a/app/src/main/java/com/example/fluffytrix/ui/navigation/Screen.kt b/app/src/main/java/com/example/fluffytrix/ui/navigation/Screen.kt index 240288c..ea76483 100644 --- a/app/src/main/java/com/example/fluffytrix/ui/navigation/Screen.kt +++ b/app/src/main/java/com/example/fluffytrix/ui/navigation/Screen.kt @@ -4,4 +4,5 @@ sealed class Screen(val route: String) { data object Login : Screen("login") data object Verification : Screen("verification") data object Main : Screen("main") + data object Settings : Screen("settings") } diff --git a/app/src/main/java/com/example/fluffytrix/ui/screens/main/MainScreen.kt b/app/src/main/java/com/example/fluffytrix/ui/screens/main/MainScreen.kt index 39d5c6b..ff1d4db 100644 --- a/app/src/main/java/com/example/fluffytrix/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/example/fluffytrix/ui/screens/main/MainScreen.kt @@ -18,15 +18,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import com.example.fluffytrix.data.local.PreferencesManager import com.example.fluffytrix.ui.screens.main.components.ChannelList import com.example.fluffytrix.ui.screens.main.components.MemberList import com.example.fluffytrix.ui.screens.main.components.MessageTimeline import com.example.fluffytrix.ui.screens.main.components.SpaceList import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject @Composable fun MainScreen( onLogout: () -> Unit, + onSettingsClick: () -> Unit = {}, viewModel: MainViewModel = koinViewModel(), ) { val spaces by viewModel.spaces.collectAsState() @@ -43,6 +46,8 @@ fun MainScreen( val channelSections by viewModel.channelSections.collectAsState() val unreadMarkerIndex by viewModel.unreadMarkerIndex.collectAsState() val listState = viewModel.channelListState + val preferencesManager: PreferencesManager = koinInject() + val hideSpacesWhenClosed by preferencesManager.hideSpacesWhenClosed.collectAsState(initial = false) // Back button opens channel list when in a chat, or does nothing if already open BackHandler(enabled = selectedChannel != null && !showChannelList) { @@ -71,15 +76,18 @@ fun MainScreen( ) { // Main content: SpaceList + chat + members Row(modifier = Modifier.fillMaxSize()) { - SpaceList( - spaces = spaces, - selectedSpace = selectedSpace, - homeUnreadStatus = homeUnreadStatus, - onSpaceClick = { viewModel.selectSpace(it) }, - onHomeClick = { viewModel.selectHome() }, - onToggleChannelList = { viewModel.toggleChannelList() }, - contentPadding = padding, - ) + if (!hideSpacesWhenClosed) { + SpaceList( + spaces = spaces, + selectedSpace = selectedSpace, + homeUnreadStatus = homeUnreadStatus, + onSpaceClick = { viewModel.selectSpace(it) }, + onHomeClick = { viewModel.selectHome() }, + onToggleChannelList = { viewModel.toggleChannelList() }, + onSettingsClick = onSettingsClick, + contentPadding = padding, + ) + } MessageTimeline( selectedChannel = selectedChannel, @@ -110,7 +118,20 @@ fun MainScreen( modifier = Modifier.zIndex(1f), ) { Row { - Spacer(modifier = Modifier.width(64.dp)) + if (hideSpacesWhenClosed) { + SpaceList( + spaces = spaces, + selectedSpace = selectedSpace, + homeUnreadStatus = homeUnreadStatus, + onSpaceClick = { viewModel.selectSpace(it) }, + onHomeClick = { viewModel.selectHome() }, + onToggleChannelList = { viewModel.toggleChannelList() }, + onSettingsClick = onSettingsClick, + contentPadding = padding, + ) + } else { + Spacer(modifier = Modifier.width(64.dp)) + } ChannelList( channels = channels, sections = channelSections, diff --git a/app/src/main/java/com/example/fluffytrix/ui/screens/main/components/SpaceList.kt b/app/src/main/java/com/example/fluffytrix/ui/screens/main/components/SpaceList.kt index 5344cd2..8c14183 100644 --- a/app/src/main/java/com/example/fluffytrix/ui/screens/main/components/SpaceList.kt +++ b/app/src/main/java/com/example/fluffytrix/ui/screens/main/components/SpaceList.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding @@ -16,6 +17,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -44,15 +46,20 @@ fun SpaceList( onSpaceClick: (String) -> Unit, onHomeClick: () -> Unit, onToggleChannelList: () -> Unit, + onSettingsClick: () -> Unit = {}, contentPadding: PaddingValues, ) { - LazyColumn( + Column( modifier = Modifier .width(64.dp) .fillMaxHeight() .background(MaterialTheme.colorScheme.surfaceVariant) .padding(top = contentPadding.calculateTopPadding()), horizontalAlignment = Alignment.CenterHorizontally, + ) { + LazyColumn( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 8.dp), ) { @@ -149,6 +156,17 @@ fun SpaceList( } } } + IconButton( + onClick = onSettingsClick, + modifier = Modifier.padding(bottom = contentPadding.calculateBottomPadding() + 8.dp), + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = "Settings", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } } @Composable diff --git a/app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..203a6bb --- /dev/null +++ b/app/src/main/java/com/example/fluffytrix/ui/screens/settings/SettingsScreen.kt @@ -0,0 +1,175 @@ +package com.example.fluffytrix.ui.screens.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.example.fluffytrix.data.local.PreferencesManager +import kotlinx.coroutines.launch +import org.koin.compose.koinInject + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + onBack: () -> Unit, + onLogout: () -> Unit, +) { + val preferencesManager: PreferencesManager = koinInject() + val userId by preferencesManager.userId.collectAsState(initial = null) + val homeserver by preferencesManager.homeserverUrl.collectAsState(initial = null) + val deviceId by preferencesManager.deviceId.collectAsState(initial = null) + val hideSpacesWhenClosed by preferencesManager.hideSpacesWhenClosed.collectAsState(initial = false) + val scope = rememberCoroutineScope() + val context = LocalContext.current + val appVersion = try { + context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "Unknown" + } catch (_: Exception) { + "Unknown" + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Settings") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + }, + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + ) { + SectionHeader("Account") + SettingRow("User ID", userId ?: "Not logged in") + SettingRow("Homeserver", homeserver ?: "Unknown") + SettingRow("Device ID", deviceId ?: "Unknown") + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = onLogout, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error, + ), + modifier = Modifier.fillMaxWidth(), + ) { + Text("Log out") + } + + HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp)) + + SectionHeader("Appearance") + SettingToggleRow( + label = "Hide spaces bar when rooms list is closed", + description = "Only show the spaces sidebar when the rooms list is open", + checked = hideSpacesWhenClosed, + onCheckedChange = { scope.launch { preferencesManager.setHideSpacesWhenClosed(it) } }, + ) + SettingRow("Theme", "System default") + + HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp)) + + SectionHeader("Notifications") + SettingRow("Push notifications", "Enabled") + + HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp)) + + SectionHeader("About") + SettingRow("App version", appVersion) + SettingRow("App name", "Fluffytrix") + + Spacer(modifier = Modifier.height(32.dp)) + } + } +} + +@Composable +private fun SectionHeader(title: String) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 8.dp), + ) +} + +@Composable +private fun SettingToggleRow( + label: String, + description: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text(text = label, style = MaterialTheme.typography.bodyMedium) + Text( + text = description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch(checked = checked, onCheckedChange = onCheckedChange) + } +} + +@Composable +private fun SettingRow(label: String, value: String) { + Column(modifier = Modifier.padding(vertical = 6.dp)) { + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + ) + Text( + text = value, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } +}