@@ -2,89 +2,102 @@ package com.m3u.smartphone.ui
22
33import android.app.ActivityOptions
44import android.content.Intent
5- import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
5+ import androidx.activity.compose.BackHandler
66import androidx.compose.foundation.layout.Arrangement
7+ import androidx.compose.foundation.layout.Column
78import androidx.compose.foundation.layout.Row
8- import androidx.compose.foundation.layout.fillMaxSize
9+ import androidx.compose.foundation.layout.WindowInsets
10+ import androidx.compose.foundation.layout.asPaddingValues
911import androidx.compose.foundation.layout.fillMaxWidth
12+ import androidx.compose.foundation.layout.ime
1013import androidx.compose.foundation.layout.padding
14+ import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
15+ import androidx.compose.foundation.text.input.rememberTextFieldState
16+ import androidx.compose.material.icons.Icons
17+ import androidx.compose.material.icons.automirrored.filled.ArrowBack
18+ import androidx.compose.material.icons.filled.MoreVert
19+ import androidx.compose.material.icons.filled.Search
20+ import androidx.compose.material3.ExpandedFullScreenSearchBar
21+ import androidx.compose.material3.Icon
22+ import androidx.compose.material3.IconButton
23+ import androidx.compose.material3.SearchBarDefaults
24+ import androidx.compose.material3.SearchBarValue
25+ import androidx.compose.material3.Text
26+ import androidx.compose.material3.TopSearchBar
27+ import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
28+ import androidx.compose.material3.rememberSearchBarState
1129import androidx.compose.runtime.Composable
1230import androidx.compose.runtime.derivedStateOf
1331import androidx.compose.runtime.getValue
1432import androidx.compose.runtime.remember
33+ import androidx.compose.runtime.rememberCoroutineScope
1534import androidx.compose.ui.Alignment
1635import androidx.compose.ui.Modifier
1736import androidx.compose.ui.platform.LocalContext
37+ import androidx.compose.ui.res.stringResource
1838import androidx.hilt.navigation.compose.hiltViewModel
1939import androidx.navigation.NavHostController
2040import androidx.navigation.compose.currentBackStackEntryAsState
2141import androidx.navigation.compose.rememberNavController
2242import androidx.navigation.navOptions
43+ import androidx.paging.PagingData
2344import com.m3u.core.architecture.preferences.PreferencesKeys
2445import com.m3u.core.architecture.preferences.preferenceOf
46+ import com.m3u.data.database.model.Channel
47+ import com.m3u.data.service.MediaCommand
2548import com.m3u.smartphone.ui.business.channel.PlayerActivity
49+ import com.m3u.smartphone.ui.business.playlist.components.ChannelGallery
2650import com.m3u.smartphone.ui.common.AppNavHost
27- import com.m3u.smartphone.ui.common.Scaffold
51+ import com.m3u.smartphone.ui.common.helper.LocalHelper
2852import com.m3u.smartphone.ui.material.components.Destination
2953import com.m3u.smartphone.ui.material.components.SnackHost
3054import com.m3u.smartphone.ui.material.model.LocalSpacing
55+ import kotlinx.coroutines.flow.Flow
56+ import kotlinx.coroutines.launch
3157
3258@Composable
3359fun App (
3460 modifier : Modifier = Modifier ,
3561 viewModel : AppViewModel = hiltViewModel(),
3662) {
37- val onBackPressedDispatcher = checkNotNull(
38- LocalOnBackPressedDispatcherOwner .current
39- ).onBackPressedDispatcher
40-
4163 val navController = rememberNavController()
42- val entry by navController.currentBackStackEntryAsState()
43-
44- val shouldDispatchBackStack by remember {
45- derivedStateOf {
46- with (entry) {
47- this != null && destination.route in Destination .Root .entries.map { it.name }
48- }
49- }
50- }
51-
52- val onBackPressed: (() -> Unit ) = {
53- onBackPressedDispatcher.onBackPressed()
54- }
5564
5665 AppImpl (
5766 navController = navController,
58- onBackPressed = onBackPressed.takeUnless { shouldDispatchBackStack },
59- onDismissRequest = {
60- viewModel.code = " "
61- viewModel.isConnectSheetVisible = false
62- },
67+ channels = viewModel.channels,
6368 modifier = modifier
6469 )
6570}
6671
6772@Composable
6873private fun AppImpl (
6974 navController : NavHostController ,
70- onBackPressed : (() -> Unit )? ,
71- onDismissRequest : () -> Unit ,
75+ channels : Flow <PagingData <Channel >>,
7276 modifier : Modifier = Modifier
7377) {
7478 val context = LocalContext .current
7579 val spacing = LocalSpacing .current
80+ val helper = LocalHelper .current
7681
7782 val zappingMode by preferenceOf(PreferencesKeys .ZAPPING_MODE )
7883 val remoteControl by preferenceOf(PreferencesKeys .REMOTE_CONTROL )
7984
8085 val entry by navController.currentBackStackEntryAsState()
8186
82- val rootDestination by remember {
87+ val currentDestination by remember {
8388 derivedStateOf {
84- Destination .Root . of(entry?.destination?.route)
89+ Destination .of(entry?.destination?.route)
8590 }
8691 }
8792
93+ val navigateToDestination = { destination: Destination ->
94+ navController.navigate(destination.name, navOptions {
95+ popUpTo(destination.name) {
96+ inclusive = true
97+ }
98+ })
99+ }
100+
88101 val navigateToChannel: () -> Unit = {
89102 if (! zappingMode || ! PlayerActivity .isInPipMode) {
90103 val options = ActivityOptions .makeCustomAnimation(
@@ -99,37 +112,110 @@ private fun AppImpl(
99112 }
100113 }
101114
102- Scaffold (
103- rootDestination = rootDestination,
104- onBackPressed = onBackPressed,
105- navigateToChannel = navigateToChannel,
106- navigateToRootDestination = {
107- navController.navigate(it.name, navOptions {
108- popUpTo(it.name) {
109- inclusive = true
110- }
111- })
115+ NavigationSuiteScaffold (
116+ navigationSuiteItems = {
117+ Destination .entries.forEach { destination ->
118+ val isSelected = destination == currentDestination
119+ item(
120+ icon = {
121+ Icon (
122+ imageVector = when {
123+ isSelected -> destination.selectedIcon
124+ else -> destination.unselectedIcon
125+ },
126+ contentDescription = stringResource(destination.iconTextId)
127+ )
128+ },
129+ label = {
130+ Text (stringResource(destination.iconTextId))
131+ },
132+ selected = isSelected,
133+ onClick = { navigateToDestination(destination) },
134+ alwaysShowLabel = false
135+ )
136+ }
112137 },
113- modifier = modifier.fillMaxSize()
114- ) { contentPadding ->
115- AppNavHost (
116- navController = navController,
117- navigateToRootDestination = { navController.navigate(it.name) },
118- navigateToChannel = navigateToChannel,
119- contentPadding = contentPadding,
120- modifier = Modifier .fillMaxSize()
121- )
122- // snack-host area
123- Row (
124- horizontalArrangement = Arrangement .spacedBy(spacing.small, Alignment .End ),
125- verticalAlignment = Alignment .Bottom ,
126- modifier = Modifier
127- .fillMaxWidth()
128- .align(Alignment .BottomCenter )
129- .padding(contentPadding)
130- .padding(spacing.medium)
131- ) {
132- SnackHost (Modifier .weight(1f ))
138+ modifier = modifier
139+ ) {
140+ Column {
141+ val coroutineScope = rememberCoroutineScope()
142+ val searchBarState = rememberSearchBarState()
143+ val textFieldState = rememberTextFieldState()
144+ val inputField = @Composable {
145+ SearchBarDefaults .InputField (
146+ searchBarState = searchBarState,
147+ textFieldState = textFieldState,
148+ onSearch = { coroutineScope.launch { searchBarState.animateToCollapsed() } },
149+ placeholder = { Text (" Search..." ) },
150+ leadingIcon = {
151+ if (searchBarState.currentValue == SearchBarValue .Expanded ) {
152+ IconButton (
153+ onClick = { coroutineScope.launch { searchBarState.animateToCollapsed() } }
154+ ) {
155+ Icon (
156+ Icons .AutoMirrored .Default .ArrowBack ,
157+ contentDescription = " Back"
158+ )
159+ }
160+ } else {
161+ Icon (Icons .Default .Search , contentDescription = null )
162+ }
163+ },
164+ trailingIcon = { Icon (Icons .Default .MoreVert , contentDescription = null ) },
165+ )
166+ }
167+ TopSearchBar (
168+ state = searchBarState,
169+ inputField = inputField
170+ )
171+ ExpandedFullScreenSearchBar (
172+ inputField = inputField,
173+ state = searchBarState
174+ ) {
175+ BackHandler {
176+ coroutineScope.launch {
177+ searchBarState.animateToCollapsed()
178+ }
179+ }
180+ val state = rememberLazyStaggeredGridState()
181+ ChannelGallery (
182+ state = state,
183+ rowCount = 1 ,
184+ channels = channels,
185+ zapping = null ,
186+ recently = false ,
187+ isVodOrSeriesPlaylist = false ,
188+ onClick = { channel ->
189+ coroutineScope.launch {
190+ helper.play(MediaCommand .Common (channel.id))
191+ navigateToChannel()
192+ }
193+ },
194+ onLongClick = {},
195+ getProgrammeCurrently = { null },
196+ reloadThumbnail = { null },
197+ syncThumbnail = { null },
198+ contentPadding = WindowInsets .ime.asPaddingValues()
199+ )
200+ }
201+ AppNavHost (
202+ navController = navController,
203+ navigateToDestination = { navController.navigate(it.name) },
204+ navigateToChannel = navigateToChannel,
205+ modifier = Modifier
206+ .fillMaxWidth()
207+ .weight(1f )
208+ )
209+ // snack-host area
210+ Row (
211+ horizontalArrangement = Arrangement .spacedBy(spacing.small, Alignment .End ),
212+ verticalAlignment = Alignment .Bottom ,
213+ modifier = Modifier
214+ .fillMaxWidth()
215+ .padding(spacing.medium)
216+ ) {
217+ SnackHost (Modifier .weight(1f ))
218+ }
133219 }
134220 }
135221}
0 commit comments