@@ -9,20 +9,43 @@ import androidx.activity.compose.setContent
99import androidx.activity.enableEdgeToEdge
1010import androidx.compose.animation.EnterTransition
1111import androidx.compose.animation.ExitTransition
12+ import androidx.compose.foundation.interaction.MutableInteractionSource
13+ import androidx.compose.foundation.interaction.PressInteraction
1214import androidx.compose.foundation.layout.Box
15+ import androidx.compose.foundation.layout.Row
16+ import androidx.compose.foundation.layout.WindowInsets
17+ import androidx.compose.foundation.layout.asPaddingValues
18+ import androidx.compose.foundation.layout.height
19+ import androidx.compose.foundation.layout.navigationBars
1320import androidx.compose.foundation.layout.padding
1421import androidx.compose.foundation.layout.size
22+ import androidx.compose.foundation.layout.width
23+ import androidx.compose.foundation.shape.CircleShape
24+ import androidx.compose.material.icons.Icons
25+ import androidx.compose.material.icons.outlined.UnfoldMore
26+ import androidx.compose.material3.ExperimentalMaterial3Api
1527import androidx.compose.material3.Icon
1628import androidx.compose.material3.MaterialTheme
29+ import androidx.compose.material3.ModalBottomSheet
1730import androidx.compose.material3.NavigationBar
1831import androidx.compose.material3.NavigationBarItem
1932import androidx.compose.material3.NavigationBarItemDefaults
2033import androidx.compose.material3.Scaffold
34+ import androidx.compose.material3.rememberModalBottomSheetState
2135import androidx.compose.runtime.Composable
2236import androidx.compose.runtime.LaunchedEffect
2337import androidx.compose.runtime.getValue
38+ import androidx.compose.runtime.mutableStateOf
39+ import androidx.compose.runtime.remember
40+ import androidx.compose.runtime.rememberCoroutineScope
41+ import androidx.compose.runtime.setValue
42+ import androidx.compose.ui.Alignment
2443import androidx.compose.ui.Modifier
44+ import androidx.compose.ui.draw.clip
2545import androidx.compose.ui.graphics.Color
46+ import androidx.compose.ui.res.painterResource
47+ import androidx.compose.ui.res.stringResource
48+ import androidx.compose.ui.unit.Density
2649import androidx.compose.ui.unit.dp
2750import androidx.core.view.WindowCompat
2851import androidx.navigation.NavHostController
@@ -31,6 +54,7 @@ import androidx.navigation.compose.composable
3154import androidx.navigation.compose.currentBackStackEntryAsState
3255import androidx.navigation.compose.rememberNavController
3356import androidx.navigation.navArgument
57+ import coil.compose.AsyncImage
3458import com.daniebeler.pfpixelix.common.Destinations
3559import com.daniebeler.pfpixelix.di.HostSelectionInterceptorInterface
3660import com.daniebeler.pfpixelix.domain.model.LoginData
@@ -43,12 +67,14 @@ import com.daniebeler.pfpixelix.ui.composables.direct_messages.chat.ChatComposab
4367import com.daniebeler.pfpixelix.ui.composables.direct_messages.conversations.ConversationsComposable
4468import com.daniebeler.pfpixelix.ui.composables.edit_post.EditPostComposable
4569import com.daniebeler.pfpixelix.ui.composables.edit_profile.EditProfileComposable
70+ import com.daniebeler.pfpixelix.ui.composables.explore.ExploreComposable
4671import com.daniebeler.pfpixelix.ui.composables.followers.FollowersMainComposable
72+ import com.daniebeler.pfpixelix.ui.composables.mention.MentionComposable
4773import com.daniebeler.pfpixelix.ui.composables.newpost.NewPostComposable
4874import com.daniebeler.pfpixelix.ui.composables.notifications.NotificationsComposable
4975import com.daniebeler.pfpixelix.ui.composables.profile.other_profile.OtherProfileComposable
76+ import com.daniebeler.pfpixelix.ui.composables.profile.own_profile.AccountSwitchBottomSheet
5077import com.daniebeler.pfpixelix.ui.composables.profile.own_profile.OwnProfileComposable
51- import com.daniebeler.pfpixelix.ui.composables.search.SearchComposable
5278import com.daniebeler.pfpixelix.ui.composables.settings.about_instance.AboutInstanceComposable
5379import com.daniebeler.pfpixelix.ui.composables.settings.about_pixelix.AboutPixelixComposable
5480import com.daniebeler.pfpixelix.ui.composables.settings.blocked_accounts.BlockedAccountsComposable
@@ -60,11 +86,13 @@ import com.daniebeler.pfpixelix.ui.composables.settings.muted_accounts.MutedAcco
6086import com.daniebeler.pfpixelix.ui.composables.settings.preferences.PreferencesComposable
6187import com.daniebeler.pfpixelix.ui.composables.single_post.SinglePostComposable
6288import com.daniebeler.pfpixelix.ui.composables.timelines.hashtag_timeline.HashtagTimelineComposable
63- import com.daniebeler.pfpixelix.ui.composables.trending.TrendingComposable
6489import com.daniebeler.pfpixelix.ui.theme.PixelixTheme
6590import com.daniebeler.pfpixelix.utils.Navigate
6691import dagger.hilt.android.AndroidEntryPoint
92+ import kotlinx.coroutines.cancelChildren
93+ import kotlinx.coroutines.delay
6794import kotlinx.coroutines.flow.firstOrNull
95+ import kotlinx.coroutines.launch
6896import kotlinx.coroutines.runBlocking
6997import javax.inject.Inject
7098
@@ -91,12 +119,13 @@ class MainActivity : ComponentActivity() {
91119 }
92120 }
93121
122+ @OptIn(ExperimentalMaterial3Api ::class )
94123 override fun onCreate (savedInstanceState : Bundle ? ) {
95124
96125 super .onCreate(savedInstanceState)
97126 enableEdgeToEdge()
98127 WindowCompat .setDecorFitsSystemWindows(window, false )
99-
128+ var avatar = " "
100129 runBlocking {
101130 val loginData: LoginData ? = currentLoginDataUseCase()
102131 if (loginData == null || loginData.accessToken.isBlank() || loginData.baseUrl.isBlank()) {
@@ -119,23 +148,30 @@ class MainActivity : ComponentActivity() {
119148 )
120149 )
121150 }
151+ avatar = loginData.avatar
122152 }
123153 }
124154
125155 setContent {
156+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true )
157+ var showAccountSwitchBottomSheet by remember { mutableStateOf(false ) }
158+
126159 PixelixTheme {
127160 val navController: NavHostController = rememberNavController()
128161
129- Scaffold (bottomBar = {
130- BottomBar (navController = navController)
162+ Scaffold (contentWindowInsets = WindowInsets (0 .dp), bottomBar = {
163+ BottomBar (
164+ navController = navController,
165+ avatar = avatar,
166+ openAccountSwitchBottomSheet = { showAccountSwitchBottomSheet = true },
167+ context = this
168+ )
131169 }) { paddingValues ->
132170 Box (
133171 modifier = Modifier .padding(paddingValues)
134172 ) {
135-
136-
137173 NavigationGraph (
138- navController = navController
174+ navController = navController,
139175 )
140176 val destination = intent.extras?.getString(KEY_DESTINATION ) ? : " "
141177 if (destination.isNotBlank()) {
@@ -175,11 +211,23 @@ class MainActivity : ComponentActivity() {
175211
176212
177213 }
214+ if (showAccountSwitchBottomSheet) {
215+ ModalBottomSheet (
216+ onDismissRequest = {
217+ showAccountSwitchBottomSheet = false
218+ }, sheetState = sheetState
219+ ) {
220+ AccountSwitchBottomSheet (closeBottomSheet = {
221+ showAccountSwitchBottomSheet = false
222+ }, null )
223+ }
224+ }
178225 }
179226 }
180227 }
181228}
182229
230+
183231fun updateAuthToV2 (context : Context , baseUrl : String , accessToken : String ) {
184232 val intent = Intent (context, LoginActivity ::class .java)
185233 intent.putExtra(" base_url" , baseUrl)
@@ -207,10 +255,6 @@ fun NavigationGraph(navController: NavHostController) {
207255 composable(Destinations .HomeScreen .route) {
208256 HomeComposable (navController)
209257 }
210- composable(Destinations .TrendingScreen .route) { navBackStackEntry ->
211- val page = navBackStackEntry.arguments?.getString(" page" ) ? : " posts"
212- TrendingComposable (navController, page)
213- }
214258
215259 composable(Destinations .NotificationsScreen .route) {
216260 NotificationsComposable (navController)
@@ -329,7 +373,7 @@ fun NavigationGraph(navController: NavHostController) {
329373 }
330374
331375 composable(Destinations .Search .route) {
332- SearchComposable (navController)
376+ ExploreComposable (navController)
333377 }
334378
335379 composable(Destinations .Conversation .route) {
@@ -342,37 +386,90 @@ fun NavigationGraph(navController: NavHostController) {
342386 ChatComposable (navController = navController, accountId = id)
343387 }
344388 }
389+
390+ composable(Destinations .Mention .route) { navBackStackEntry ->
391+ val mentionId = navBackStackEntry.arguments?.getString(" mentionid" )
392+ mentionId?.let { id ->
393+ MentionComposable (navController = navController, mentionId = id)
394+ }
395+ }
345396 }
346397}
347398
348399@Composable
349- fun BottomBar (navController : NavHostController ) {
400+ fun BottomBar (
401+ navController : NavHostController ,
402+ avatar : String ,
403+ openAccountSwitchBottomSheet : () -> Unit ,
404+ context : Context
405+ ) {
350406 val screens = listOf (
351407 Destinations .HomeScreen ,
352408 Destinations .Search ,
353- Destinations .TrendingScreen ,
354409 Destinations .NotificationsScreen ,
355410 Destinations .OwnProfile
356411 )
412+ val systemNavigationBarHeight =
413+ WindowInsets .navigationBars.asPaddingValues(Density (context)).calculateBottomPadding()
357414
358- NavigationBar {
415+ NavigationBar (
416+ modifier = Modifier .height(60 .dp + systemNavigationBarHeight)
417+ ) {
359418 val navBackStackEntry by navController.currentBackStackEntryAsState()
360419 val currentRoute = navBackStackEntry?.destination?.route
361-
362420 screens.forEach { screen ->
421+ val interactionSource = remember { MutableInteractionSource () }
422+ val coroutineScope = rememberCoroutineScope()
423+ var isLongPress by remember { mutableStateOf(false ) }
424+
425+ LaunchedEffect (interactionSource) {
426+ interactionSource.interactions.collect { interaction ->
427+ when (interaction) {
428+ is PressInteraction .Press -> {
429+ isLongPress = false // Reset flag before starting detection
430+ coroutineScope.launch {
431+ delay(500L ) // Long-press threshold
432+ if (screen.route == Destinations .OwnProfile .route) {
433+ openAccountSwitchBottomSheet()
434+ }
435+ isLongPress = true
436+ }
437+ }
363438
439+ is PressInteraction .Release , is PressInteraction .Cancel -> {
440+ coroutineScope.coroutineContext.cancelChildren()
441+ }
442+ }
443+ }
444+ }
364445 NavigationBarItem (icon = {
365- if (currentRoute == screen.route) {
446+ if (screen.route == Destinations .OwnProfile .route && avatar.isNotBlank()) {
447+ Row (verticalAlignment = Alignment .CenterVertically ) {
448+ AsyncImage (
449+ model = avatar,
450+ error = painterResource(id = R .drawable.default_avatar),
451+ contentDescription = " " ,
452+ modifier = Modifier
453+ .height(30 .dp)
454+ .width(30 .dp)
455+ .clip(CircleShape )
456+ )
457+ Icon (
458+ Icons .Outlined .UnfoldMore ,
459+ contentDescription = " long press to switch account"
460+ )
461+ }
462+ } else if (currentRoute == screen.route) {
366463 Icon (
367464 imageVector = screen.activeIcon!! ,
368- modifier = Modifier .size(32 .dp),
369- contentDescription = " "
465+ modifier = Modifier .size(30 .dp),
466+ contentDescription = stringResource(screen.label)
370467 )
371468 } else {
372469 Icon (
373470 imageVector = screen.icon!! ,
374- modifier = Modifier .size(32 .dp),
375- contentDescription = " "
471+ modifier = Modifier .size(30 .dp),
472+ contentDescription = stringResource(screen.label)
376473 )
377474 }
378475 },
@@ -381,8 +478,11 @@ fun BottomBar(navController: NavHostController) {
381478 selectedIconColor = MaterialTheme .colorScheme.inverseSurface,
382479 indicatorColor = Color .Transparent
383480 ),
481+ interactionSource = interactionSource,
384482 onClick = {
385- Navigate .navigateWithPopUp(screen.route, navController)
483+ if (! isLongPress) {
484+ Navigate .navigateWithPopUp(screen.route, navController)
485+ }
386486 })
387487 }
388488 }
0 commit comments