diff --git a/data/src/main/java/com/m3u/data/database/dao/StreamDao.kt b/data/src/main/java/com/m3u/data/database/dao/StreamDao.kt index 92ecfbea3..83e25721b 100644 --- a/data/src/main/java/com/m3u/data/database/dao/StreamDao.kt +++ b/data/src/main/java/com/m3u/data/database/dao/StreamDao.kt @@ -17,6 +17,9 @@ internal interface StreamDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertOrReplaceAll(vararg streams: Stream) + @Query("SELECT DISTINCT `group` FROM streams WHERE playlistUrl = :playlistUrl") + suspend fun getCategoriesByPlaylistUrl(playlistUrl: String): List + @Delete suspend fun delete(stream: Stream) @@ -77,27 +80,38 @@ internal interface StreamDao { @Query("SELECT * FROM streams WHERE hidden = 1") fun observeAllHidden(): Flow> - @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%'") + @Query( + """ + SELECT * FROM streams + WHERE playlistUrl = :url + AND title LIKE '%'||:query||'%' + AND `group` = :category + """ + ) fun pagingAllByPlaylistUrl( url: String, + category: String, query: String ): PagingSource - @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' ORDER BY title ASC") + @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' AND `group` = :category ORDER BY title ASC") fun pagingAllByPlaylistUrlAsc( url: String, + category: String, query: String ): PagingSource - @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' ORDER BY title DESC") + @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' AND `group` = :category ORDER BY title DESC") fun pagingAllByPlaylistUrlDesc( url: String, + category: String, query: String ): PagingSource - @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' ORDER BY seen DESC") + @Query("SELECT * FROM streams WHERE playlistUrl = :url AND title LIKE '%'||:query||'%' AND `group` = :category ORDER BY seen DESC") fun pagingAllByPlaylistUrlRecently( url: String, + category: String, query: String ): PagingSource diff --git a/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepository.kt b/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepository.kt index ff8b7d60d..1778f3a18 100644 --- a/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepository.kt +++ b/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepository.kt @@ -14,6 +14,7 @@ interface PlaylistRepository { fun observeAllEpgs(): Flow> fun observePlaylistUrls(): Flow> suspend fun get(url: String): Playlist? + suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String): List fun observe(url: String): Flow fun observeWithStreams(url: String): Flow suspend fun getWithStreams(url: String): PlaylistWithStreams? diff --git a/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepositoryImpl.kt b/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepositoryImpl.kt index 2fe382339..fd54c166b 100644 --- a/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepositoryImpl.kt +++ b/data/src/main/java/com/m3u/data/repository/playlist/PlaylistRepositoryImpl.kt @@ -514,6 +514,15 @@ internal class PlaylistRepositoryImpl @Inject constructor( playlistDao.getByUrl(url) } + override suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String): List = + logger.execute { + val playlist = playlistDao.getByUrl(url) + val hiddenCategories = playlist?.hiddenCategories ?: emptyList() + streamDao + .getCategoriesByPlaylistUrl(url) + .filterNot { it in hiddenCategories } + } ?: emptyList() + override suspend fun unsubscribe(url: String): Playlist? = logger.execute { val playlist = playlistDao.getByUrl(url) streamDao.deleteByPlaylistUrl(url) diff --git a/data/src/main/java/com/m3u/data/repository/stream/StreamRepository.kt b/data/src/main/java/com/m3u/data/repository/stream/StreamRepository.kt index 3c7494a50..04a7d600c 100644 --- a/data/src/main/java/com/m3u/data/repository/stream/StreamRepository.kt +++ b/data/src/main/java/com/m3u/data/repository/stream/StreamRepository.kt @@ -11,6 +11,7 @@ interface StreamRepository { fun observeAllByPlaylistUrl(playlistUrl: String): Flow> fun pagingAllByPlaylistUrl( url: String, + category: String, query: String, sort: Sort ): PagingSource diff --git a/data/src/main/java/com/m3u/data/repository/stream/StreamRepositoryImpl.kt b/data/src/main/java/com/m3u/data/repository/stream/StreamRepositoryImpl.kt index 69a8a0635..569a07c48 100644 --- a/data/src/main/java/com/m3u/data/repository/stream/StreamRepositoryImpl.kt +++ b/data/src/main/java/com/m3u/data/repository/stream/StreamRepositoryImpl.kt @@ -32,13 +32,14 @@ internal class StreamRepositoryImpl @Inject constructor( override fun pagingAllByPlaylistUrl( url: String, + category: String, query: String, sort: Sort ): PagingSource = when (sort) { - Sort.UNSPECIFIED -> streamDao.pagingAllByPlaylistUrl(url, query) - Sort.ASC -> streamDao.pagingAllByPlaylistUrlAsc(url, query) - Sort.DESC -> streamDao.pagingAllByPlaylistUrlDesc(url, query) - Sort.RECENTLY -> streamDao.pagingAllByPlaylistUrlRecently(url, query) + Sort.UNSPECIFIED -> streamDao.pagingAllByPlaylistUrl(url, category, query) + Sort.ASC -> streamDao.pagingAllByPlaylistUrlAsc(url, category, query) + Sort.DESC -> streamDao.pagingAllByPlaylistUrlDesc(url, category, query) + Sort.RECENTLY -> streamDao.pagingAllByPlaylistUrlRecently(url, category, query) } override suspend fun get(id: Int): Stream? = logger.execute { diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistScreen.kt b/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistScreen.kt index 31789cce6..be00236f4 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistScreen.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistScreen.kt @@ -16,7 +16,6 @@ import android.provider.Settings import android.view.KeyEvent import androidx.activity.compose.BackHandler 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.fillMaxSize @@ -40,8 +39,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.permissions.rememberPermissionState import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.util.basic.title @@ -89,27 +86,27 @@ internal fun PlaylistRoute( val zapping by viewModel.zapping.collectAsStateWithLifecycle() val playlistUrl by viewModel.playlistUrl.collectAsStateWithLifecycle() val playlist by viewModel.playlist.collectAsStateWithLifecycle() - val categories by viewModel.categories.collectAsStateWithLifecycle() - val streamPaged = viewModel.streamPaged.collectAsLazyPagingItems() + + val channels by viewModel.channels.collectAsStateWithLifecycle() + val episodes by viewModel.episodes.collectAsStateWithLifecycle() val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle() val refreshing by viewModel.subscribingOrRefreshing.collectAsStateWithLifecycle() val series by viewModel.series.collectAsStateWithLifecycle() val isSeriesPlaylist by remember { - derivedStateOf { - playlist?.type in Playlist.SERIES_TYPES - } + derivedStateOf { playlist?.type in Playlist.SERIES_TYPES } } val isVodPlaylist by remember { - derivedStateOf { - playlist?.type in Playlist.VOD_TYPES - } + derivedStateOf { playlist?.type in Playlist.VOD_TYPES } } - val sorts = viewModel.sorts + val sorts = Sort.entries val sort by viewModel.sort.collectAsStateWithLifecycle() + val query by viewModel.query.collectAsStateWithLifecycle() + val scrollUp by viewModel.scrollUp.collectAsStateWithLifecycle() + val writeExternalPermission = rememberPermissionState( Manifest.permission.WRITE_EXTERNAL_STORAGE ) @@ -131,109 +128,106 @@ internal fun PlaylistRoute( } } - BackHandler(viewModel.query.isNotEmpty()) { - viewModel.query = "" + BackHandler(query.isNotEmpty()) { + viewModel.query.value = "" } Background { - Box { - PlaylistScreen( - title = playlist?.title.orEmpty(), - query = viewModel.query, - onQuery = { viewModel.query = it }, - rowCount = preferences.rowCount, - zapping = zapping, - categories = categories, - streamPaged = streamPaged, - pinnedCategories = pinnedCategories, - onPinOrUnpinCategory = { viewModel.pinOrUnpinCategory(it) }, - onHideCategory = { viewModel.hideCategory(it) }, - scrollUp = viewModel.scrollUp, - sorts = sorts, - sort = sort, - onSort = { viewModel.sort(it) }, - onStream = { stream -> - if (!isSeriesPlaylist) { - coroutineScope.launch { - helper.play(MediaCommand.Live(stream.id)) - navigateToStream() - } - } else { - viewModel.series.value = stream + PlaylistScreen( + title = playlist?.title.orEmpty(), + query = query, + onQuery = { viewModel.query.value = it }, + rowCount = preferences.rowCount, + zapping = zapping, + channels = channels, + pinnedCategories = pinnedCategories, + onPinOrUnpinCategory = { viewModel.pinOrUnpinCategory(it) }, + onHideCategory = { viewModel.hideCategory(it) }, + scrollUp = scrollUp, + sorts = sorts, + sort = sort, + onSort = { viewModel.sort(it) }, + onStream = { stream -> + if (!isSeriesPlaylist) { + coroutineScope.launch { + helper.play(MediaCommand.Live(stream.id)) + navigateToStream() } - }, - onScrollUp = { viewModel.scrollUp = eventOf(Unit) }, - refreshing = refreshing, - onRefresh = { - postNotificationPermission.checkPermissionOrRationale( - showRationale = { - val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) - .apply { - putExtra( - Settings.EXTRA_APP_PACKAGE, - helper.activityContext.packageName - ) - } - helper.activityContext.startActivity(intent) - }, - block = { - viewModel.refresh() - } - ) - }, - contentPadding = contentPadding, - favourite = viewModel::favourite, - hide = viewModel::hide, - savePicture = { id -> - writeExternalPermission.checkPermissionOrRationale { - viewModel.savePicture(id) + } else { + viewModel.series.value = stream + } + }, + onScrollUp = { viewModel.scrollUp.value = eventOf(Unit) }, + refreshing = refreshing, + onRefresh = { + postNotificationPermission.checkPermissionOrRationale( + showRationale = { + val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .apply { + putExtra( + Settings.EXTRA_APP_PACKAGE, + helper.activityContext.packageName + ) + } + helper.activityContext.startActivity(intent) + }, + block = { + viewModel.refresh() } - }, - createShortcut = { id -> viewModel.createShortcut(context, id) }, - createTvRecommend = { id -> viewModel.createTvRecommend(context, id) }, - isVodPlaylist = isVodPlaylist, - isSeriesPlaylist = isSeriesPlaylist, - getProgrammeCurrently = { channelId -> viewModel.getProgrammeCurrently(channelId) }, - modifier = Modifier - .fillMaxSize() - .thenIf(!tv && preferences.godMode) { - Modifier.interceptVolumeEvent { event -> - preferences.rowCount = when (event) { - KeyEvent.KEYCODE_VOLUME_UP -> - (preferences.rowCount - 1).coerceAtLeast(1) + ) + }, + contentPadding = contentPadding, + favourite = viewModel::favourite, + hide = viewModel::hide, + savePicture = { id -> + writeExternalPermission.checkPermissionOrRationale { + viewModel.savePicture(id) + } + }, + createShortcut = { id -> viewModel.createShortcut(context, id) }, + createTvRecommend = { id -> viewModel.createTvRecommend(context, id) }, + isVodPlaylist = isVodPlaylist, + isSeriesPlaylist = isSeriesPlaylist, + getProgrammeCurrently = { channelId -> viewModel.getProgrammeCurrently(channelId) }, + modifier = Modifier + .fillMaxSize() + .thenIf(!tv && preferences.godMode) { + Modifier.interceptVolumeEvent { event -> + preferences.rowCount = when (event) { + KeyEvent.KEYCODE_VOLUME_UP -> + (preferences.rowCount - 1).coerceAtLeast(1) - KeyEvent.KEYCODE_VOLUME_DOWN -> - (preferences.rowCount + 1).coerceAtMost(2) + KeyEvent.KEYCODE_VOLUME_DOWN -> + (preferences.rowCount + 1).coerceAtMost(2) - else -> return@interceptVolumeEvent - } + else -> return@interceptVolumeEvent } } - .then(modifier) - ) + } + .then(modifier) + ) - if (isSeriesPlaylist) { - EpisodesBottomSheet( - series = series, - episodes = episodes, - onEpisodeClick = { episode -> - coroutineScope.launch { - series?.let { - val input = MediaCommand.XtreamEpisode( - streamId = it.id, - episode = episode - ) - helper.play(input) - navigateToStream() - } + if (isSeriesPlaylist) { + EpisodesBottomSheet( + series = series, + episodes = episodes, + onEpisodeClick = { episode -> + coroutineScope.launch { + series?.let { + val input = MediaCommand.XtreamEpisode( + streamId = it.id, + episode = episode + ) + helper.play(input) + navigateToStream() } - }, - onRefresh = { viewModel.seriesReplay.value += 1 }, - onDismissRequest = { - viewModel.series.value = null } - ) - } + }, + onRefresh = { viewModel.seriesReplay.value += 1 }, + onDismissRequest = { + viewModel.series.value = null + } + ) } } } @@ -246,8 +240,7 @@ private fun PlaylistScreen( onQuery: (String) -> Unit, rowCount: Int, zapping: Stream?, - categories: List, - streamPaged: LazyPagingItems, + channels: List, pinnedCategories: List, onPinOrUnpinCategory: (String) -> Unit, onHideCategory: (String) -> Unit, @@ -298,8 +291,7 @@ private fun PlaylistScreen( val tv = isTelevision() if (!tv) { SmartphonePlaylistScreenImpl( - categories = categories, - streamPaged = streamPaged, + channels = channels, pinnedCategories = pinnedCategories, onPinOrUnpinCategory = onPinOrUnpinCategory, onHideCategory = onHideCategory, @@ -327,8 +319,7 @@ private fun PlaylistScreen( } else { TvPlaylistScreenImpl( title = title, - categories = categories, - streamPaged = streamPaged, + channels = channels, query = query, onQuery = onQuery, onStream = onStream, diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistViewModel.kt b/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistViewModel.kt index 7725d4fd2..4d2a2ad11 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistViewModel.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/PlaylistViewModel.kt @@ -1,14 +1,8 @@ package com.m3u.features.playlist -import android.annotation.SuppressLint import android.content.ComponentName -import android.content.ContentUris import android.content.Context import android.content.Intent -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -22,9 +16,6 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.filter -import androidx.tvprovider.media.tv.Channel -import androidx.tvprovider.media.tv.PreviewProgram -import androidx.tvprovider.media.tv.TvContractCompat import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.WorkQuery @@ -69,7 +60,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.datetime.Clock import javax.inject.Inject @@ -155,8 +145,6 @@ class PlaylistViewModel @Inject constructor( } } - internal var scrollUp: Event by mutableStateOf(handledEvent()) - internal fun savePicture(id: Int) { viewModelScope.launch { val stream = streamRepository.get(id) @@ -224,63 +212,9 @@ class PlaylistViewModel @Inject constructor( } } - @SuppressLint("RestrictedApi") - internal fun createTvRecommend( - context: Context, - id: Int - ) { - viewModelScope.launch(ioDispatcher) { - val stream = streamRepository.get(id) ?: return@launch - val logo = stream.cover?.let { mediaRepository.loadDrawable(it)?.toBitmap() } - val channel = Channel.Builder() - .setType(TvContractCompat.Channels.TYPE_PREVIEW) - .setDisplayName("M3U") - .setAppLinkIntent( - Intent(Intent.ACTION_VIEW).apply { - component = ComponentName.createRelative( - context, - Contracts.PLAYER_ACTIVITY - ) - putExtra(Contracts.PLAYER_SHORTCUT_STREAM_ID, stream.id) - } - ) - .build() - val channelUri = checkNotNull( - context.contentResolver.insert( - TvContractCompat.Channels.CONTENT_URI, - channel.toContentValues() - ) - ) - val channelId = ContentUris.parseId(channelUri) - val program = PreviewProgram.Builder() - .setChannelId(channelId) - .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP) - .setTitle("Title") - .setDescription("Program description") -// .setPosterArtUri(uri) -// .setIntentUri(uri) - .setInternalProviderId(stream.id.toString()) - .build() - val programUri = checkNotNull( - context.contentResolver.insert( - TvContractCompat.PreviewPrograms.CONTENT_URI, - program.toContentValues() - ) - ) - } + internal fun createTvRecommend(context: Context, id: Int) { } - internal var query: String by mutableStateOf("") - - private fun List.toCategories(): List = - groupBy { it.category } - .toList() - .map { (name, streams) -> Category(name, streams) } - - private fun List.toSingleCategory(): List = listOf( - Category("", toList()) - ) - internal suspend fun getProgrammeCurrently(channelId: String): Programme? { val playlist = playlist.value ?: return null val epgUrls = playlist.epgUrlsOrXtreamXmlUrl() @@ -293,36 +227,10 @@ class PlaylistViewModel @Inject constructor( ) } - private val unsorted: StateFlow> = combine( - playlistUrl.flatMapLatest { url -> - snapshotFlow { preferences.paging }.flatMapLatest { paging -> - if (paging) flow { } - else playlistRepository.observeWithStreams(url) - } - }, - snapshotFlow { query }, - ) { current, query -> - val hiddenCategories = current?.playlist?.hiddenCategories ?: emptyList() - current - ?.streams - ?.filter { - !it.hidden && it.category !in hiddenCategories && it.title.contains(query, true) - } - ?: emptyList() - } - .flowOn(ioDispatcher) - .stateIn( - scope = viewModelScope, - initialValue = emptyList(), - started = SharingStarted.WhileSubscribed(5_000L) - ) - - internal val sorts: List = Sort.entries - private val sortIndex: MutableStateFlow = MutableStateFlow(0) internal val sort: StateFlow = sortIndex - .map { sorts[it] } + .map { Sort.entries[it] } .stateIn( scope = viewModelScope, initialValue = Sort.UNSPECIFIED, @@ -330,79 +238,70 @@ class PlaylistViewModel @Inject constructor( ) internal fun sort(sort: Sort) { - sortIndex.update { sorts.indexOf(sort).coerceAtLeast(0) } + sortIndex.value = Sort.entries.indexOf(sort).coerceAtLeast(0) } - internal val streamPaged: Flow> = combine( - playlist, - snapshotFlow { query }, + internal val query = MutableStateFlow("") + internal val scrollUp: MutableStateFlow> = MutableStateFlow(handledEvent()) + + data class PagingStreamParameters( + val playlistUrl: String, + val query: String, + val sort: Sort, + val categories: List + ) + + data class Channel( + val category: String, + val streams: Flow> + ) + + internal val channels: StateFlow> = combine( + playlistUrl, + query, sort - ) { playlist, query, sort -> Triple(playlist, query, sort) } - .flatMapLatest { (playlist, query, sort) -> - Pager(PagingConfig(15)) { - // streamDao.pagingAllByPlaylistUrl - // streamDao.pagingAllByPlaylistUrlAsc - // streamDao.pagingAllByPlaylistUrlDesc - // streamDao.pagingAllByPlaylistUrlRecently - streamRepository.pagingAllByPlaylistUrl( - playlist?.url.orEmpty(), - query, - when (sort) { - Sort.UNSPECIFIED -> StreamRepository.Sort.UNSPECIFIED - Sort.ASC -> StreamRepository.Sort.ASC - Sort.DESC -> StreamRepository.Sort.DESC - Sort.RECENTLY -> StreamRepository.Sort.RECENTLY + ) { playlistUrl, query, sort -> + val categories = playlistRepository.getCategoriesByPlaylistUrlIgnoreHidden(playlistUrl) + PagingStreamParameters( + playlistUrl = playlistUrl, + query = query, + sort = sort, + categories = categories + ) + } + .map { (playlistUrl, query, sort, categories) -> + categories.map { category -> + Channel( + category = category, + streams = Pager(PagingConfig(15)) { + streamRepository.pagingAllByPlaylistUrl( + playlistUrl, + category, + query, + when (sort) { + Sort.UNSPECIFIED -> StreamRepository.Sort.UNSPECIFIED + Sort.ASC -> StreamRepository.Sort.ASC + Sort.DESC -> StreamRepository.Sort.DESC + Sort.RECENTLY -> StreamRepository.Sort.RECENTLY + } + ) } + .flow + .map { data -> data.filter { !it.hidden } } + .flowOn(ioDispatcher) + .cachedIn(viewModelScope) ) } - .flow - .cachedIn(viewModelScope) - } - .let { flow -> - combine( - flow, - playlist, - snapshotFlow { preferences.paging } - ) { pagingData, playlist, paging -> - if (!paging) return@combine PagingData.empty() - val hiddenCategories = playlist?.hiddenCategories ?: emptyList() - pagingData.filter { stream -> - !stream.hidden && stream.category !in hiddenCategories - } - } } - .flowOn(ioDispatcher) - - internal val pinnedCategories: StateFlow> = playlist - .map { it?.pinnedCategories ?: emptyList() } - - .flowOn(ioDispatcher) .stateIn( scope = viewModelScope, initialValue = emptyList(), started = SharingStarted.WhileSubscribed(5_000L) ) - internal val categories: StateFlow> = combine( - unsorted, - sort, - pinnedCategories - ) { all, sort, pinnedCategories -> - when (sort) { - Sort.ASC -> all.sortedWith( - compareBy(String.CASE_INSENSITIVE_ORDER) { it.title } - ).toSingleCategory() - - Sort.DESC -> all.sortedWith( - compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.title } - ).toSingleCategory() - - Sort.RECENTLY -> all.sortedByDescending { it.seen }.toSingleCategory() - Sort.UNSPECIFIED -> all.toCategories() - } - .sortedByDescending { it.name in pinnedCategories } + internal val pinnedCategories: StateFlow> = playlist + .map { it?.pinnedCategories ?: emptyList() } - } .flowOn(ioDispatcher) .stateIn( scope = viewModelScope, @@ -441,9 +340,3 @@ class PlaylistViewModel @Inject constructor( started = SharingStarted.Lazily ) } - -@Immutable -internal data class Category( - val name: String = "", - val streams: List = emptyList() -) \ No newline at end of file diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/components/PlaylistTabRow.kt b/features/playlist/src/main/java/com/m3u/features/playlist/components/PlaylistTabRow.kt index c38b75e6f..e61d5e246 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/components/PlaylistTabRow.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/components/PlaylistTabRow.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -43,15 +43,14 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp -import com.m3u.features.playlist.Category import com.m3u.material.components.IconButton import com.m3u.material.model.LocalSpacing @Composable internal fun PlaylistTabRow( - page: Int, - onPageChanged: (Int) -> Unit, - categories: List, + selectedCategory: String, + categories: List, + onCategoryChanged: (String) -> Unit, pinnedCategories: List, onPinOrUnpinCategory: (String) -> Unit, onHideCategory: (String) -> Unit, @@ -107,21 +106,21 @@ internal fun PlaylistTabRow( } } } - itemsIndexed(categories) { index, channel -> + items(categories) { category -> PlaylistTabRowItem( - name = channel.name, - selected = page == index, - pinned = channel.name in pinnedCategories, - focused = focusCategory == channel.name, - hasOtherFocused = focusCategory != null && focusCategory != channel.name, + name = category, + selected = category == selectedCategory, + pinned = category in pinnedCategories, + focused = category == focusCategory, + hasOtherFocused = focusCategory != null && focusCategory != category, onClick = { if (focusCategory == null) { - onPageChanged(index) + onCategoryChanged(category) } }, onLongClick = { - focusCategory = channel.name - onPageChanged(index) + focusCategory = category + onCategoryChanged(category) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) } ) diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/components/SmartphoneStreamGallery.kt b/features/playlist/src/main/java/com/m3u/features/playlist/components/SmartphoneStreamGallery.kt index 0da8ab03a..e6d365770 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/components/SmartphoneStreamGallery.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/components/SmartphoneStreamGallery.kt @@ -1,7 +1,5 @@ package com.m3u.features.playlist.components -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -11,47 +9,24 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan -import androidx.compose.foundation.lazy.staggeredgrid.items -import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.FlashOn -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.capitalize -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.paging.compose.LazyPagingItems -import androidx.tv.material3.Text +import androidx.paging.compose.collectAsLazyPagingItems import com.m3u.core.architecture.preferences.hiltPreferences -import com.m3u.core.util.basic.title import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Stream -import com.m3u.i18n.R.string +import com.m3u.features.playlist.PlaylistViewModel import com.m3u.material.components.CircularProgressIndicator -import com.m3u.material.components.Icon import com.m3u.material.ktx.plus import com.m3u.material.model.LocalSpacing -import kotlinx.coroutines.delay -import kotlin.time.Duration.Companion.seconds @Composable internal fun SmartphoneStreamGallery( state: LazyStaggeredGridState, rowCount: Int, - streams: List, - streamPaged: LazyPagingItems, + channel: PlaylistViewModel.Channel?, zapping: Stream?, recently: Boolean, isVodOrSeriesPlaylist: Boolean, @@ -70,13 +45,7 @@ internal fun SmartphoneStreamGallery( else -> rowCount } - val currentWithProgrammes by rememberUpdatedState(streams) - val isPagingTipShowing by produceState(false) { - value = if (!preferences.paging) { - delay(4.seconds) - currentWithProgrammes.isEmpty() - } else false - } + val streams = channel?.streams?.collectAsLazyPagingItems() LazyVerticalStaggeredGrid( state = state, @@ -86,101 +55,29 @@ internal fun SmartphoneStreamGallery( contentPadding = PaddingValues(spacing.medium) + contentPadding, modifier = modifier.fillMaxSize() ) { - item(span = StaggeredGridItemSpan.FullLine) { - PagingTips(isPagingTipShowing) - } - if (!preferences.paging) { - items( - items = currentWithProgrammes, - key = { stream -> stream.id } - ) { stream -> + items(streams?.itemCount ?: 0) { index -> + val stream = streams?.get(index) + if (stream != null) { SmartphoneStreamItem( stream = stream, getProgrammeCurrently = { getProgrammeCurrently(stream.channelId.orEmpty()) }, recently = recently, - zapping = zapping?.id == stream.id, + zapping = zapping == stream, isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, onClick = { onClick(stream) }, onLongClick = { onLongClick(stream) }, modifier = Modifier.fillMaxWidth() ) - } - } else { - items(streamPaged.itemCount) { - val stream = streamPaged[it] - if (stream != null) { - SmartphoneStreamItem( - stream = stream, - getProgrammeCurrently = { getProgrammeCurrently(stream.channelId.orEmpty()) }, - recently = recently, - zapping = zapping == stream, - isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, - onClick = { onClick(stream) }, - onLongClick = { onLongClick(stream) }, - modifier = Modifier.fillMaxWidth() - ) - } else { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f) - ) { - CircularProgressIndicator() - } + } else { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + ) { + CircularProgressIndicator() } } } } } - -@Composable -private fun PagingTips( - isPagingTipShowing: Boolean, - modifier: Modifier = Modifier -) { - val preferences = hiltPreferences() - val spacing = LocalSpacing.current - AnimatedVisibility( - visible = isPagingTipShowing, - modifier = modifier - ) { - ElevatedCard( - shape = AbsoluteRoundedCornerShape(spacing.medium) - ) { - ListItem( - headlineContent = { - Text( - text = stringResource(string.feat_setting_paging).title(), - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - supportingContent = { - Text( - text = stringResource(string.feat_setting_paging_description) - .capitalize(Locale.current), - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - leadingContent = { - Icon( - imageVector = Icons.Rounded.FlashOn, - contentDescription = "tips" - ) - }, - trailingContent = { - Switch( - checked = preferences.paging, - onCheckedChange = null - ) - }, - colors = ListItemDefaults.colors(MaterialTheme.colorScheme.primaryContainer), - modifier = Modifier.clickable { preferences.paging = !preferences.paging } - ) - } - } -} \ No newline at end of file diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamGallery.kt b/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamGallery.kt index 5ea7cdcc3..b7000d998 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamGallery.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamGallery.kt @@ -1,81 +1,67 @@ package com.m3u.features.playlist.components import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.unit.Dp -import androidx.paging.compose.LazyPagingItems -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.items +import androidx.paging.compose.collectAsLazyPagingItems import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.foundation.lazy.list.TvLazyRow import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text -import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.data.database.model.Stream -import com.m3u.features.playlist.Category -import com.m3u.material.components.CircularProgressIndicator +import com.m3u.features.playlist.PlaylistViewModel import com.m3u.material.model.LocalSpacing @Composable internal fun TvStreamGallery( - categories: List, - streamPaged: LazyPagingItems, + channels: List, maxBrowserHeight: Dp, - useGridLayout: Boolean, + isSpecifiedSort: Boolean, isVodOrSeriesPlaylist: Boolean, onClick: (Stream) -> Unit, onLongClick: (Stream) -> Unit, onFocus: (Stream) -> Unit, modifier: Modifier = Modifier, ) { - val preferences = hiltPreferences() val spacing = LocalSpacing.current - val multiCategories = categories.size > 1 + val multiCategories = channels.size > 1 - val paging = preferences.paging - - if (!useGridLayout) { - TvLazyColumn( - verticalArrangement = Arrangement.spacedBy(spacing.medium), - contentPadding = PaddingValues(vertical = spacing.medium), - modifier = Modifier - .heightIn(max = maxBrowserHeight) - .fillMaxWidth() - .then(modifier) - ) { - items(categories) { channel -> - if (multiCategories) { - Text( - text = channel.name, - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(spacing.medium) - ) - } - val streams = channel.streams - TvLazyRow( - horizontalArrangement = Arrangement.spacedBy(spacing.medium), - contentPadding = PaddingValues(horizontal = spacing.medium), - modifier = Modifier.fillMaxWidth() - ) { - items( - items = streams, - key = { stream -> stream.id }, - ) { stream -> + TvLazyColumn( + verticalArrangement = Arrangement.spacedBy(spacing.medium), + contentPadding = PaddingValues(vertical = spacing.medium), + modifier = Modifier + .heightIn(max = maxBrowserHeight) + .fillMaxWidth() + .then(modifier) + ) { + items(channels) { (category, flow) -> + val streams = flow.collectAsLazyPagingItems() + if (multiCategories && streams.itemCount > 0) { + Text( + text = category, + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(spacing.medium) + ) + } + TvLazyRow( + horizontalArrangement = Arrangement.spacedBy(spacing.medium), + contentPadding = PaddingValues(horizontal = spacing.medium), + modifier = Modifier.fillMaxWidth() + ) { + items(streams.itemCount) { index -> + val stream = streams[index] + if (stream != null) { TvStreamItem( stream = stream, isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, + isGridLayout = false, onClick = { onClick(stream) }, onLongClick = { onLongClick(stream) }, modifier = Modifier.onFocusChanged { @@ -84,64 +70,8 @@ internal fun TvStreamGallery( } } ) - } - } - } - } - } else { - val actualRowCount = if (isVodOrSeriesPlaylist) preferences.rowCount + 7 - else preferences.rowCount + 5 - TvLazyVerticalGrid( - columns = TvGridCells.Fixed(actualRowCount), - contentPadding = PaddingValues(spacing.medium), - verticalArrangement = Arrangement.spacedBy(spacing.medium), - horizontalArrangement = Arrangement.spacedBy(spacing.large), - modifier = Modifier - .heightIn(max = maxBrowserHeight) - .fillMaxWidth() - ) { - if (!paging) { - categories.forEach { category -> - items(category.streams) { stream -> - TvStreamItem( - stream = stream, - isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, - onClick = { onClick(stream) }, - onLongClick = { onLongClick(stream) }, - modifier = Modifier - .onFocusChanged { - if (it.hasFocus) { - onFocus(stream) - } - } - ) - } - } - } else { - items(streamPaged.itemCount) { - val stream = streamPaged[it] - if (stream != null) { - TvStreamItem( - stream = stream, - isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, - onClick = { onClick(stream) }, - onLongClick = { onLongClick(stream) }, - modifier = Modifier - .onFocusChanged { focusState -> - if (focusState.hasFocus) { - onFocus(stream) - } - } - ) } else { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxHeight() - .aspectRatio(1f) - ) { - CircularProgressIndicator() - } + // TODO: placeholder } } } diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamItem.kt b/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamItem.kt index 384f16018..f016795ea 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamItem.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/components/TvStreamItem.kt @@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight 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.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.BrokenImage @@ -32,7 +34,6 @@ import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.data.database.model.Stream import com.m3u.material.components.CircularProgressIndicator import com.m3u.material.components.Icon -import com.m3u.material.ktx.ScaleIfHasAlphaTransformation import com.m3u.material.ktx.thenIf import com.m3u.material.model.LocalSpacing import coil.size.Size as CoilSize @@ -41,6 +42,7 @@ import coil.size.Size as CoilSize internal fun TvStreamItem( stream: Stream, isVodOrSeriesPlaylist: Boolean, + isGridLayout: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, modifier: Modifier = Modifier, @@ -73,7 +75,8 @@ internal fun TvStreamItem( ), modifier = Modifier .thenIf(!noPictureMode) { - Modifier.height(128.dp) + if (isGridLayout) Modifier.width(128.dp) + else Modifier.height(128.dp) } .then(modifier) ) { @@ -96,10 +99,10 @@ internal fun TvStreamItem( ImageRequest.Builder(context) .data(stream.cover) .size(CoilSize.ORIGINAL) - .transformations(ScaleIfHasAlphaTransformation(0.85f)) .build() }, - contentScale = ContentScale.FillHeight, + contentScale = if (isGridLayout) ContentScale.FillWidth + else ContentScale.FillHeight, contentDescription = stream.title, loading = { Column( @@ -134,7 +137,10 @@ internal fun TvStreamItem( ) } }, - modifier = Modifier.fillMaxHeight() + modifier = Modifier.then( + if (isGridLayout) Modifier.fillMaxWidth() + else Modifier.fillMaxHeight() + ) ) } } diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/internal/SmartphonePlaylistScreenImpl.kt b/features/playlist/src/main/java/com/m3u/features/playlist/internal/SmartphonePlaylistScreenImpl.kt index 0bf3aa6b5..e67b6f358 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/internal/SmartphonePlaylistScreenImpl.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/internal/SmartphonePlaylistScreenImpl.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState @@ -26,7 +25,6 @@ import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -48,12 +46,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LifecycleResumeEffect -import androidx.paging.compose.LazyPagingItems -import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.wrapper.Event import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Stream -import com.m3u.features.playlist.Category +import com.m3u.features.playlist.PlaylistViewModel import com.m3u.features.playlist.components.PlaylistTabRow import com.m3u.features.playlist.components.SmartphoneStreamGallery import com.m3u.i18n.R.string @@ -79,8 +75,7 @@ import kotlinx.coroutines.launch @Composable @InternalComposeApi internal fun SmartphonePlaylistScreenImpl( - categories: List, - streamPaged: LazyPagingItems, + channels: List, pinnedCategories: List, onPinOrUnpinCategory: (String) -> Unit, onHideCategory: (String) -> Unit, @@ -107,7 +102,6 @@ internal fun SmartphonePlaylistScreenImpl( ) { val spacing = LocalSpacing.current val configuration = LocalConfiguration.current - val preferences = hiltPreferences() val focusManager = LocalFocusManager.current val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed) @@ -146,12 +140,8 @@ internal fun SmartphonePlaylistScreenImpl( } } - var currentPage by remember(categories.size) { - mutableIntStateOf( - if (categories.isEmpty()) -1 - else 0 - ) - } + val categories = remember(channels) { channels.map { it.category } } + var category by remember(categories) { mutableStateOf(categories.firstOrNull().orEmpty()) } val (inner, outer) = contentPadding split WindowInsetsSides.Bottom @@ -195,7 +185,7 @@ internal fun SmartphonePlaylistScreenImpl( } }, frontLayerContent = { - MeshContainer(Modifier.fillMaxSize()) { + MeshContainer { val state = rememberLazyStaggeredGridState() LaunchedEffect(Unit) { snapshotFlow { state.isAtTop } @@ -214,22 +204,19 @@ internal fun SmartphonePlaylistScreenImpl( } } Column { - if (!preferences.paging) { - PlaylistTabRow( - page = currentPage, - onPageChanged = { currentPage = it }, - categories = categories, - pinnedCategories = pinnedCategories, - onPinOrUnpinCategory = onPinOrUnpinCategory, - onHideCategory = onHideCategory - ) - } + PlaylistTabRow( + selectedCategory = category, + categories = categories, + onCategoryChanged = { category = it }, + pinnedCategories = pinnedCategories, + onPinOrUnpinCategory = onPinOrUnpinCategory, + onHideCategory = onHideCategory + ) + val channel = channels.find { it.category == category } SmartphoneStreamGallery( state = state, rowCount = actualRowCount, - streams = if (preferences.paging) emptyList() - else categories.getOrElse(currentPage) { Category() }.streams, - streamPaged = streamPaged, + channel = channel, zapping = zapping, recently = sort == Sort.RECENTLY, isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, diff --git a/features/playlist/src/main/java/com/m3u/features/playlist/internal/TvPlaylistScreenImpl.kt b/features/playlist/src/main/java/com/m3u/features/playlist/internal/TvPlaylistScreenImpl.kt index 51e52b3b9..d73781226 100644 --- a/features/playlist/src/main/java/com/m3u/features/playlist/internal/TvPlaylistScreenImpl.kt +++ b/features/playlist/src/main/java/com/m3u/features/playlist/internal/TvPlaylistScreenImpl.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.paging.compose.LazyPagingItems import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.material3.DenseListItem import androidx.tv.material3.ImmersiveList @@ -37,7 +36,7 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.data.database.model.Stream -import com.m3u.features.playlist.Category +import com.m3u.features.playlist.PlaylistViewModel import com.m3u.features.playlist.components.ImmersiveBackground import com.m3u.features.playlist.components.TvStreamGallery import com.m3u.i18n.R @@ -58,8 +57,7 @@ import dev.chrisbanes.haze.hazeChild @InternalComposeApi internal fun TvPlaylistScreenImpl( title: String, - categories: List, - streamPaged: LazyPagingItems, + channels: List, query: String, onQuery: (String) -> Unit, sorts: List, @@ -76,12 +74,11 @@ internal fun TvPlaylistScreenImpl( ) { val preferences = hiltPreferences() - val paging = preferences.paging - val multiCategories = categories.size > 1 + val multiCategories = channels.size > 1 val noPictureMode = preferences.noPictureMode val darkMode = if (preferences.followSystemTheme) isSystemInDarkTheme() else preferences.darkMode - val useGridLayout = sort != Sort.UNSPECIFIED || paging + val useGridLayout = sort != Sort.UNSPECIFIED val maxBrowserHeight by animateDpAsState( targetValue = when { @@ -117,10 +114,9 @@ internal fun TvPlaylistScreenImpl( }, list = { TvStreamGallery( - categories = categories, - streamPaged = streamPaged, + channels = channels, maxBrowserHeight = maxBrowserHeight, - useGridLayout = useGridLayout, + isSpecifiedSort = useGridLayout, isVodOrSeriesPlaylist = isVodOrSeriesPlaylist, onClick = onStream, onLongClick = { stream -> press = stream }, diff --git a/features/setting/src/main/java/com/m3u/features/setting/fragments/preferences/RegularPreferences.kt b/features/setting/src/main/java/com/m3u/features/setting/fragments/preferences/RegularPreferences.kt index a32a5601e..6f8acf905 100644 --- a/features/setting/src/main/java/com/m3u/features/setting/fragments/preferences/RegularPreferences.kt +++ b/features/setting/src/main/java/com/m3u/features/setting/fragments/preferences/RegularPreferences.kt @@ -6,7 +6,6 @@ import androidx.compose.material.icons.rounded.AccessTime import androidx.compose.material.icons.rounded.ColorLens import androidx.compose.material.icons.rounded.DeviceHub import androidx.compose.material.icons.rounded.FitScreen -import androidx.compose.material.icons.rounded.FlashOn import androidx.compose.material.icons.rounded.HideImage import androidx.compose.material.icons.rounded.MusicNote import androidx.compose.material.icons.rounded.Pages @@ -20,11 +19,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.annotation.ClipMode import com.m3u.core.architecture.preferences.annotation.ConnectTimeout import com.m3u.core.architecture.preferences.annotation.PlaylistStrategy import com.m3u.core.architecture.preferences.annotation.UnseensMilliseconds +import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.util.basic.title import com.m3u.features.setting.components.CheckBoxSharedPreference import com.m3u.i18n.R.string @@ -151,14 +150,6 @@ internal fun RegularPreferences( onChanged = { preferences.autoRefresh = !preferences.autoRefresh } ) - CheckBoxSharedPreference( - title = string.feat_setting_paging, - content = string.feat_setting_paging_description, - icon = Icons.Rounded.FlashOn, - checked = preferences.paging, - onChanged = { preferences.paging = !preferences.paging } - ) - CheckBoxSharedPreference( title = string.feat_setting_randomly_in_favourite, icon = Icons.Rounded.Terrain, @@ -167,7 +158,7 @@ internal fun RegularPreferences( ) CheckBoxSharedPreference( - title = string.feat_setting_epg_clock_mode, + title = string.feat_setting_epg_clock_mode, icon = Icons.Rounded.AccessTime, checked = preferences.twelveHourClock, onChanged = { preferences.twelveHourClock = !preferences.twelveHourClock } diff --git a/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt b/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt index 11c35c30f..e67fc7555 100644 --- a/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt +++ b/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt @@ -249,6 +249,7 @@ class StreamViewModel @Inject constructor( Pager(PagingConfig(10)) { streamRepository.pagingAllByPlaylistUrl( playlist.url, + stream.value?.category.orEmpty(), "", StreamRepository.Sort.UNSPECIFIED )