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

Add feature navigate between channels in Player #207

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

tungnk123
Copy link
Contributor

No description provided.

Copy link
Owner

@oxyroid oxyroid left a comment

Choose a reason for hiding this comment

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

Please revert some unnecessary formatting changes. Including Linebreak and contraction.

@tungnk123 tungnk123 force-pushed the feature/issue-201-navigate-between-channels branch from 4ca54ae to dbb72f9 Compare November 4, 2024 15:33
@oxyroid
Copy link
Owner

oxyroid commented Nov 17, 2024

I have a new idea to find out the adjacent channels:

Define a new class to hold the SQL result.

// Channel.kt
data class AdjacentChannels(
    @ColumnInfo("prev")
    val prev: Channel?,
    @ColumnInfo("next")
    val next: Channel?
)

Add a method which returns "Flow"

// ChannelDao.kt
/**
 * Observe the two channels adjacent to a specific channel in a particular channel order.
 * If the channel is not in this channel order, a null object will be returned.
 */
@Query(
    """
        Write SQL here.
    """
)
fun observeAdjacentChannels(
    channelId: Int,
    playlistUrl: String,
    category: String
): Flow<AdjacentChannels?>
  • You can refer to the observeProgrammeRange method in ProgrammeDao. It shows you how to map a custom object to a SQL statement.
  • You can refer to the pagingAllByPlaylistUrl method in ChannelDao. It shows you how the channel order should be.

@oxyroid
Copy link
Owner

oxyroid commented Nov 17, 2024

Then, because it is not a collection, you can maintain it directly in the viewmodel through a series of operators such as combine/map/stateIn like other flows without performance problems.

// ChannelViewModel.kt
val adjacentChannels: StateFlow<AdjacentChannels?> = flatmapCombined(playlist, channel) { playlist, channel ->
    playlist?: return@flatmapCombined flowOf(null)
    channel?: return@flatmapCombined flowOf(null)
    // Don't forget to define the same method in repository because viewModel shouldn't hold the DAO instance.
    channelRepository.observeAdjacentChannels(
        channelId = channel.id,
        playlistUrl = playlist.url,
        category = channel.category
    )
}
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.Lazily,
        initialValue = null
    )

In addition, you need to modify the function flatmapCombined in the project, it has some problems.

fun <R> flatmapCombined(
    flows: Iterable<Flow<Any?>>,
    transform: (keys: Array<Any?>) -> Flow<R>
): Flow<R> = combine(flows) { it }.flatMapLatest { keys -> transform(keys) }

@Suppress("UNCHECKED_CAST")
fun <T1, T2, R> flatmapCombined(
    flow1: Flow<T1>,
    flow2: Flow<T2>,
    transform: (t1: T1, t2: T2) -> Flow<R>
): Flow<R> = flatmapCombined(listOf(flow1, flow2)) { keys ->
    transform(keys[0] as T1, keys[1] as T2)
}

@Suppress("UNCHECKED_CAST")
fun <T1, T2, T3, R> flatmapCombined(
    flow1: Flow<T1>,
    flow2: Flow<T2>,
    flow3: Flow<T3>,
    transform: (t1: T1, t2: T2, t3: T3) -> Flow<R>
): Flow<R> = flatmapCombined(listOf(flow1, flow2, flow3)) { keys ->
    transform(keys[0] as T1, keys[1] as T2, keys[2] as T3)
}

@Suppress("UNCHECKED_CAST")
fun <T1, T2, T3, T4, R> flatmapCombined(
    flow1: Flow<T1>,
    flow2: Flow<T2>,
    flow3: Flow<T3>,
    flow4: Flow<T4>,
    transform: (t1: T1, t2: T2, t3: T3, t4: T4) -> Flow<R>
): Flow<R> = flatmapCombined(listOf(flow1, flow2, flow3, flow4)) { keys ->
    transform(keys[0] as T1, keys[1] as T2, keys[2] as T3, keys[3] as T4)
}

Then, you can call collectAsState to convert it in the UI layer.

val adjacentChannels = viewModel.adjacentChannels.collectAsStateWithLifecycle()

This way has a great advantage, that is, you can control in real time whether the previous or next button is enabled or not.

MaskButton(
    enabled = adjacentChannels != null && adjacentChannels.prev != null,
    onClick = {
        val prev = adjacentChannels.prev
        if (adjacentChannels != null && prev != null) {
            helper.play(MediaCommand.Common(prev.id))
        }
    }
)

oxyroid and others added 6 commits November 18, 2024 02:05
* feat: overscroll effect.

* fix: remove unused import directive.
* feat: overscroll effect.

* fix: remove unused import directive.

* fix: overscroll effect.

* fix: remove unused import directive.
@tungnk123
Copy link
Contributor Author

Could you pls review this pull request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants