This commit is contained in:
2026-02-24 18:03:22 +00:00
parent 1c8c306652
commit 39010f882d
6 changed files with 252 additions and 12 deletions

View File

@@ -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<Boolean> = context.dataStore.data.map { prefs ->
@@ -117,6 +118,16 @@ class PreferencesManager(private val context: Context) {
}
}
val hideSpacesWhenClosed: Flow<Boolean> = 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() }
}

View File

@@ -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 }
}
},
)
}
}

View File

@@ -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")
}

View File

@@ -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,6 +76,7 @@ fun MainScreen(
) {
// Main content: SpaceList + chat + members
Row(modifier = Modifier.fillMaxSize()) {
if (!hideSpacesWhenClosed) {
SpaceList(
spaces = spaces,
selectedSpace = selectedSpace,
@@ -78,8 +84,10 @@ fun MainScreen(
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 {
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,

View File

@@ -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

View File

@@ -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,
)
}
}