From 5772da46e574dfcd4df3a76f688151bbff19385e Mon Sep 17 00:00:00 2001 From: oxy Date: Sat, 18 May 2024 18:39:34 +0800 Subject: [PATCH] fix: new playlist won't be displayed after refreshing in playlist screen. --- .../java/com/m3u/core/util/coroutine/Flows.kt | 51 ++++++++++++++----- .../com/m3u/data/database/dao/StreamDao.kt | 13 +++++ .../repository/playlist/PlaylistRepository.kt | 1 + .../playlist/PlaylistRepositoryImpl.kt | 19 +++++++ .../features/playlist/PlaylistViewModel.kt | 36 +++++++++---- .../internal/SmartphonePlaylistScreenImpl.kt | 4 +- 6 files changed, 100 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/m3u/core/util/coroutine/Flows.kt b/core/src/main/java/com/m3u/core/util/coroutine/Flows.kt index 39196efd3..f653188e4 100644 --- a/core/src/main/java/com/m3u/core/util/coroutine/Flows.kt +++ b/core/src/main/java/com/m3u/core/util/coroutine/Flows.kt @@ -4,22 +4,12 @@ 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 MutableStateFlow.setValue(ref: Any, property: KProperty<*>, t: T) { - value = t -} - -operator fun StateFlow.getValue(ref: Any, property: KProperty<*>): T { - return value -} - @OptIn(FlowPreview::class) fun Flow.onTimeout(duration: Duration, block: FlowCollector.() -> Unit) = timeout(duration).catch { @@ -27,3 +17,40 @@ fun Flow.onTimeout(duration: Duration, block: FlowCollector.() -> Unit block() } } + +fun flatmapCombined( + flows: Iterable>, + transform: (keys: Array) -> Flow +): Flow = combine(flows) { it }.flatMapLatest { keys -> transform(keys) } + +@Suppress("UNCHECKED_CAST") +fun flatmapCombined( + flow1: Flow, + flow2: Flow, + transform: (t1: T1, t2: T2) -> Flow +): Flow 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 flatmapCombined( + flow1: Flow, + flow2: Flow, + flow3: Flow, + transform: (t1: T1, t2: T2, t3: T3) -> Flow +): Flow 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 flatmapCombined( + flow1: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + transform: (t1: T1, t2: T2, t3: T3, t4: T4) -> Flow +): Flow 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) + } 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 8344eeb40..6acb5991a 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 @@ -30,6 +30,19 @@ internal interface StreamDao { query: String ): List + @Query( + """ + SELECT DISTINCT `group` + FROM streams + WHERE playlistUrl = :playlistUrl + AND title LIKE '%'||:query||'%' + """ + ) + fun observeCategoriesByPlaylistUrl( + playlistUrl: String, + query: String + ): Flow> + @Delete suspend fun delete(stream: Stream) 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 1ea009bc5..bf7c52e6c 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 @@ -15,6 +15,7 @@ interface PlaylistRepository { fun observePlaylistUrls(): Flow> suspend fun get(url: String): Playlist? suspend fun getCategoriesByPlaylistUrlIgnoreHidden(url: String, query: String): List + fun observeCategoriesByPlaylistUrlIgnoreHidden(url: String, query: String): Flow> 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 e7c83cb51..0324c4778 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 @@ -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 @@ -526,6 +529,22 @@ internal class PlaylistRepositoryImpl @Inject constructor( .sortedByDescending { it in pinnedCategories } } + override fun observeCategoriesByPlaylistUrlIgnoreHidden( + url: String, + query: String + ): Flow> = 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) 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 3d255407f..fb3dd361c 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 @@ -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 @@ -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 @@ -326,7 +334,7 @@ class PlaylistViewModel @Inject constructor( internal val query = MutableStateFlow("") internal val scrollUp: MutableStateFlow> = MutableStateFlow(handledEvent()) - data class PagingStreamParameters( + data class ChannelParameters( val playlistUrl: String, val query: String, val sort: Sort, @@ -338,25 +346,31 @@ class PlaylistViewModel @Inject constructor( val streams: Flow>, ) + @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> = 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, 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 8312a07b1..93196167a 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 @@ -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,