feat: move search to top bar, replace with Stats tab in bottom nav

- Bottom tabs: Home / Stats / Settings (was Home / Search / Settings)
- Search icon back in feed top bar
- Stats screen: no back button, tab-style fade transitions
- Removed Stats link from Settings screen
This commit is contained in:
Paweł Orzech 2026-03-19 14:57:44 +01:00
parent c91ccd0afb
commit 3da3e97e77
No known key found for this signature in database
4 changed files with 19 additions and 91 deletions

View file

@ -249,6 +249,9 @@ fun FeedScreen(
} }
}, },
actions = { actions = {
IconButton(onClick = { viewModel.activateSearch() }) {
Icon(Icons.Default.Search, contentDescription = "Search")
}
SortButton( SortButton(
currentSort = sortOrder, currentSort = sortOrder,
onSortSelected = { viewModel.setSortOrder(it) } onSortSelected = { viewModel.setSortOrder(it) }

View file

@ -10,8 +10,8 @@ import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BarChart
import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -56,12 +56,12 @@ data class BottomNavItem(
val bottomNavItems = listOf( val bottomNavItems = listOf(
BottomNavItem(Routes.FEED, "Home", Icons.Default.Home), BottomNavItem(Routes.FEED, "Home", Icons.Default.Home),
BottomNavItem(Routes.SEARCH, "Search", Icons.Default.Search), BottomNavItem(Routes.STATS, "Stats", Icons.Default.BarChart),
BottomNavItem(Routes.SETTINGS, "Settings", Icons.Default.Settings) BottomNavItem(Routes.SETTINGS, "Settings", Icons.Default.Settings)
) )
/** Routes where the bottom navigation bar should be visible */ /** Routes where the bottom navigation bar should be visible */
private val bottomBarRoutes = setOf(Routes.FEED, Routes.SEARCH, Routes.SETTINGS) private val bottomBarRoutes = setOf(Routes.FEED, Routes.STATS, Routes.SETTINGS)
@Composable @Composable
fun SwooshNavGraph( fun SwooshNavGraph(
@ -93,17 +93,6 @@ fun SwooshNavGraph(
NavigationBarItem( NavigationBarItem(
selected = selected, selected = selected,
onClick = { onClick = {
if (item.route == Routes.SEARCH) {
// Navigate to feed with search mode active
feedViewModel.activateSearch()
navController.navigate(Routes.SEARCH) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
} else {
if (item.route == Routes.FEED) { if (item.route == Routes.FEED) {
feedViewModel.deactivateSearch() feedViewModel.deactivateSearch()
} }
@ -114,7 +103,6 @@ fun SwooshNavGraph(
launchSingleTop = true launchSingleTop = true
restoreState = true restoreState = true
} }
}
}, },
icon = { icon = {
Icon( Icon(
@ -189,37 +177,6 @@ fun SwooshNavGraph(
) )
} }
composable(
Routes.SEARCH,
enterTransition = { fadeIn(tween(200)) },
exitTransition = { fadeOut(tween(150)) },
popEnterTransition = { fadeIn(tween(200)) },
popExitTransition = { fadeOut(tween(150)) }
) {
// Ensure search is active when this tab is shown
LaunchedEffect(Unit) {
feedViewModel.activateSearch()
}
FeedScreen(
viewModel = feedViewModel,
onPostClick = { post ->
selectedPost = post
navController.navigate(Routes.DETAIL)
},
onCompose = {
editPost = null
navController.navigate(Routes.COMPOSER)
},
onEditPost = { post ->
editPost = post
navController.navigate(Routes.COMPOSER)
},
onAddAccount = {
navController.navigate(Routes.ADD_ACCOUNT)
}
)
}
composable( composable(
Routes.COMPOSER, Routes.COMPOSER,
enterTransition = { slideInVertically(initialOffsetY = { it }, animationSpec = tween(250)) + fadeIn(tween(200)) }, enterTransition = { slideInVertically(initialOffsetY = { it }, animationSpec = tween(250)) + fadeIn(tween(200)) },
@ -298,23 +255,18 @@ fun SwooshNavGraph(
navController.navigate(Routes.SETUP) { navController.navigate(Routes.SETUP) {
popUpTo(0) { inclusive = true } popUpTo(0) { inclusive = true }
} }
},
onStatsClick = {
navController.navigate(Routes.STATS)
} }
) )
} }
composable( composable(
Routes.STATS, Routes.STATS,
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(250)) }, enterTransition = { fadeIn(tween(200)) },
exitTransition = { fadeOut(tween(150)) }, exitTransition = { fadeOut(tween(150)) },
popEnterTransition = { fadeIn(tween(200)) }, popEnterTransition = { fadeIn(tween(200)) },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(200)) } popExitTransition = { fadeOut(tween(150)) }
) { ) {
StatsScreen( StatsScreen()
onBack = { navController.popBackStack() }
)
} }
composable( composable(

View file

@ -7,7 +7,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.BarChart
import androidx.compose.material.icons.filled.BrightnessAuto import androidx.compose.material.icons.filled.BrightnessAuto
import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.LightMode import androidx.compose.material.icons.filled.LightMode
@ -31,8 +30,7 @@ import com.swoosh.microblog.ui.theme.ThemeViewModel
fun SettingsScreen( fun SettingsScreen(
onBack: () -> Unit, onBack: () -> Unit,
onLogout: () -> Unit, onLogout: () -> Unit,
themeViewModel: ThemeViewModel? = null, themeViewModel: ThemeViewModel? = null
onStatsClick: () -> Unit = {}
) { ) {
val context = LocalContext.current val context = LocalContext.current
val accountManager = remember { AccountManager(context) } val accountManager = remember { AccountManager(context) }
@ -130,24 +128,6 @@ fun SettingsScreen(
HorizontalDivider() HorizontalDivider()
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Writing Statistics button
FilledTonalButton(
onClick = onStatsClick,
modifier = Modifier.fillMaxWidth()
) {
Icon(
Icons.Default.BarChart,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text("Writing Statistics")
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(16.dp))
// Disconnect current account // Disconnect current account
OutlinedButton( OutlinedButton(
onClick = { showDisconnectDialog = true }, onClick = { showDisconnectDialog = true },

View file

@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Article import androidx.compose.material.icons.automirrored.filled.Article
import androidx.compose.material.icons.filled.Create import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Schedule import androidx.compose.material.icons.filled.Schedule
@ -29,7 +28,6 @@ import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun StatsScreen( fun StatsScreen(
onBack: () -> Unit,
viewModel: StatsViewModel = viewModel() viewModel: StatsViewModel = viewModel()
) { ) {
val state by viewModel.uiState.collectAsStateWithLifecycle() val state by viewModel.uiState.collectAsStateWithLifecycle()
@ -73,12 +71,7 @@ fun StatsScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Writing Statistics") }, title = { Text("Statistics") }
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
}
}
) )
} }
) { padding -> ) { padding ->