From 030323c7e492c89d8faad38aff3fefb49c4c5d8c Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 3 Jul 2023 15:50:47 -0300 Subject: [PATCH 1/7] WIP download section UI --- .../src/main/res/layout/fragment_feed_all.xml | 45 +++++++++++++++++++ .../src/main/res/values-b+fil/strings.xml | 1 + .../src/main/res/values-es/strings.xml | 2 + .../src/main/res/values-ja/strings.xml | 2 + .../src/main/res/values-zh/strings.xml | 2 + .../dashboard/src/main/res/values/strings.xml | 2 + 6 files changed, 54 insertions(+) diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_all.xml b/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_all.xml index 4765f81f61..bcbc95e357 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_all.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_all.xml @@ -259,6 +259,51 @@ + + + + + + + + + + diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml index a3ed6fd81d..3ecf573a60 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml @@ -102,6 +102,7 @@ Kamakailang Nilalaro Basahin na Ngayon Manood na Ngayon + Downloaded Mga rekomendasyon No \"Feed\" Results Found Walang \"Makinig\" Natagpuan ang mga Resulta diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml index 19f9f1f3eb..ca3c35e8c6 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml @@ -129,6 +129,8 @@ Read Now Watch Now + Descargado + Recomendaciones Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml index eb90db4c8a..25ed5369c5 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml @@ -128,6 +128,8 @@ Read Now Watch Now + Downloaded + Recommendations Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml index f3e440f2d4..13957b07a9 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml @@ -128,6 +128,8 @@ Read Now Watch Now + Downloaded + Recommendations Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml index e215363a78..a87d8fa616 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml @@ -127,6 +127,8 @@ Recently Read Read Now Watch Now + Downloaded + Recommendations From c122ea54cf55ad136b3cafaa20a7f354c2778a0a Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 3 Jul 2023 16:29:40 -0300 Subject: [PATCH 2/7] Asign Feed to feedItem list --- .../feature_repository/SphinxRepository.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt index 86f8a061a6..4810124a14 100644 --- a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt +++ b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt @@ -4131,9 +4131,10 @@ abstract class SphinxRepository( .asFlow() .mapToList(io) .map { listFeedItemDbo -> - listFeedItemDbo.map { + val feedItems = listFeedItemDbo.map { feedItemDboPresenterMapper.mapFrom(it) } + assignFeedToFeedItemList(feedItems, coreDB.getSphinxDatabaseQueries()) } .distinctUntilChanged() ) @@ -4511,6 +4512,31 @@ abstract class SphinxRepository( FeedItemDboPodcastEpisodePresenterMapper(dispatchers) } + private suspend fun assignFeedToFeedItemList( + feedItems: List, + queries: SphinxDatabaseQueries + ): List { + val feedsMap: MutableMap = mutableMapOf() + + val feedIds = feedItems.map { it.feedId }.distinct() + + queries.feedGetByIds(feedIds) + .executeAsList() + .let { response -> + response.forEach { dbo -> + val feed = feedDboPresenterMapper.mapFrom(dbo) + feedsMap[dbo.id] = feed + } + } + + feedItems.forEach { item -> + item.feed = feedsMap[item.feedId] + } + + return feedItems + } + + override fun getPodcastByChatId(chatId: ChatId): Flow = flow { val queries = coreDB.getSphinxDatabaseQueries() From 9e3081e8ec02b5a5eefa74058924b7f1310262d0 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 3 Jul 2023 17:19:10 -0300 Subject: [PATCH 3/7] Populate list of downloaded feed items --- .../ui/adapter/FeedDownloadedAdapter.kt | 249 ++++++++++++++++++ .../ui/feed/FeedDownloadedViewModel.kt | 12 + .../dashboard/ui/feed/all/FeedAllFragment.kt | 17 ++ .../dashboard/ui/feed/all/FeedAllViewModel.kt | 23 +- 4 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/adapter/FeedDownloadedAdapter.kt create mode 100644 sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/FeedDownloadedViewModel.kt diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/adapter/FeedDownloadedAdapter.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/adapter/FeedDownloadedAdapter.kt new file mode 100644 index 0000000000..ecd8ccdba6 --- /dev/null +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/adapter/FeedDownloadedAdapter.kt @@ -0,0 +1,249 @@ +package chat.sphinx.dashboard.ui.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import androidx.core.content.ContextCompat +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import chat.sphinx.concept_image_loader.Disposable +import chat.sphinx.concept_image_loader.ImageLoader +import chat.sphinx.concept_image_loader.ImageLoaderOptions +import chat.sphinx.dashboard.R +import chat.sphinx.dashboard.databinding.LayoutFeedSquaredRowHolderBinding +import chat.sphinx.dashboard.ui.feed.FeedDownloadedViewModel +import chat.sphinx.wrapper_common.timeAgo +import chat.sphinx.wrapper_feed.FeedItem +import io.matthewnelson.android_feature_screens.util.goneIfFalse +import io.matthewnelson.android_feature_viewmodel.util.OnStopSupervisor +import io.matthewnelson.concept_coroutines.CoroutineDispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class FeedDownloadedAdapter( + private val imageLoader: ImageLoader, + private val lifecycleOwner: LifecycleOwner, + private val onStopSupervisor: OnStopSupervisor, + private val viewModel: FeedDownloadedViewModel, + private val viewModelDispatcher: CoroutineDispatchers, + ): RecyclerView.Adapter(), DefaultLifecycleObserver { + + private inner class Diff( + private val oldList: List, + private val newList: List, + ): DiffUtil.Callback() { + + override fun getOldListSize(): Int { + return oldList.size + } + + override fun getNewListSize(): Int { + return newList.size + } + + @Volatile + var sameList: Boolean = oldListSize == newListSize + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return try { + val old = oldList[oldItemPosition] + val new = newList[newItemPosition] + + val same: Boolean = + old.feed?.id == new.feed?.id && + old.id == new.id + + if (sameList) { + sameList = same + } + + same + } catch (e: IndexOutOfBoundsException) { + sameList = false + false + } + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return try { + val old = oldList[oldItemPosition] + val new = newList[newItemPosition] + + val same: Boolean = + old.title == new.title && + old.description == new.description && + old.feed?.itemsCount == new.feed?.itemsCount && + old.feed?.lastItem?.id == new.feed?.lastItem?.id && + old.contentEpisodeStatus?.currentTime == new.contentEpisodeStatus?.currentTime && + old.contentEpisodeStatus?.duration == new.contentEpisodeStatus?.duration + + if (sameList) { + sameList = same + } + + same + } catch (e: IndexOutOfBoundsException) { + sameList = false + false + } + } + + } + + private val podcastEpisodes = ArrayList(listOf()) + + override fun onStart(owner: LifecycleOwner) { + super.onStart(owner) + + onStopSupervisor.scope.launch(viewModelDispatcher.mainImmediate) { + viewModel.feedDownloadedHolderViewStateFlow.collect { list -> + + val episodesList = mutableListOf() + + list.forEach { feedItem -> + episodesList.add(feedItem) + } + + if (podcastEpisodes.isEmpty()) { + podcastEpisodes.addAll(episodesList) + this@FeedDownloadedAdapter.notifyDataSetChanged() + } else { + + val diff = Diff(podcastEpisodes, episodesList) + + withContext(viewModelDispatcher.default) { + DiffUtil.calculateDiff(diff) + }.let { result -> + + if (!diff.sameList) { + podcastEpisodes.clear() + podcastEpisodes.addAll(episodesList) + result.dispatchUpdatesTo(this@FeedDownloadedAdapter) + } + } + } + } + } + } + + override fun getItemCount(): Int { + return podcastEpisodes.size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedDownloadedAdapter.DownloadedPodcastEpisodeViewHolder { + val binding = LayoutFeedSquaredRowHolderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + + return DownloadedPodcastEpisodeViewHolder(binding) + } + + override fun onBindViewHolder(holder: FeedDownloadedAdapter.DownloadedPodcastEpisodeViewHolder, position: Int) { + holder.bind(position) + } + + private val imageLoaderOptions: ImageLoaderOptions by lazy { + ImageLoaderOptions.Builder() + .placeholderResId(R.drawable.ic_podcast_placeholder) + .build() + } + + inner class DownloadedPodcastEpisodeViewHolder( + private val binding: LayoutFeedSquaredRowHolderBinding + ): RecyclerView.ViewHolder(binding.root), DefaultLifecycleObserver { + + private var holderJob: Job? = null + private var disposable: Disposable? = null + + private var episode: FeedItem? = null + + init { + binding.layoutConstraintFeedHolder.setOnClickListener { + episode?.let { nnEpisode -> + lifecycleOwner.lifecycleScope.launch { + viewModel.feedDownloadedSelected(nnEpisode) + } + } + } + binding.seekBarCurrentTimeEpisodeProgress.setOnTouchListener { _, _ -> true } + } + + fun bind(position: Int) { + binding.apply { + val podcastEpisode: FeedItem = podcastEpisodes.getOrNull(position) ?: let { + episode = null + return + } + episode = podcastEpisode + disposable?.dispose() + holderJob?.cancel() + + podcastEpisode.imageUrlToShow?.let { imageUrl -> + onStopSupervisor.scope.launch(viewModelDispatcher.mainImmediate) { + imageLoader.load( + imageViewItemImage, + imageUrl.value, + imageLoaderOptions + ).also { + disposable = it + } + }.let { job -> + holderJob = job + } + } ?: run { + imageViewItemImage.setImageDrawable( + ContextCompat.getDrawable(root.context, R.drawable.ic_podcast_placeholder) + ) + } + + textViewItemName.goneIfFalse(podcastEpisode.titleToShow.isNotEmpty()) + textViewItemDescription.goneIfFalse(podcastEpisode.descriptionToShow.isNotEmpty()) + + textViewItemName.text = podcastEpisode.titleToShow + textViewItemDescription.text = podcastEpisode.descriptionToShow + + textViewItemPublishTime.text = podcastEpisode.datePublished?.timeAgo() + + val currentTime = (podcastEpisode.contentEpisodeStatus?.currentTime?.value ?: 0).toInt() + val duration = (podcastEpisode.contentEpisodeStatus?.duration?.value ?: 0).toInt() + + textViewItemEpisodeTime.goneIfFalse(currentTime > 0 || duration > 0) + + val progress = getSeekbarProgress(duration, currentTime) + seekBarCurrentTimeEpisodeProgress.progress = progress + seekBarCurrentTimeEpisodeProgress.goneIfFalse(currentTime > 0) + + if (currentTime > 0 && duration > 0) { + val timeLeft = duration - currentTime + textViewItemEpisodeTime.text = binding.root.context.getString(R.string.time_left, "${timeLeft.toHrAndMin()}") + } else if (duration > 0) { + textViewItemEpisodeTime.text = "${duration.toHrAndMin()}" + } + } + } + + init { + lifecycleOwner.lifecycle.addObserver(this) + } + + } + + init { + lifecycleOwner.lifecycle.addObserver(this) + } + + private fun getSeekbarProgress(duration: Int, currentTime: Int): Int { + return try { + currentTime * 100 / duration + } catch (e: ArithmeticException) { + 0 + } + } +} \ No newline at end of file diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/FeedDownloadedViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/FeedDownloadedViewModel.kt new file mode 100644 index 0000000000..3f78b82443 --- /dev/null +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/FeedDownloadedViewModel.kt @@ -0,0 +1,12 @@ +package chat.sphinx.dashboard.ui.feed + +import chat.sphinx.wrapper_feed.Feed +import chat.sphinx.wrapper_feed.FeedItem +import chat.sphinx.wrapper_podcast.FeedRecommendation +import kotlinx.coroutines.flow.StateFlow + +interface FeedDownloadedViewModel { + val feedDownloadedHolderViewStateFlow: StateFlow> + + fun feedDownloadedSelected(feedItem: FeedItem) +} \ No newline at end of file diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt index 5ce29719a0..baac6720e0 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt @@ -10,6 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import chat.sphinx.concept_image_loader.ImageLoader import chat.sphinx.dashboard.R import chat.sphinx.dashboard.databinding.FragmentFeedAllBinding +import chat.sphinx.dashboard.ui.adapter.FeedDownloadedAdapter import chat.sphinx.dashboard.ui.adapter.FeedFollowingAdapter import chat.sphinx.dashboard.ui.adapter.FeedRecentlyPlayedAdapter import chat.sphinx.dashboard.ui.adapter.FeedRecommendationsAdapter @@ -47,6 +48,7 @@ internal class FeedAllFragment : SideEffectFragment< setupRecommendationsAdapter() setupFollowingAdapter() setupRecentlyPlayedAdapter() + setupDownloadedAdapter() setupRefreshButton() setupNestedScrollView() } @@ -119,6 +121,21 @@ internal class FeedAllFragment : SideEffectFragment< } } + private fun setupDownloadedAdapter() { + binding.recyclerViewDownloaded.apply { + val downloadedAdapter = FeedDownloadedAdapter( + imageLoader, + viewLifecycleOwner, + onStopSupervisor, + viewModel, + viewModel + ) + this.setHasFixedSize(false) + adapter = downloadedAdapter + itemAnimator = null + } + } + override suspend fun onSideEffectCollect(sideEffect: FeedAllSideEffect) { sideEffect.execute(requireActivity()) } diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt index 0efa442f2f..4d43ca54a5 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt @@ -7,6 +7,7 @@ import chat.sphinx.concept_repository_feed.FeedRepository import chat.sphinx.concept_service_media.MediaPlayerServiceController import chat.sphinx.concept_service_media.UserAction import chat.sphinx.dashboard.navigation.DashboardNavigator +import chat.sphinx.dashboard.ui.feed.FeedDownloadedViewModel import chat.sphinx.dashboard.ui.feed.FeedFollowingViewModel import chat.sphinx.dashboard.ui.feed.FeedRecentlyPlayedViewModel import chat.sphinx.dashboard.ui.feed.FeedRecommendationsViewModel @@ -18,6 +19,7 @@ import chat.sphinx.wrapper_common.feed.FeedUrl import chat.sphinx.wrapper_common.feed.isTrue import chat.sphinx.wrapper_common.time import chat.sphinx.wrapper_feed.Feed +import chat.sphinx.wrapper_feed.FeedItem import chat.sphinx.wrapper_podcast.FeedRecommendation import chat.sphinx.wrapper_podcast.Podcast import com.squareup.moshi.Moshi @@ -44,7 +46,11 @@ internal class FeedAllViewModel @Inject constructor( FragmentActivity, FeedAllSideEffect, FeedAllViewState - >(dispatchers, FeedAllViewState.Disabled), FeedFollowingViewModel, FeedRecommendationsViewModel, FeedRecentlyPlayedViewModel + >(dispatchers, FeedAllViewState.Disabled), + FeedFollowingViewModel, + FeedRecommendationsViewModel, + FeedRecentlyPlayedViewModel, + FeedDownloadedViewModel { override val feedRecommendationsHolderViewStateFlow: MutableStateFlow> = MutableStateFlow(emptyList()) @@ -63,6 +69,12 @@ internal class FeedAllViewModel @Inject constructor( .sortedWith(compareByDescending { it.lastPlayed?.time }.thenByDescending { it.lastPublished?.datePublished?.time ?: 0 }) } } + + viewModelScope.launch(mainImmediate) { + feedRepository.getAllDownloadedFeedItems().collect { feedItems -> + _feedDownloadedHolderViewStateFlow.value = feedItems.sortedBy { it.feed?.lastPlayed?.value } + } + } } private fun setRecommendationsVisibility() { @@ -142,6 +154,15 @@ internal class FeedAllViewModel @Inject constructor( feedSelected(feed) } + private val _feedDownloadedHolderViewStateFlow: MutableStateFlow> by lazy { + MutableStateFlow(listOf()) + } + + override val feedDownloadedHolderViewStateFlow: StateFlow> + get() = _feedDownloadedHolderViewStateFlow + + override fun feedDownloadedSelected(feedItem: FeedItem) {} + private fun goToPodcastPlayer( chatId: ChatId, feedId: FeedId, From 830218c87093f0dea5357392e00ec96a694e4fbd Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 3 Jul 2023 18:14:57 -0300 Subject: [PATCH 4/7] Play downloaded episodes from Downloaded section --- .../dashboard/ui/feed/all/FeedAllFragment.kt | 10 ++++ .../dashboard/ui/feed/all/FeedAllViewModel.kt | 54 ++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt index baac6720e0..08efaa0dd0 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt @@ -191,6 +191,16 @@ internal class FeedAllFragment : SideEffectFragment< ) } } + + onStopSupervisor.scope.launch(viewModel.mainImmediate) { + viewModel.feedDownloadedHolderViewStateFlow.collect { list -> + if (list.isEmpty()) { + binding.layoutConstraintDownloadedSection.gone + } else { + binding.layoutConstraintDownloadedSection.visible + } + } + } } private fun toggleElements(contentAvailable: Boolean) { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt index 4d43ca54a5..4d788a3fed 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt @@ -1,5 +1,6 @@ package chat.sphinx.dashboard.ui.feed.all +import android.net.Uri import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope import chat.sphinx.concept_repository_dashboard_android.RepositoryDashboardAndroid @@ -11,6 +12,7 @@ import chat.sphinx.dashboard.ui.feed.FeedDownloadedViewModel import chat.sphinx.dashboard.ui.feed.FeedFollowingViewModel import chat.sphinx.dashboard.ui.feed.FeedRecentlyPlayedViewModel import chat.sphinx.dashboard.ui.feed.FeedRecommendationsViewModel +import chat.sphinx.dashboard.ui.getMediaDuration import chat.sphinx.dashboard.ui.viewstates.FeedAllViewState import chat.sphinx.wrapper_common.dashboard.ChatId import chat.sphinx.wrapper_common.feed.FeedId @@ -20,8 +22,10 @@ import chat.sphinx.wrapper_common.feed.isTrue import chat.sphinx.wrapper_common.time import chat.sphinx.wrapper_feed.Feed import chat.sphinx.wrapper_feed.FeedItem +import chat.sphinx.wrapper_feed.FeedItemDuration import chat.sphinx.wrapper_podcast.FeedRecommendation import chat.sphinx.wrapper_podcast.Podcast +import chat.sphinx.wrapper_podcast.PodcastEpisode import com.squareup.moshi.Moshi import dagger.hilt.android.lifecycle.HiltViewModel import io.matthewnelson.android_feature_viewmodel.SideEffectViewModel @@ -161,7 +165,55 @@ internal class FeedAllViewModel @Inject constructor( override val feedDownloadedHolderViewStateFlow: StateFlow> get() = _feedDownloadedHolderViewStateFlow - override fun feedDownloadedSelected(feedItem: FeedItem) {} + override fun feedDownloadedSelected(feedItem: FeedItem) { + viewModelScope.launch(mainImmediate) { + feedRepository.getPodcastById(feedItem.feedId).firstOrNull()?.let { podcast -> + podcast.getEpisodeWithId(feedItem.id.value)?.let { episode -> + + podcast.willStartPlayingEpisode( + episode, + episode.currentTimeMilliseconds ?: 0, + ::retrieveEpisodeDuration + ) + + dashboardNavigator.toPodcastPlayerScreen( + feedItem.feed?.chatId ?: ChatId(ChatId.NULL_CHAT_ID.toLong()), + episode.podcastId, + podcast.feedUrl + ) + + mediaPlayerServiceController.submitAction( + UserAction.ServiceAction.Play( + feedItem.feed?.chatId ?: ChatId(ChatId.NULL_CHAT_ID.toLong()), + episode.episodeUrl, + podcast.getUpdatedContentFeedStatus(), + podcast.getUpdatedContentEpisodeStatus() + ) + ) + } + } + } + } + + private fun retrieveEpisodeDuration( + episode: PodcastEpisode + ): Long { + val duration = episode.localFile?.let { + Uri.fromFile(it).getMediaDuration(true) + } ?: Uri.parse(episode.episodeUrl).getMediaDuration(false) + + viewModelScope.launch(io) { + feedRepository.updateContentEpisodeStatus( + feedId = episode.podcastId, + itemId = episode.id, + FeedItemDuration(duration / 1000), + FeedItemDuration(episode.currentTimeSeconds) + ) + } + + return duration + } + private fun goToPodcastPlayer( chatId: ChatId, From 8607248398660aff937fca82297069182d54ebd1 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 3 Jul 2023 18:19:40 -0300 Subject: [PATCH 5/7] Implement Downloaded section --- .../java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt index 08efaa0dd0..42bb79e043 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt @@ -216,4 +216,5 @@ internal class FeedAllFragment : SideEffectFragment< } } } + } From 106727e9786426f5b591e660e48800de08eb3cfd Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 12 Jul 2023 12:40:38 -0300 Subject: [PATCH 6/7] Improvements on downloaded content section - Showing just downloaded episodes when coming from new section --- .../primary/ContactChatNavigatorImpl.kt | 2 +- .../primary/DashboardNavigatorImpl.kt | 9 ++- .../primary/GroupChatNavigatorImpl.kt | 2 +- .../primary/TribeChatNavigatorImpl.kt | 11 ++- .../chat/sphinx/wrapper_podcast/Podcast.kt | 16 ++++ .../feature_repository/SphinxRepository.kt | 67 +++++----------- .../navigation/PodcastPlayerNavigator.kt | 3 +- .../navigation/ToPodcastPlayerScreen.kt | 4 +- .../ui/PodcastPlayerViewModel.kt | 30 +++++-- .../ui/adapter/PodcastEpisodesListAdapter.kt | 18 ++++- .../ui/viewstates/PodcastPlayerViewState.kt | 7 +- .../res/layout/layout_episode_list_footer.xml | 2 +- .../navigation/podcast_player_nav_graph.xml | 3 + .../navigation/TribeChatNavigator.kt | 8 +- .../navigation/DashboardNavigator.kt | 7 +- .../dashboard/ui/feed/all/FeedAllViewModel.kt | 78 ++++++++++++++----- .../ui/feed/listen/FeedListenViewModel.kt | 4 +- .../src/main/res/values-b+fil/strings.xml | 2 +- .../src/main/res/values-es/strings.xml | 2 +- .../src/main/res/values-ja/strings.xml | 2 +- .../src/main/res/values-zh/strings.xml | 2 +- .../dashboard/src/main/res/values/strings.xml | 2 +- 22 files changed, 188 insertions(+), 93 deletions(-) diff --git a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/ContactChatNavigatorImpl.kt b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/ContactChatNavigatorImpl.kt index 9d987f7d96..acbb4b979f 100644 --- a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/ContactChatNavigatorImpl.kt +++ b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/ContactChatNavigatorImpl.kt @@ -78,7 +78,7 @@ internal class ContactChatNavigatorImpl @Inject constructor( } override suspend fun toPodcastPlayer(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) { - detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, true)) + detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, fromFeed = false, fromDownloaded = false)) } override suspend fun toChatContact(chatId: ChatId?, contactId: ContactId) { diff --git a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/DashboardNavigatorImpl.kt b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/DashboardNavigatorImpl.kt index ae0662abe1..85957df225 100644 --- a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/DashboardNavigatorImpl.kt +++ b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/DashboardNavigatorImpl.kt @@ -84,8 +84,13 @@ internal class DashboardNavigatorImpl @Inject constructor( ) } - override suspend fun toPodcastPlayerScreen(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) { - detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, true)) + override suspend fun toPodcastPlayerScreen( + chatId: ChatId, + feedId: FeedId, + feedUrl: FeedUrl, + fromDownloadedSection: Boolean + ) { + detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, true, fromDownloadedSection)) } override suspend fun toCommonPlayerScreen(podcastId: FeedId, episodeId: FeedId) { diff --git a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/GroupChatNavigatorImpl.kt b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/GroupChatNavigatorImpl.kt index 02f54afac5..a303c7dbb2 100644 --- a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/GroupChatNavigatorImpl.kt +++ b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/GroupChatNavigatorImpl.kt @@ -104,6 +104,6 @@ internal class GroupChatNavigatorImpl @Inject constructor( } override suspend fun toPodcastPlayer(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) { - detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, true)) + detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, fromFeed = false, fromDownloaded = false)) } } diff --git a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/TribeChatNavigatorImpl.kt b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/TribeChatNavigatorImpl.kt index fe52d17488..60fe167fa9 100644 --- a/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/TribeChatNavigatorImpl.kt +++ b/sphinx/activity/main/activitymain/src/main/java/chat/sphinx/activitymain/navigation/navigators/primary/TribeChatNavigatorImpl.kt @@ -50,8 +50,13 @@ internal class TribeChatNavigatorImpl @Inject constructor( detailDriver.submitNavigationRequest(ToPaymentReceiveDetail(contactId, chatId)) } - override suspend fun toPodcastPlayerScreen(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) { - detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, false)) + override suspend fun toPodcastPlayerScreen( + chatId: ChatId, + feedId: FeedId, + feedUrl: FeedUrl, + fromDownloadedSection: Boolean + ) { + detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, fromFeed = false, fromDownloaded = false)) } override suspend fun toTribeDetailScreen(chatId: ChatId) { @@ -139,6 +144,6 @@ internal class TribeChatNavigatorImpl @Inject constructor( } override suspend fun toPodcastPlayer(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) { - detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, true)) + detailDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, fromFeed = false, fromDownloaded = false)) } } diff --git a/sphinx/application/common/wrappers/wrapper-podcast/src/main/java/chat/sphinx/wrapper_podcast/Podcast.kt b/sphinx/application/common/wrappers/wrapper-podcast/src/main/java/chat/sphinx/wrapper_podcast/Podcast.kt index d7b85e86c4..9767f2e245 100644 --- a/sphinx/application/common/wrappers/wrapper-podcast/src/main/java/chat/sphinx/wrapper_podcast/Podcast.kt +++ b/sphinx/application/common/wrappers/wrapper-podcast/src/main/java/chat/sphinx/wrapper_podcast/Podcast.kt @@ -200,6 +200,22 @@ data class Podcast( return episodesList } + fun getDownloadedEpisodesListCopy(): ArrayList { + var episodesList = ArrayList() + + for (episode in this.episodes) { + if (episode.downloaded) { + val episodeCopy = episode.copy() + episodeCopy.playing = episode.playing + episodeCopy.contentEpisodeStatus = episode.contentEpisodeStatus + episodeCopy.played = episode.played + + episodesList.add(episodeCopy) + } + } + return episodesList + } + fun setCurrentEpisodeWith(episodeId: String) { this.playingEpisode?.playing = false diff --git a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt index 4810124a14..d933e962bd 100644 --- a/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt +++ b/sphinx/application/data/features/feature-repository/src/main/java/chat/sphinx/feature_repository/SphinxRepository.kt @@ -4131,10 +4131,10 @@ abstract class SphinxRepository( .asFlow() .mapToList(io) .map { listFeedItemDbo -> - val feedItems = listFeedItemDbo.map { - feedItemDboPresenterMapper.mapFrom(it) - } - assignFeedToFeedItemList(feedItems, coreDB.getSphinxDatabaseQueries()) + mapFeedItemDboList( + listFeedItemDbo, + coreDB.getSphinxDatabaseQueries() + ) } .distinctUntilChanged() ) @@ -4504,21 +4504,13 @@ abstract class SphinxRepository( return feedItem } - private val podcastDboPresenterMapper: FeedDboPodcastPresenterMapper by lazy { - FeedDboPodcastPresenterMapper(dispatchers) - } - - private val podcastEpisodeDboPresenterMapper: FeedItemDboPodcastEpisodePresenterMapper by lazy { - FeedItemDboPodcastEpisodePresenterMapper(dispatchers) - } - - private suspend fun assignFeedToFeedItemList( - feedItems: List, + private suspend fun mapFeedItemDboList( + listFeedItemDbo: List, queries: SphinxDatabaseQueries ): List { - val feedsMap: MutableMap = mutableMapOf() - val feedIds = feedItems.map { it.feedId }.distinct() + val feedsMap: MutableMap = mutableMapOf() + val feedIds = listFeedItemDbo.map { it.feed_id }.distinct() queries.feedGetByIds(feedIds) .executeAsList() @@ -4529,6 +4521,12 @@ abstract class SphinxRepository( } } + val feedItems = listFeedItemDbo.map { + feedItemDboPresenterMapper.mapFrom(it).apply { + it.feed_id + } + } + feedItems.forEach { item -> item.feed = feedsMap[item.feedId] } @@ -4536,6 +4534,13 @@ abstract class SphinxRepository( return feedItems } + private val podcastDboPresenterMapper: FeedDboPodcastPresenterMapper by lazy { + FeedDboPodcastPresenterMapper(dispatchers) + } + + private val podcastEpisodeDboPresenterMapper: FeedItemDboPodcastEpisodePresenterMapper by lazy { + FeedItemDboPodcastEpisodePresenterMapper(dispatchers) + } override fun getPodcastByChatId(chatId: ChatId): Flow = flow { val queries = coreDB.getSphinxDatabaseQueries() @@ -4755,36 +4760,6 @@ abstract class SphinxRepository( } } - private suspend fun mapFeedItemDboList( - listFeedItemDbo: List, - queries: SphinxDatabaseQueries - ): List { - val feedsMap: MutableMap> = - LinkedHashMap(listFeedItemDbo.size) - - for (dbo in listFeedItemDbo) { - feedsMap[dbo.feed_id] = ArrayList(0) - } - - feedsMap.keys.chunked(500).forEach { chunkedFeedIds -> - queries.feedGetAllByIds(chunkedFeedIds) - .executeAsList() - .let { response -> - response.forEach { dbo -> - feedsMap[dbo.id]?.add( - feedDboPresenterMapper.mapFrom(dbo) - ) - } - } - } - - return listFeedItemDbo.map { - feedItemDboPresenterMapper.mapFrom(it).apply { - it.feed_id - } - } - } - override val networkRefreshFeedContent: Flow> by lazy { flow { diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/PodcastPlayerNavigator.kt b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/PodcastPlayerNavigator.kt index 6e92fb5e26..8eaec33fe8 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/PodcastPlayerNavigator.kt +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/PodcastPlayerNavigator.kt @@ -18,8 +18,9 @@ abstract class PodcastPlayerNavigator( chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl, + fromDownloadedSection: Boolean = false ) { - navigationDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, false)) + navigationDriver.submitNavigationRequest(ToPodcastPlayerScreen(chatId, feedId, feedUrl, false, fromDownloadedSection)) } @JvmSynthetic diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/ToPodcastPlayerScreen.kt b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/ToPodcastPlayerScreen.kt index 9b453020a2..357261c902 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/ToPodcastPlayerScreen.kt +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/navigation/ToPodcastPlayerScreen.kt @@ -15,6 +15,7 @@ class ToPodcastPlayerScreen( private val feedId: FeedId, private val feedUrl: FeedUrl, private val fromFeed: Boolean, + private val fromDownloaded: Boolean, private val options: NavOptions = DetailNavOptions.defaultBuilt ) : NavigationRequest() { @@ -25,7 +26,8 @@ class ToPodcastPlayerScreen( chatId.value, feedId.value, feedUrl.value, - fromFeed + fromFeed, + fromDownloaded ).build().toBundle(), options ) diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/PodcastPlayerViewModel.kt b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/PodcastPlayerViewModel.kt index 2c067c742a..deadb80f20 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/PodcastPlayerViewModel.kt +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/PodcastPlayerViewModel.kt @@ -89,6 +89,9 @@ internal inline val PodcastPlayerFragmentArgs.feedId: FeedId internal inline val PodcastPlayerFragmentArgs.fromFeed: Boolean get() = argFromFeed +internal inline val PodcastPlayerFragmentArgs.fromDownloaded: Boolean + get() = argFromDownloaded + @HiltViewModel internal class PodcastPlayerViewModel @Inject constructor( dispatchers: CoroutineDispatchers, @@ -205,6 +208,7 @@ internal class PodcastPlayerViewModel @Inject constructor( viewStateContainer.updateViewState( PodcastPlayerViewState.MediaStateUpdate( podcast, + args.fromDownloaded, serviceState ) ) @@ -215,6 +219,7 @@ internal class PodcastPlayerViewModel @Inject constructor( viewStateContainer.updateViewState( PodcastPlayerViewState.MediaStateUpdate( podcast, + args.fromDownloaded, serviceState ) ) @@ -227,6 +232,7 @@ internal class PodcastPlayerViewModel @Inject constructor( viewStateContainer.updateViewState( PodcastPlayerViewState.MediaStateUpdate( podcast, + args.fromDownloaded, serviceState ) ) @@ -357,7 +363,10 @@ internal class PodcastPlayerViewModel @Inject constructor( podcast.applyPlayingContentState(playingContent) viewStateContainer.updateViewState( - PodcastPlayerViewState.PodcastLoaded(podcast) + PodcastPlayerViewState.PodcastLoaded( + podcast, + args.fromDownloaded + ) ) val contentFeedStatus = podcast.getUpdatedContentFeedStatus() @@ -453,7 +462,8 @@ internal class PodcastPlayerViewModel @Inject constructor( viewStateContainer.updateViewState( PodcastPlayerViewState.EpisodePlayed( - podcast + podcast, + args.fromDownloaded ) ) @@ -861,7 +871,10 @@ internal class PodcastPlayerViewModel @Inject constructor( viewState.podcast.forceUpdate = !viewState.podcast.forceUpdate viewStateContainer.updateViewState( - PodcastPlayerViewState.PodcastLoaded(viewState.podcast) + PodcastPlayerViewState.PodcastLoaded( + viewState.podcast, + args.fromDownloaded + ) ) Log.d("isAudioComingOut", "UpdateState! PodcastLoaded") } @@ -869,7 +882,10 @@ internal class PodcastPlayerViewModel @Inject constructor( viewState.podcast.forceUpdate = !viewState.podcast.forceUpdate viewStateContainer.updateViewState( - PodcastPlayerViewState.EpisodePlayed(viewState.podcast) + PodcastPlayerViewState.EpisodePlayed( + viewState.podcast, + args.fromDownloaded + ) ) Log.d("isAudioComingOut", "UpdateState! EpisodePlayed ") } @@ -877,7 +893,11 @@ internal class PodcastPlayerViewModel @Inject constructor( viewState.podcast.forceUpdate = !viewState.podcast.forceUpdate viewStateContainer.updateViewState( - PodcastPlayerViewState.MediaStateUpdate(viewState.podcast, viewState.state) + PodcastPlayerViewState.MediaStateUpdate( + viewState.podcast, + args.fromDownloaded, + viewState.state + ) ) Log.d("isAudioComingOut", "UpdateState! MediaStateUpdate") } diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/adapter/PodcastEpisodesListAdapter.kt b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/adapter/PodcastEpisodesListAdapter.kt index 612a824067..68e645e335 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/adapter/PodcastEpisodesListAdapter.kt +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/adapter/PodcastEpisodesListAdapter.kt @@ -112,11 +112,19 @@ internal class PodcastEpisodesListAdapter( var episodes = ArrayList() if (viewState is PodcastPlayerViewState.PodcastLoaded) { - episodes = viewState.podcast.getEpisodesListCopy() + episodes = if (viewState.downloadedOnly) { + viewState.podcast.getDownloadedEpisodesListCopy() + } else { + viewState.podcast.getEpisodesListCopy() + } } if (viewState is PodcastPlayerViewState.EpisodePlayed) { - episodes = viewState.podcast.getEpisodesListCopy() + episodes = if (viewState.downloadedOnly) { + viewState.podcast.getDownloadedEpisodesListCopy() + } else { + viewState.podcast.getEpisodesListCopy() + } } if (viewState is PodcastPlayerViewState.MediaStateUpdate) { @@ -125,7 +133,11 @@ internal class PodcastEpisodesListAdapter( viewState.state is MediaPlayerServiceState.ServiceActive.MediaState.Ended || viewState.state is MediaPlayerServiceState.ServiceActive.MediaState.Playing ) { - episodes = viewState.podcast.getEpisodesListCopy() + episodes = if (viewState.downloadedOnly) { + viewState.podcast.getDownloadedEpisodesListCopy() + } else { + viewState.podcast.getEpisodesListCopy() + } } } diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/viewstates/PodcastPlayerViewState.kt b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/viewstates/PodcastPlayerViewState.kt index 163e768720..d7cc8ff8f5 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/viewstates/PodcastPlayerViewState.kt +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/java/chat/sphinx/podcast_player/ui/viewstates/PodcastPlayerViewState.kt @@ -13,18 +13,21 @@ internal sealed class PodcastPlayerViewState: ViewState( class PodcastLoaded( val podcast: Podcast, + val downloadedOnly: Boolean ): PodcastPlayerViewState() class LoadingEpisode( - val episode: PodcastEpisode + val episode: PodcastEpisode, ): PodcastPlayerViewState() class EpisodePlayed( - val podcast: Podcast + val podcast: Podcast, + val downloadedOnly: Boolean ): PodcastPlayerViewState() class MediaStateUpdate( val podcast: Podcast, + val downloadedOnly: Boolean, val state: MediaPlayerServiceState.ServiceActive.MediaState ): PodcastPlayerViewState() } diff --git a/sphinx/screens-detail/podcast-player/podcast-player/src/main/res/layout/layout_episode_list_footer.xml b/sphinx/screens-detail/podcast-player/podcast-player/src/main/res/layout/layout_episode_list_footer.xml index a0ca73e624..a18f20faa7 100644 --- a/sphinx/screens-detail/podcast-player/podcast-player/src/main/res/layout/layout_episode_list_footer.xml +++ b/sphinx/screens-detail/podcast-player/podcast-player/src/main/res/layout/layout_episode_list_footer.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/body"> + android:background="@color/headerBG"> + diff --git a/sphinx/screens/chats/chat-tribe/chat-tribe/src/main/java/chat/sphinx/chat_tribe/navigation/TribeChatNavigator.kt b/sphinx/screens/chats/chat-tribe/chat-tribe/src/main/java/chat/sphinx/chat_tribe/navigation/TribeChatNavigator.kt index 7873c99884..ca407d3bb5 100644 --- a/sphinx/screens/chats/chat-tribe/chat-tribe/src/main/java/chat/sphinx/chat_tribe/navigation/TribeChatNavigator.kt +++ b/sphinx/screens/chats/chat-tribe/chat-tribe/src/main/java/chat/sphinx/chat_tribe/navigation/TribeChatNavigator.kt @@ -11,7 +11,13 @@ abstract class TribeChatNavigator( navigationDriver: BaseNavigationDriver ): ChatNavigator(navigationDriver) { - abstract suspend fun toPodcastPlayerScreen(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) + abstract suspend fun toPodcastPlayerScreen( + chatId: ChatId, + feedId: FeedId, + feedUrl: FeedUrl, + fromDownloadedSection: Boolean = false + ) + abstract suspend fun toTribeDetailScreen(chatId: ChatId) abstract suspend fun toShareTribeScreen( diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/navigation/DashboardNavigator.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/navigation/DashboardNavigator.kt index 9603386c4f..131e4a5b6b 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/navigation/DashboardNavigator.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/navigation/DashboardNavigator.kt @@ -38,7 +38,12 @@ abstract class DashboardNavigator( abstract suspend fun toNewsletterDetail(chatId: ChatId, feedUrl: FeedUrl) - abstract suspend fun toPodcastPlayerScreen(chatId: ChatId, feedId: FeedId, feedUrl: FeedUrl) + abstract suspend fun toPodcastPlayerScreen( + chatId: ChatId, + feedId: FeedId, + feedUrl: FeedUrl, + fromDownloadedSection: Boolean = false + ) abstract suspend fun toCommonPlayerScreen( podcastId: FeedId, diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt index 4d788a3fed..bca5ed97d2 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt @@ -32,7 +32,11 @@ import io.matthewnelson.android_feature_viewmodel.SideEffectViewModel import io.matthewnelson.android_feature_viewmodel.submitSideEffect import io.matthewnelson.android_feature_viewmodel.updateViewState import io.matthewnelson.concept_coroutines.CoroutineDispatchers -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import javax.annotation.meta.Exhaustive import javax.inject.Inject @@ -166,28 +170,47 @@ internal class FeedAllViewModel @Inject constructor( get() = _feedDownloadedHolderViewStateFlow override fun feedDownloadedSelected(feedItem: FeedItem) { - viewModelScope.launch(mainImmediate) { - feedRepository.getPodcastById(feedItem.feedId).firstOrNull()?.let { podcast -> - podcast.getEpisodeWithId(feedItem.id.value)?.let { episode -> + feedItem.feed?.let { feed -> + viewModelScope.launch(mainImmediate) { - podcast.willStartPlayingEpisode( - episode, - episode.currentTimeMilliseconds ?: 0, - ::retrieveEpisodeDuration - ) + //Pause if playing + pausePlayingIfNeeded( + feed, + feedItem + ) - dashboardNavigator.toPodcastPlayerScreen( - feedItem.feed?.chatId ?: ChatId(ChatId.NULL_CHAT_ID.toLong()), - episode.podcastId, - podcast.feedUrl - ) + delay(50L) + //Set new episode + setEpisodeOnFeed( + feed, + feedItem + ) + + dashboardNavigator.toPodcastPlayerScreen( + feed.chat?.id ?: feed.chatId, + feed.id, + feed.feedUrl, + true + ) + } + } + } + + private suspend fun pausePlayingIfNeeded( + feed: Feed, + episode: FeedItem + ) { + mediaPlayerServiceController.getPlayingContent()?.let { playingContent -> + if ( + playingContent.first == feed.id.value && + playingContent.second != episode.id.value + ) { + viewModelScope.launch(mainImmediate) { mediaPlayerServiceController.submitAction( - UserAction.ServiceAction.Play( - feedItem.feed?.chatId ?: ChatId(ChatId.NULL_CHAT_ID.toLong()), - episode.episodeUrl, - podcast.getUpdatedContentFeedStatus(), - podcast.getUpdatedContentEpisodeStatus() + UserAction.ServiceAction.Pause( + feed.chatId, + playingContent.second ) ) } @@ -195,6 +218,23 @@ internal class FeedAllViewModel @Inject constructor( } } + private fun setEpisodeOnFeed( + feed: Feed, + episode: FeedItem + ) { + feed?.getNNContentFeedStatus()?.let { contentFeedStatus -> + feedRepository.updateContentFeedStatus( + feed.id, + contentFeedStatus.feedUrl, + contentFeedStatus.subscriptionStatus, + contentFeedStatus.chatId, + episode.id, + contentFeedStatus.satsPerMinute, + contentFeedStatus.playerSpeed + ) + } + } + private fun retrieveEpisodeDuration( episode: PodcastEpisode ): Long { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt index bb2f06ef65..e975933680 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt @@ -22,7 +22,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import io.matthewnelson.android_feature_viewmodel.SideEffectViewModel import io.matthewnelson.concept_coroutines.CoroutineDispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml index 3ecf573a60..2ef262bc94 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-b+fil/strings.xml @@ -102,7 +102,7 @@ Kamakailang Nilalaro Basahin na Ngayon Manood na Ngayon - Downloaded + Downloaded Content Mga rekomendasyon No \"Feed\" Results Found Walang \"Makinig\" Natagpuan ang mga Resulta diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml index ca3c35e8c6..bbd48b392e 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-es/strings.xml @@ -129,7 +129,7 @@ Read Now Watch Now - Descargado + Contenido Descargado Recomendaciones Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml index 25ed5369c5..99bc27d01e 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-ja/strings.xml @@ -128,7 +128,7 @@ Read Now Watch Now - Downloaded + Downloaded Content Recommendations Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml index 13957b07a9..8cbb54f162 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values-zh/strings.xml @@ -128,7 +128,7 @@ Read Now Watch Now - Downloaded + Downloaded Content Recommendations Refresh diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml b/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml index a87d8fa616..fd417875c8 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/values/strings.xml @@ -127,7 +127,7 @@ Recently Read Read Now Watch Now - Downloaded + Downloaded Content Recommendations From 2b8fe7f03abc2008baa6546ba1045d9def77282d Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 12 Jul 2023 13:03:03 -0300 Subject: [PATCH 7/7] Showing just played content on recently played --- .../dashboard/ui/feed/all/FeedAllFragment.kt | 10 +++ .../dashboard/ui/feed/all/FeedAllViewModel.kt | 1 + .../ui/feed/listen/FeedListenFragment.kt | 12 +++ .../ui/feed/listen/FeedListenViewModel.kt | 1 + .../ui/feed/read/FeedReadFragment.kt | 12 +++ .../ui/feed/read/FeedReadViewModel.kt | 1 + .../ui/feed/watch/FeedWatchFragment.kt | 12 +++ .../ui/feed/watch/FeedWatchViewModel.kt | 1 + .../main/res/layout/fragment_feed_listen.xml | 10 ++- .../main/res/layout/fragment_feed_read.xml | 13 +++- .../main/res/layout/fragment_feed_watch.xml | 74 ++++++++++--------- 11 files changed, 111 insertions(+), 36 deletions(-) diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt index 42bb79e043..107282a289 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllFragment.kt @@ -201,6 +201,16 @@ internal class FeedAllFragment : SideEffectFragment< } } } + + onStopSupervisor.scope.launch(viewModel.mainImmediate) { + viewModel.lastPlayedFeedsHolderViewStateFlow.collect { list -> + if (list.isEmpty()) { + binding.layoutConstraintRecentlyPlayed.gone + } else { + binding.layoutConstraintRecentlyPlayed.visible + } + } + } } private fun toggleElements(contentAvailable: Boolean) { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt index bca5ed97d2..7ffde92705 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/all/FeedAllViewModel.kt @@ -74,6 +74,7 @@ internal class FeedAllViewModel @Inject constructor( .sortedByDescending { it.lastPublished?.datePublished?.time ?: 0 } _lastPlayedFeedsHolderViewStateFlow.value = feeds.toList() + .filter { it.lastPlayed != null } .sortedWith(compareByDescending { it.lastPlayed?.time }.thenByDescending { it.lastPublished?.datePublished?.time ?: 0 }) } } diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenFragment.kt index 3be01b9392..e07e3d95da 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenFragment.kt @@ -21,7 +21,9 @@ import chat.sphinx.dashboard.ui.placeholder.PlaceholderContent import chat.sphinx.dashboard.ui.viewstates.FeedListenViewState import dagger.hilt.android.AndroidEntryPoint import io.matthewnelson.android_feature_screens.ui.sideeffect.SideEffectFragment +import io.matthewnelson.android_feature_screens.util.gone import io.matthewnelson.android_feature_screens.util.goneIfFalse +import io.matthewnelson.android_feature_screens.util.visible import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @@ -121,6 +123,16 @@ internal class FeedListenFragment : SideEffectFragment< ) } } + + onStopSupervisor.scope.launch(viewModel.mainImmediate) { + viewModel.lastPlayedFeedsHolderViewStateFlow.collect { list -> + if (list.isEmpty()) { + binding.layoutConstraintRecentlyPlayed.gone + } else { + binding.layoutConstraintRecentlyPlayed.visible + } + } + } } private fun toggleElements(contentAvailable: Boolean) { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt index e975933680..36df1a7337 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/listen/FeedListenViewModel.kt @@ -65,6 +65,7 @@ class FeedListenViewModel @Inject constructor( .sortedByDescending { it.lastPublished?.datePublished?.time ?: 0 } _lastPlayedFeedsHolderViewStateFlow.value = feeds.toList() + .filter { it.lastPlayed != null } .sortedWith(compareByDescending { it.lastPlayed?.time }.thenByDescending { it.lastPublished?.datePublished?.time ?: 0 }) } } diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadFragment.kt index 7c0a754e94..1bedfc6494 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadFragment.kt @@ -16,7 +16,9 @@ import chat.sphinx.dashboard.ui.feed.FeedFragment import chat.sphinx.dashboard.ui.viewstates.FeedReadViewState import dagger.hilt.android.AndroidEntryPoint import io.matthewnelson.android_feature_screens.ui.sideeffect.SideEffectFragment +import io.matthewnelson.android_feature_screens.util.gone import io.matthewnelson.android_feature_screens.util.goneIfFalse +import io.matthewnelson.android_feature_screens.util.visible import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @@ -115,6 +117,16 @@ internal class FeedReadFragment : SideEffectFragment< ) } } + + onStopSupervisor.scope.launch(viewModel.mainImmediate) { + viewModel.lastPlayedFeedsHolderViewStateFlow.collect { list -> + if (list.isEmpty()) { + binding.layoutConstraintRecentlyPlayed.gone + } else { + binding.layoutConstraintRecentlyPlayed.visible + } + } + } } private fun toggleElements(contentAvailable: Boolean) { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadViewModel.kt index 5e5a310f0e..9b43d54caa 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/read/FeedReadViewModel.kt @@ -60,6 +60,7 @@ class FeedReadViewModel @Inject constructor( .sortedByDescending { it.lastPublished?.datePublished?.time ?: 0 } _lastPlayedFeedsHolderViewStateFlow.value = feeds.toList() + .filter { it.lastPlayed != null } .sortedWith(compareByDescending { it.lastPlayed?.time }.thenByDescending { it.lastPublished?.datePublished?.time ?: 0 }) } } diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchFragment.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchFragment.kt index 6b23bc9f74..3ec95dbe62 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchFragment.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchFragment.kt @@ -18,7 +18,9 @@ import chat.sphinx.dashboard.ui.feed.FeedRecentlyPlayedViewModel import chat.sphinx.dashboard.ui.viewstates.FeedWatchViewState import dagger.hilt.android.AndroidEntryPoint import io.matthewnelson.android_feature_screens.ui.sideeffect.SideEffectFragment +import io.matthewnelson.android_feature_screens.util.gone import io.matthewnelson.android_feature_screens.util.goneIfFalse +import io.matthewnelson.android_feature_screens.util.visible import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @@ -116,6 +118,16 @@ internal class FeedWatchFragment : SideEffectFragment< ) } } + + onStopSupervisor.scope.launch(viewModel.mainImmediate) { + viewModel.lastPlayedFeedsHolderViewStateFlow.collect { list -> + if (list.isEmpty()) { + binding.layoutConstraintRecentlyPlayed.gone + } else { + binding.layoutConstraintRecentlyPlayed.visible + } + } + } } private fun toggleElements(contentAvailable: Boolean) { diff --git a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchViewModel.kt b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchViewModel.kt index ea72c25a75..ebd729f14a 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchViewModel.kt +++ b/sphinx/screens/dashboard/dashboard/src/main/java/chat/sphinx/dashboard/ui/feed/watch/FeedWatchViewModel.kt @@ -57,6 +57,7 @@ class FeedWatchViewModel @Inject constructor( .sortedByDescending { it.lastPublished?.datePublished?.time ?: 0 } _lastPlayedFeedsHolderViewStateFlow.value = feeds.toList() + .filter { it.lastPlayed != null } .sortedWith(compareByDescending { it.lastPlayed?.time }.thenByDescending { it.lastPublished?.datePublished?.time ?: 0 }) } } diff --git a/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_listen.xml b/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_listen.xml index d17588df2f..52c34b6f8e 100644 --- a/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_listen.xml +++ b/sphinx/screens/dashboard/dashboard/src/main/res/layout/fragment_feed_listen.xml @@ -56,6 +56,12 @@ app:layout_constraintTop_toBottomOf="@+id/text_view_listen_now_header" tools:listitem="@layout/layout_feed_squared_row_holder" /> + + + + + android:layout_height="match_parent" + android:background="@color/headerBG"> + + + + - + app:layout_constraintTop_toBottomOf="@id/recycler_view_watch_now"> - + - + + + + +