Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/issue 201 navigate between channels #217

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions data/src/main/java/com/m3u/data/database/dao/ChannelDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel
import kotlinx.coroutines.flow.Flow

Expand Down Expand Up @@ -198,4 +199,33 @@ internal interface ChannelDao {

@Query("UPDATE streams SET seen = :target WHERE id = :id")
suspend fun updateSeen(id: Int, target: Long)

@Query(
"""
WITH TargetChannel AS (
SELECT *
FROM streams
WHERE id = :channelId
AND playlist_url = :playlistUrl
AND `group` = :category
)
SELECT
(SELECT id FROM streams
WHERE playlist_url = :playlistUrl
AND `group` = :category
AND title < (SELECT title FROM TargetChannel)
ORDER BY title DESC LIMIT 1) AS prev_id,
(SELECT id FROM streams
WHERE playlist_url = :playlistUrl
AND `group` = :category
AND title > (SELECT title FROM TargetChannel)
ORDER BY title ASC LIMIT 1) AS next_id
"""
)
fun observeAdjacentChannels(
channelId: Int,
playlistUrl: String,
category: String,
): Flow<AdjacentChannels>

}
10 changes: 10 additions & 0 deletions data/src/main/java/com/m3u/data/database/model/AdjacentChannels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.m3u.data.database.model

import androidx.room.ColumnInfo

data class AdjacentChannels(
@ColumnInfo("prev_id")
val prevId: Int?,
@ColumnInfo("next_id")
val nextId: Int?
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.m3u.data.repository.channel

import androidx.paging.PagingSource
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel
import kotlinx.coroutines.flow.Flow
import kotlin.time.Duration
Expand All @@ -17,6 +18,11 @@ interface ChannelRepository {
): PagingSource<Int, Channel>

suspend fun get(id: Int): Channel?
fun observeAdjacentChannels(
channelId: Int,
playlistUrl: String,
category: String,
): Flow<AdjacentChannels>

suspend fun getRandomIgnoreSeriesAndHidden(): Channel?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.m3u.core.architecture.logger.sandBox
import com.m3u.core.architecture.preferences.Preferences
import com.m3u.data.database.dao.ChannelDao
import com.m3u.data.database.dao.PlaylistDao
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.isSeries
import com.m3u.data.repository.channel.ChannelRepository.Sort
Expand Down Expand Up @@ -49,6 +50,16 @@ internal class ChannelRepositoryImpl @Inject constructor(
channelDao.get(id)
}

override fun observeAdjacentChannels(
channelId: Int,
playlistUrl: String,
category: String,
): Flow<AdjacentChannels> = channelDao.observeAdjacentChannels(
channelId = channelId,
playlistUrl = playlistUrl,
category = category
)

override suspend fun getRandomIgnoreSeriesAndHidden(): Channel? = logger.execute {
val playlists = playlistDao.getAll()
val seriesPlaylistUrls = playlists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.automirrored.rounded.NavigateBefore
import androidx.compose.material.icons.automirrored.rounded.NavigateNext
import androidx.compose.material.icons.automirrored.rounded.VolumeDown
import androidx.compose.material.icons.automirrored.rounded.VolumeOff
import androidx.compose.material.icons.automirrored.rounded.VolumeUp
Expand Down Expand Up @@ -72,6 +74,7 @@ import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.isNotEmpty
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.feature.channel.MaskCenterState.Pause
import com.m3u.feature.channel.MaskCenterState.Play
import com.m3u.feature.channel.MaskCenterState.Replay
Expand Down Expand Up @@ -100,6 +103,7 @@ import kotlin.time.toDuration

@Composable
internal fun ChannelMask(
adjacentChannels: AdjacentChannels?,
cover: String,
title: String,
gesture: MaskGesture?,
Expand All @@ -118,6 +122,8 @@ internal fun ChannelMask(
openOrClosePanel: () -> Unit,
onEnterPipMode: () -> Unit,
onVolume: (Float) -> Unit,
onNextChannelClick: () -> Unit,
onPreviousChannelClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val preferences = hiltPreferences()
Expand Down Expand Up @@ -315,29 +321,42 @@ internal fun ChannelMask(
},
body = {
Box {
val maskCenterState = MaskCenterState.of(
playerState.playState,
playerState.isPlaying,
preferences.alwaysShowReplay,
isPanelExpanded,
playerState.playerError
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemBarsIgnoringVisibility)
) {
MaskNavigateButton(
maskNavigateState = MaskNavigateState.Previous,
maskState = maskState,
enabled = adjacentChannels?.prevId != null,
onClick = onPreviousChannelClick,
)
MaskCenterButton(
maskCenterState = maskCenterState,
maskState = maskState,
onPlay = { playerState.player?.play() },
onPause = { playerState.player?.pause() },
onRetry = { coroutineScope.launch { helper.replay() } },
)
MaskNavigateButton(
maskNavigateState = MaskNavigateState.Next,
maskState = maskState,
enabled = adjacentChannels?.nextId != null,
onClick = onNextChannelClick,
)
}
val maskCenterState = MaskCenterState.of(
playerState.playState,
playerState.isPlaying,
preferences.alwaysShowReplay,
isPanelExpanded,
playerState.playerError
)
MaskCenterButton(
maskCenterState = maskCenterState,
maskState = maskState,
onPlay = { playerState.player?.play() },
onPause = { playerState.player?.pause() },
onRetry = { coroutineScope.launch { helper.replay() } },
modifier = Modifier.align(Alignment.Center)
)


}
},
footer = {
Expand Down Expand Up @@ -542,11 +561,13 @@ private fun MaskCenterButton(
onPause: () -> Unit,
onRetry: () -> Unit,
) {
Box(modifier, contentAlignment = Alignment.Center) {
Box(
modifier,
contentAlignment = Alignment.Center
) {
when (maskCenterState) {
Replay, Play, Pause -> {
MaskCircleButton(
state = maskState,
MaskCircleButton(state = maskState,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
Why i cannot use the reformat feature with these file, when i use it don't move the state to the next line?
I attached the image from my Code Style Settings

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea reformation would make mistakes sometimes.

icon = when (maskCenterState) {
Replay -> Icons.Rounded.Refresh
Play -> Icons.Rounded.PlayArrow
Expand All @@ -560,15 +581,42 @@ private fun MaskCenterButton(
else -> {
{}
} // never reached
}
)
})
}

else -> {}
}
}
}

@Composable
private fun MaskNavigateButton(
maskNavigateState: MaskNavigateState,
maskState: MaskState,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
Box(
modifier,
contentAlignment = Alignment.Center
) {
when (maskNavigateState) {
MaskNavigateState.Next, MaskNavigateState.Previous -> {
MaskCircleButton(
state = maskState,
enabled = enabled,
icon = when (maskNavigateState) {
MaskNavigateState.Next -> Icons.AutoMirrored.Rounded.NavigateNext
MaskNavigateState.Previous -> Icons.AutoMirrored.Rounded.NavigateBefore
},
onClick = onClick
)
}
}
}
}

private enum class MaskCenterState {
Replay, Play, Pause, Loading;

Expand All @@ -590,4 +638,8 @@ private enum class MaskCenterState {
else -> if (!isPlaying) Play else Pause
}
}
}

private enum class MaskNavigateState {
Next, Previous
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.google.accompanist.permissions.rememberPermissionState
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.isNotEmpty
import com.m3u.core.util.basic.title
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
import com.m3u.feature.channel.components.CoverPlaceholder
Expand Down Expand Up @@ -82,6 +83,7 @@ fun ChannelRoute(

val playerState: PlayerState by viewModel.playerState.collectAsStateWithLifecycle()
val channel by viewModel.channel.collectAsStateWithLifecycle()
val adjacentChannels by viewModel.adjacentChannels.collectAsStateWithLifecycle()
val playlist by viewModel.playlist.collectAsStateWithLifecycle()
val devices = viewModel.devices
val isDevicesVisible by viewModel.isDevicesVisible.collectAsStateWithLifecycle()
Expand All @@ -96,7 +98,7 @@ fun ChannelRoute(
initialValue = false
)

val channels = viewModel.channels.collectAsLazyPagingItems()
val channels = viewModel.pagingChannels.collectAsLazyPagingItems()
val programmes = viewModel.programmes.collectAsLazyPagingItems()
val programmeRange by viewModel.programmeRange.collectAsStateWithLifecycle()

Expand Down Expand Up @@ -230,13 +232,16 @@ fun ChannelRoute(
maskState = maskState,
playerState = playerState,
playlist = playlist,
adjacentChannels = adjacentChannels,
channel = channel,
hasTrack = tracks.isNotEmpty(),
isPanelExpanded = isPanelExpanded,
volume = volume,
onVolume = viewModel::onVolume,
brightness = brightness,
onBrightness = { brightness = it },
onPreviousChannelClick = viewModel::getPreviousChannel,
onNextChannelClick = viewModel::getNextChannel,
onEnterPipMode = {
helper.enterPipMode(playerState.videoSize)
maskState.unlockAll()
Expand Down Expand Up @@ -285,6 +290,7 @@ private fun ChannelPlayer(
playerState: PlayerState,
playlist: Playlist?,
channel: Channel?,
adjacentChannels: AdjacentChannels?,
isSeriesPlaylist: Boolean,
hasTrack: Boolean,
isPanelExpanded: Boolean,
Expand All @@ -296,6 +302,8 @@ private fun ChannelPlayer(
openOrClosePanel: () -> Unit,
onVolume: (Float) -> Unit,
onBrightness: (Float) -> Unit,
onPreviousChannelClick: () -> Unit,
onNextChannelClick: () -> Unit,
onEnterPipMode: () -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -373,6 +381,7 @@ private fun ChannelPlayer(
)

ChannelMask(
adjacentChannels = adjacentChannels,
cover = cover,
title = title,
playlistTitle = playlistTitle,
Expand All @@ -390,6 +399,8 @@ private fun ChannelPlayer(
openOrClosePanel = openOrClosePanel,
onVolume = onVolume,
onEnterPipMode = onEnterPipMode,
onPreviousChannelClick = onPreviousChannelClick,
onNextChannelClick = onNextChannelClick,
gesture = gesture
)

Expand Down
Loading
Loading