Skip to content

Commit

Permalink
Merge branch 'main' into merge/release-7.76-rc2-into-main
Browse files Browse the repository at this point in the history
  • Loading branch information
mebarbosa authored Oct 31, 2024
2 parents 7092581 + 43c0e4b commit a3593d9
Show file tree
Hide file tree
Showing 37 changed files with 2,148 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ class AppDatabaseTest {
AppDatabase.MIGRATION_99_100,
AppDatabase.MIGRATION_100_101,
AppDatabase.MIGRATION_101_102,
// 102 to 103 added via auto migration
AppDatabase.MIGRATION_103_104,
)
.build()
// close the database and release any stream resources when the test finishes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ private fun Content(
OnboardingUpgradeSource.END_OF_YEAR,
OnboardingUpgradeSource.FILES,
OnboardingUpgradeSource.FOLDERS,
OnboardingUpgradeSource.FOLDERS_PODCAST_SCREEN,
OnboardingUpgradeSource.HEADPHONE_CONTROLS_SETTINGS,
OnboardingUpgradeSource.LOGIN,
OnboardingUpgradeSource.LOGIN_PLUS_PROMOTION,
Expand All @@ -234,6 +235,7 @@ private fun Content(
OnboardingUpgradeSource.SETTINGS,
OnboardingUpgradeSource.SLUMBER_STUDIOS,
OnboardingUpgradeSource.WHATS_NEW_SKIP_CHAPTERS,
OnboardingUpgradeSource.UP_NEXT_SHUFFLE,
OnboardingUpgradeSource.UNKNOWN,
-> false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ sealed class UpgradeFeatureCard(
)
-> LR.string.skip_chapters_plus_prompt

source == OnboardingUpgradeSource.UP_NEXT_SHUFFLE &&
SubscriptionTier.fromFeatureTier(Feature.UP_NEXT_SHUFFLE) == SubscriptionTier.PLUS
-> LR.string.up_next_shuffle_plus_prompt

else -> LR.string.onboarding_plus_features_title
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ enum class PlusUpgradeFeatureItem(
title = LR.string.onboarding_plus_feature_desktop_and_web_apps_title,
),
Folders(
image = IR.drawable.ic_folders,
image = IR.drawable.ic_folder,
title = LR.string.onboarding_plus_feature_folders_and_bookmarks_title,
),
SkipChapters(
Expand Down Expand Up @@ -115,7 +115,7 @@ enum class PlusUpgradeLayoutReviewsItem(
title = LR.string.onboarding_plus_feature_desktop_and_web_apps_title,
),
Folders(
image = IR.drawable.ic_folders,
image = IR.drawable.ic_folder,
title = LR.string.onboarding_plus_feature_folders_and_bookmarks_title,
),
SkipChapters(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package au.com.shiftyjelly.pocketcasts.discover.worker

import au.com.shiftyjelly.pocketcasts.models.entity.CuratedPodcast
import au.com.shiftyjelly.pocketcasts.servers.BuildConfig
import au.com.shiftyjelly.pocketcasts.servers.model.DiscoverRow
import au.com.shiftyjelly.pocketcasts.servers.model.ListFeed
import au.com.shiftyjelly.pocketcasts.servers.model.ListType
Expand All @@ -13,14 +14,19 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber

class CuratedPodcastsCrawler @Inject constructor(
class CuratedPodcastsCrawler(
private val service: ListWebService,
private val staticHostUrl: String,
) {
@Inject constructor(
service: ListWebService,
) : this(service, BuildConfig.SERVER_STATIC_URL)

suspend fun crawl(platform: String): Result<List<CuratedPodcast>> = coroutineScope {
runCatching { service.getDiscoverFeedSuspend(platform) }.mapCatching { discover ->
val feeds = discover.layout
.filterDisplayablePodcasts()
.map { fetchFeed(it.source) }
.mapNotNull { row -> row.id?.let { id -> fetchFeed(id, row.source) } }
.awaitAll()
feeds.forEach { feed ->
feed.onFailure { Timber.d(it, "Failed to fetch a feed") }
Expand Down Expand Up @@ -56,7 +62,12 @@ class CuratedPodcastsCrawler @Inject constructor(
(isSpecialList || row.curated) && !row.sponsored && row.type == ListType.PodcastList
}

private fun CoroutineScope.fetchFeed(url: String): Deferred<Result<ListFeed>> {
return async { runCatching { service.getListFeedSuspend(url) } }
private fun CoroutineScope.fetchFeed(id: String, url: String): Deferred<Result<ListFeed>> {
val engageUrl = if (id == CuratedPodcast.FEATURED_LIST_ID) {
"$staticHostUrl/engage/featured.json"
} else {
url
}
return async { runCatching { service.getListFeedSuspend(engageUrl) } }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class CuratedPodcastsCrawlerTest {
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
.create<ListWebService>()
crawler = CuratedPodcastsCrawler(service)
crawler = CuratedPodcastsCrawler(service, staticHostUrl = server.url("/static").toString())
}

@Test
Expand All @@ -49,7 +49,7 @@ class CuratedPodcastsCrawlerTest {

val podcasts = crawler.crawl("android").getOrThrow()

assertEqualsInAnyOrder(emptyList<CuratedPodcast>(), podcasts)
assertEqualsInAnyOrder(emptyList(), podcasts)
}

@Test
Expand Down Expand Up @@ -327,7 +327,7 @@ class CuratedPodcastsCrawlerTest {

val podcasts = crawler.crawl("android").getOrThrow()

assertEqualsInAnyOrder(emptyList<CuratedPodcast>(), podcasts)
assertEqualsInAnyOrder(emptyList(), podcasts)
}

@Test
Expand All @@ -352,7 +352,7 @@ class CuratedPodcastsCrawlerTest {

val podcasts = crawler.crawl("android").getOrThrow()

assertEqualsInAnyOrder(emptyList<CuratedPodcast>(), podcasts)
assertEqualsInAnyOrder(emptyList(), podcasts)
}

@Test
Expand All @@ -377,7 +377,7 @@ class CuratedPodcastsCrawlerTest {

val podcasts = crawler.crawl("android").getOrThrow()

assertEqualsInAnyOrder(emptyList<CuratedPodcast>(), podcasts)
assertEqualsInAnyOrder(emptyList(), podcasts)
}

@Test
Expand Down Expand Up @@ -441,7 +441,7 @@ class CuratedPodcastsCrawlerTest {
| "sponsored": false,
| "type": "podcast_list",
| "title": "Featured",
| "source": "${server.url("/")}",
| "source": "${server.url("/featured.json")}",
| "summary_style": "",
| "expanded_style": "",
| "regions": []
Expand Down Expand Up @@ -478,6 +478,10 @@ class CuratedPodcastsCrawlerTest {
),
podcasts,
)

// Skip initial request
server.takeRequest()
assertEquals("/static/engage/featured.json", server.takeRequest().path)
}

private fun enqueueDiscoverPage(layout: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.scale
import au.com.shiftyjelly.pocketcasts.endofyear.ui.backgroundColor
import au.com.shiftyjelly.pocketcasts.models.to.Story
import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer
import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer.TAG_CRASH
import dev.shreyaspatil.capturable.controller.CaptureController
import dev.shreyaspatil.capturable.controller.rememberCaptureController
import java.io.File
Expand Down Expand Up @@ -117,7 +119,8 @@ internal fun rememberStoryCaptureController(): StoryCaptureController {
}
file
}
}.getOrNull()
}.onFailure { LogBuffer.e(TAG_CRASH, it, "Failed to create a screenshot") }
.getOrNull()
_isSharing = false
return file
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ internal class ClusterService(
.setName(podcast.title)
.addPosterImage(
Image.Builder()
.setImageUri(Uri.parse(podcast.coverUrl))
.setImageWidthInPixel(podcast.coverSize)
.setImageHeightInPixel(podcast.coverSize)
.setImageUri(Uri.parse(podcast.landscapeCoverUrl))
.setImageWidthInPixel(podcast.landscapeCoverWidth)
.setImageHeightInPixel(podcast.landscapeCoverHeight)
.build(),
)
.setInfoPageUri(podcast.uri(SourceView.ENGAGE_SDK_FEATURED))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ import au.com.shiftyjelly.pocketcasts.repositories.images.loadInto
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager
import au.com.shiftyjelly.pocketcasts.repositories.playback.UpNextSource
import au.com.shiftyjelly.pocketcasts.repositories.podcast.EpisodeManager
import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingFlow
import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingLauncher
import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingUpgradeSource
import au.com.shiftyjelly.pocketcasts.ui.extensions.themed
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
import au.com.shiftyjelly.pocketcasts.ui.theme.ThemeColor
import au.com.shiftyjelly.pocketcasts.utils.extensions.dpToPx
import au.com.shiftyjelly.pocketcasts.utils.extensions.getActivity
import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import au.com.shiftyjelly.pocketcasts.views.helper.SwipeButtonLayoutFactory
Expand Down Expand Up @@ -81,6 +85,9 @@ class UpNextAdapter(
notifyDataSetChanged()
}

private var isSignedInAsPaidUser: Boolean = false
private var isUpNextNotEmpty: Boolean = false

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
Expand All @@ -100,8 +107,7 @@ class UpNextAdapter(
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (item) {
when (val item = getItem(position)) {
is BaseEpisode -> bindEpisodeRow(holder as UpNextEpisodeViewHolder, item)
is PlayerViewModel.UpNextSummary -> (holder as HeaderViewHolder).bind(item)
is UpNextPlaying -> (holder as PlayingViewHolder).bind(item)
Expand Down Expand Up @@ -158,6 +164,14 @@ class UpNextAdapter(
(holder as? UpNextEpisodeViewHolder)?.clearDisposable()
}

fun updateUserSignInState(isSignedInAsPaidUser: Boolean) {
this.isSignedInAsPaidUser = isSignedInAsPaidUser
}

fun updateUpNextEmptyState(isUpNextNotEmpty: Boolean) {
this.isUpNextNotEmpty = isUpNextNotEmpty
}

inner class HeaderViewHolder(val binding: AdapterUpNextFooterBinding) : RecyclerView.ViewHolder(binding.root) {

fun bind(header: PlayerViewModel.UpNextSummary) {
Expand All @@ -171,14 +185,18 @@ class UpNextAdapter(
root.resources.getQuantityString(LR.plurals.player_up_next_header_title, header.episodeCount, header.episodeCount, time)
}

shuffle.isVisible = hasEpisodeInProgress() && FeatureFlag.isEnabled(Feature.UP_NEXT_SHUFFLE)
shuffle.isVisible = isUpNextNotEmpty && FeatureFlag.isEnabled(Feature.UP_NEXT_SHUFFLE)
shuffle.updateShuffleButton()

shuffle.setOnClickListener {
val newValue = !settings.upNextShuffle.value
analyticsTracker.track(AnalyticsEvent.UP_NEXT_SHUFFLE_ENABLED, mapOf("value" to newValue, SOURCE_KEY to upNextSource.analyticsValue))
if (isSignedInAsPaidUser) {
val newValue = !settings.upNextShuffle.value
analyticsTracker.track(AnalyticsEvent.UP_NEXT_SHUFFLE_ENABLED, mapOf("value" to newValue, SOURCE_KEY to upNextSource.analyticsValue))

settings.upNextShuffle.set(newValue, updateModifiedAt = false)
settings.upNextShuffle.set(newValue, updateModifiedAt = false)
} else {
OnboardingLauncher.openOnboardingFlow(root.context.getActivity(), OnboardingFlow.Upsell(OnboardingUpgradeSource.UP_NEXT_SHUFFLE))
}

shuffle.updateShuffleButton()
}
Expand All @@ -190,19 +208,29 @@ class UpNextAdapter(
private fun ImageButton.updateShuffleButton() {
if (!FeatureFlag.isEnabled(Feature.UP_NEXT_SHUFFLE)) return

val isEnabled = settings.upNextShuffle.value

this.setImageResource(if (isEnabled) IR.drawable.shuffle_enabled else IR.drawable.shuffle)
this.setImageResource(
when {
!isSignedInAsPaidUser -> IR.drawable.shuffle_plus_feature_icon
settings.upNextShuffle.value -> IR.drawable.shuffle_enabled
else -> IR.drawable.shuffle
},
)

this.contentDescription = context.getString(
if (isEnabled) LR.string.up_next_shuffle_disable_button_content_description else LR.string.up_next_shuffle_button_content_description,
when {
isSignedInAsPaidUser -> LR.string.up_next_shuffle_button_content_description
settings.upNextShuffle.value -> LR.string.up_next_shuffle_disable_button_content_description
else -> LR.string.up_next_shuffle_button_content_description
},
)

this.setImageTintList(
ColorStateList.valueOf(
if (isEnabled) ThemeColor.primaryIcon01(theme) else ThemeColor.primaryIcon02(theme),
),
)
if (isSignedInAsPaidUser) {
this.setImageTintList(
ColorStateList.valueOf(
if (settings.upNextShuffle.value) ThemeColor.primaryIcon01(theme) else ThemeColor.primaryIcon02(theme),
),
)
}

TooltipCompat.setTooltipText(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
Expand All @@ -27,6 +28,7 @@ import au.com.shiftyjelly.pocketcasts.models.type.EpisodeViewSource
import au.com.shiftyjelly.pocketcasts.player.R
import au.com.shiftyjelly.pocketcasts.player.databinding.FragmentUpnextBinding
import au.com.shiftyjelly.pocketcasts.player.viewmodel.PlayerViewModel
import au.com.shiftyjelly.pocketcasts.player.viewmodel.UpNextViewModel
import au.com.shiftyjelly.pocketcasts.preferences.Settings
import au.com.shiftyjelly.pocketcasts.repositories.playback.PlaybackManager
import au.com.shiftyjelly.pocketcasts.repositories.playback.UpNextSource
Expand Down Expand Up @@ -93,6 +95,7 @@ class UpNextFragment : BaseFragment(), UpNextListener, UpNextTouchCallback.ItemT
lateinit var adapter: UpNextAdapter
private val sourceView = SourceView.UP_NEXT
private val playerViewModel: PlayerViewModel by activityViewModels()
private val upNextViewModel: UpNextViewModel by viewModels<UpNextViewModel>()
private val swipeButtonLayoutViewModel: SwipeButtonLayoutViewModel by activityViewModels()
private var userRearrangingFrom: Int? = null
private var userDraggingStart: Int? = null
Expand Down Expand Up @@ -305,10 +308,18 @@ class UpNextFragment : BaseFragment(), UpNextListener, UpNextTouchCallback.ItemT

playerViewModel.listDataLive.observe(viewLifecycleOwner) {
adapter.isPlaying = it.podcastHeader.isPlaying
adapter.updateUpNextEmptyState(it.upNextEpisodes.isNotEmpty())
toolbar.menu.findItem(R.id.menu_select)?.isVisible = it.upNextEpisodes.isNotEmpty()
toolbar.menu.findItem(R.id.clear_up_next)?.isVisible = it.upNextEpisodes.isNotEmpty()
}

viewLifecycleOwner.lifecycleScope.launch {
upNextViewModel.isSignedInAsPaidUser.collect { isSignedInAsPaidUser ->
adapter.updateUserSignInState(isSignedInAsPaidUser)
adapter.notifyDataSetChanged()
}
}

view.isClickable = true

val callback = UpNextTouchCallback(adapter = this)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package au.com.shiftyjelly.pocketcasts.player.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import au.com.shiftyjelly.pocketcasts.repositories.di.IoDispatcher
import au.com.shiftyjelly.pocketcasts.repositories.user.UserManager
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactive.asFlow

@HiltViewModel
class UpNextViewModel @Inject constructor(
val userManager: UserManager,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {

private val _isSignedInAsPaidUser = MutableStateFlow(false)
val isSignedInAsPaidUser: StateFlow<Boolean> get() = _isSignedInAsPaidUser

init {
viewModelScope.launch(ioDispatcher) {
userManager.getSignInState().asFlow().collect { signInState ->
_isSignedInAsPaidUser.value = signInState.isSignedInAsPlusOrPatron
}
}
}
}
Loading

0 comments on commit a3593d9

Please sign in to comment.