Skip to content

Commit

Permalink
fix: new playlist won't be displayed after refreshing in playlist scr…
Browse files Browse the repository at this point in the history
…een.
  • Loading branch information
oxyroid committed May 18, 2024
1 parent 1660cd8 commit 5772da4
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 24 deletions.
51 changes: 39 additions & 12 deletions core/src/main/java/com/m3u/core/util/coroutine/Flows.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,53 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.timeout
import kotlin.reflect.KProperty
import kotlin.time.Duration


operator fun <T> MutableStateFlow<T>.setValue(ref: Any, property: KProperty<*>, t: T) {
value = t
}

operator fun <T> StateFlow<T>.getValue(ref: Any, property: KProperty<*>): T {
return value
}

@OptIn(FlowPreview::class)
fun <T> Flow<T>.onTimeout(duration: Duration, block: FlowCollector<T>.() -> Unit) =
timeout(duration).catch {
if (it is TimeoutCancellationException) {
block()
}
}

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> where T1 : Any, T2 : Any, R : Any = 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> where T1 : Any, T2 : Any, T3 : Any, R : Any =
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> where T1 : Any, T2 : Any, T3 : Any, T4 : Any, R : Any =
flatmapCombined(listOf(flow1, flow2, flow3, flow4)) { keys ->
transform(keys[0] as T1, keys[1] as T2, keys[2] as T3, keys[3] as T4)
}
13 changes: 13 additions & 0 deletions data/src/main/java/com/m3u/data/database/dao/StreamDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ internal interface StreamDao {
query: String
): List<String>

@Query(
"""
SELECT DISTINCT `group`
FROM streams
WHERE playlistUrl = :playlistUrl
AND title LIKE '%'||:query||'%'
"""
)
fun observeCategoriesByPlaylistUrl(
playlistUrl: String,
query: String
): Flow<List<String>>

@Delete
suspend fun delete(stream: Stream)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface PlaylistRepository {
fun observePlaylistUrls(): Flow<List<String>>
suspend fun get(url: String): Playlist?
suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String, query: String): List<String>
fun observeCategoriesByPlaylistUrlIgnoreHidden(url: String, query: String): Flow<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 @@ -50,7 +50,10 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
Expand Down Expand Up @@ -526,6 +529,22 @@ internal class PlaylistRepositoryImpl @Inject constructor(
.sortedByDescending { it in pinnedCategories }
}

override fun observeCategoriesByPlaylistUrlIgnoreHidden(
url: String,
query: String
): Flow<List<String>> = playlistDao.observeByUrl(url).flatMapLatest { playlist ->
playlist ?: return@flatMapLatest flowOf()
val pinnedCategories = playlist.pinnedCategories
val hiddenCategories = playlist.hiddenCategories
streamDao
.observeCategoriesByPlaylistUrl(playlist.url, query)
.map { categories ->
categories
.filterNot { it in hiddenCategories }
.sortedByDescending { it in pinnedCategories }
}
}

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 @@ -30,6 +30,7 @@ import com.m3u.core.architecture.logger.Logger
import com.m3u.core.architecture.logger.Profiles
import com.m3u.core.architecture.logger.install
import com.m3u.core.architecture.preferences.Preferences
import com.m3u.core.util.coroutine.flatmapCombined
import com.m3u.core.wrapper.Event
import com.m3u.core.wrapper.Resource
import com.m3u.core.wrapper.handledEvent
Expand All @@ -55,21 +56,28 @@ import com.m3u.features.playlist.navigation.PlaylistNavigation
import com.m3u.ui.Sort
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import androidx.tvprovider.media.tv.Channel as TvProviderChannel

const val REQUEST_CHANNEL_BROWSABLE = 4001
Expand Down Expand Up @@ -326,7 +334,7 @@ class PlaylistViewModel @Inject constructor(
internal val query = MutableStateFlow("")
internal val scrollUp: MutableStateFlow<Event<Unit>> = MutableStateFlow(handledEvent())

data class PagingStreamParameters(
data class ChannelParameters(
val playlistUrl: String,
val query: String,
val sort: Sort,
Expand All @@ -338,25 +346,31 @@ class PlaylistViewModel @Inject constructor(
val streams: Flow<PagingData<Stream>>,
)

@OptIn(FlowPreview::class)
private val categories = flatmapCombined(playlistUrl, query) { playlistUrl, query ->
playlistRepository.observeCategoriesByPlaylistUrlIgnoreHidden(playlistUrl, query)
}
.let { flow ->
merge(
flow.take(1),
flow.drop(1).debounce(1.seconds)
)
}

internal val channels: StateFlow<List<Channel>> = combine(
playlistUrl.flatMapLatest { playlistRepository.observe(it) },
playlistUrl,
categories,
query,
sort
) { playlist, query, sort ->
val playlistUrl = playlist?.url ?: return@combine null
val categories = playlistUrl.let {
playlistRepository.getCategoriesByPlaylistUrlIgnoreHidden(it, query)
}
PagingStreamParameters(
) { playlistUrl, categories, query, sort ->
ChannelParameters(
playlistUrl = playlistUrl,
query = query,
sort = sort,
categories = categories
)
}
.map { params ->
params ?: return@map emptyList()
val (playlistUrl, query, sort, categories) = params
.mapLatest { (playlistUrl, query, sort, categories) ->
categories.map { category ->
Channel(
category = category,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ internal fun SmartphonePlaylistScreenImpl(
}

val gallery = @Composable {
val channel = channels.find { it.category == category }
val channel = remember(channels, category) {
channels.find { it.category == category }
}
SmartphoneStreamGallery(
state = state,
rowCount = actualRowCount,
Expand Down

0 comments on commit 5772da4

Please sign in to comment.