Skip to content

Commit

Permalink
feat: now "paging channel" supports "grouped by category", so the "pa…
Browse files Browse the repository at this point in the history
…ging channel" is always enabled and its option in settings has been removed.
  • Loading branch information
oxyroid committed May 12, 2024
1 parent cb23312 commit 0271d89
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 565 deletions.
22 changes: 18 additions & 4 deletions data/src/main/java/com/m3u/data/database/dao/StreamDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>

@Delete
suspend fun delete(stream: Stream)

Expand Down Expand Up @@ -77,27 +80,38 @@ internal interface StreamDao {
@Query("SELECT * FROM streams WHERE hidden = 1")
fun observeAllHidden(): Flow<List<Stream>>

@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<Int, Stream>

@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<Int, Stream>

@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<Int, Stream>

@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<Int, Stream>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface PlaylistRepository {
fun observeAllEpgs(): Flow<List<Playlist>>
fun observePlaylistUrls(): Flow<List<String>>
suspend fun get(url: String): Playlist?
suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String): List<String>
fun observe(url: String): Flow<Playlist?>
fun observeWithStreams(url: String): Flow<PlaylistWithStreams?>
suspend fun getWithStreams(url: String): PlaylistWithStreams?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,15 @@ internal class PlaylistRepositoryImpl @Inject constructor(
playlistDao.getByUrl(url)
}

override suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String): List<String> =
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface StreamRepository {
fun observeAllByPlaylistUrl(playlistUrl: String): Flow<List<Stream>>
fun pagingAllByPlaylistUrl(
url: String,
category: String,
query: String,
sort: Sort
): PagingSource<Int, Stream>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ internal class StreamRepositoryImpl @Inject constructor(

override fun pagingAllByPlaylistUrl(
url: String,
category: String,
query: String,
sort: Sort
): PagingSource<Int, Stream> = 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
)
Expand All @@ -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
}
)
}
}
}
Expand All @@ -246,8 +240,7 @@ private fun PlaylistScreen(
onQuery: (String) -> Unit,
rowCount: Int,
zapping: Stream?,
categories: List<Category>,
streamPaged: LazyPagingItems<Stream>,
channels: List<PlaylistViewModel.Channel>,
pinnedCategories: List<String>,
onPinOrUnpinCategory: (String) -> Unit,
onHideCategory: (String) -> Unit,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -327,8 +319,7 @@ private fun PlaylistScreen(
} else {
TvPlaylistScreenImpl(
title = title,
categories = categories,
streamPaged = streamPaged,
channels = channels,
query = query,
onQuery = onQuery,
onStream = onStream,
Expand Down
Loading

0 comments on commit 0271d89

Please sign in to comment.