Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Suggested Folders] Update flow for free users #3573

Merged
merged 4 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.doOnLayout
import androidx.fragment.app.viewModels
import androidx.fragment.compose.content
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
import au.com.shiftyjelly.pocketcasts.settings.onboarding.OnboardingFlow
Expand All @@ -24,6 +25,8 @@ class SuggestedFoldersPaywallBottomSheet : BottomSheetDialogFragment() {
@Inject
lateinit var theme: Theme

private val viewModel: SuggestedFoldersPaywallViewModel by viewModels<SuggestedFoldersPaywallViewModel>()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -38,6 +41,7 @@ class SuggestedFoldersPaywallBottomSheet : BottomSheetDialogFragment() {
OnboardingLauncher.openOnboardingFlow(activity, onboardingFlow)
},
onMaybeLater = {
viewModel.dismissModal()
dismiss()
},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package au.com.shiftyjelly.pocketcasts.podcasts.view.folders

import androidx.lifecycle.ViewModel
import au.com.shiftyjelly.pocketcasts.preferences.Settings
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class SuggestedFoldersPaywallViewModel @Inject constructor(
private val settings: Settings,
) : ViewModel() {

fun dismissModal() {
settings.suggestedFolderPaywallDismissTime.set(System.currentTimeMillis(), updateModifiedAt = false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,28 @@ class PodcastsFragment : BaseFragment(), FolderAdapter.ClickListener, PodcastTou
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewLifecycleOwner.lifecycleScope.launch {
viewModel.fetchSuggestedFolders()
}

viewLifecycleOwner.lifecycleScope.launch {
viewModel.userSuggestedFoldersState.collect { (signInState, suggestedFoldersState) ->
when (suggestedFoldersState) {
PodcastsViewModel.SuggestedFoldersState.Loaded -> {
if (viewModel.showSuggestedFoldersPaywallOnOpen(signInState.isSignedInAsPlusOrPatron)) {
SuggestedFoldersPaywallBottomSheet().show(parentFragmentManager, "suggested_folders_paywall")
}
}
PodcastsViewModel.SuggestedFoldersState.Fetching -> {}
is PodcastsViewModel.SuggestedFoldersState.Error -> {}
}
}
}
}

override fun onDestroyView() {
listState = binding.recyclerView.layoutManager?.onSaveInstanceState()
binding.recyclerView.adapter = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import au.com.shiftyjelly.pocketcasts.repositories.podcast.EpisodeManager
import au.com.shiftyjelly.pocketcasts.repositories.podcast.FolderManager
import au.com.shiftyjelly.pocketcasts.repositories.podcast.PodcastManager
import au.com.shiftyjelly.pocketcasts.repositories.user.UserManager
import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.BackpressureStrategy
Expand All @@ -26,11 +28,16 @@ import java.util.Collections
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.rx2.asObservable
import timber.log.Timber

Expand Down Expand Up @@ -126,6 +133,13 @@ class PodcastsViewModel
val folder: Folder?
get() = folderState.value?.folder

private val _suggestedFoldersState = MutableSharedFlow<SuggestedFoldersState>()

val userSuggestedFoldersState: Flow<Pair<SignInState, SuggestedFoldersState>> = userManager.getSignInState().asFlow()
.combine(_suggestedFoldersState) { signIn, suggestedFolders ->
Pair(signIn, suggestedFolders)
}

private fun buildHomeFolderItems(podcasts: List<Podcast>, folders: List<FolderItem>, podcastSortType: PodcastsSortType): List<FolderItem> {
if (podcastSortType == PodcastsSortType.EPISODE_DATE_NEWEST_TO_OLDEST) {
val folderUuids = folders.mapTo(mutableSetOf()) { it.uuid }
Expand Down Expand Up @@ -212,7 +226,7 @@ class PodcastsViewModel
}
}
} catch (ex: IndexOutOfBoundsException) {
Timber.e("Move folder item failed.", ex)
Timber.e("Move folder item failed: $ex")
}

return adapterState.toList()
Expand Down Expand Up @@ -282,6 +296,23 @@ class PodcastsViewModel
}
}

suspend fun fetchSuggestedFolders() {
if (FeatureFlag.isEnabled(Feature.SUGGESTED_FOLDERS)) {
_suggestedFoldersState.emit(SuggestedFoldersState.Fetching)
delay(2.seconds)
_suggestedFoldersState.emit(SuggestedFoldersState.Loaded)
}
}

fun showSuggestedFoldersPaywallOnOpen(isSignedInAsPlusOrPatron: Boolean) =
FeatureFlag.isEnabled(Feature.SUGGESTED_FOLDERS) && !isSignedInAsPlusOrPatron && settings.suggestedFolderPaywallDismissTime.value == 0L

sealed class SuggestedFoldersState {
data object Fetching : SuggestedFoldersState()
data object Loaded : SuggestedFoldersState()
data class Error(val message: String) : SuggestedFoldersState()
}

companion object {
private const val NUMBER_OF_FOLDERS_KEY = "number_of_folders"
private const val NUMBER_OF_PODCASTS_KEY = "number_of_podcasts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ interface Settings {

val hideNotificationOnPause: UserSetting<Boolean>

val suggestedFolderPaywallDismissTime: UserSetting<Long>

val streamingMode: UserSetting<Boolean>
val keepScreenAwake: UserSetting<Boolean>
val openPlayerAutomatically: UserSetting<Boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ class SettingsImpl @Inject constructor(
sharedPrefs = sharedPreferences,
)

override val suggestedFolderPaywallDismissTime = UserSetting.LongPref(
sharedPrefKey = "suggestedFolderPaywallDismissTime",
defaultValue = 0L,
sharedPrefs = sharedPreferences,
)

override val streamingMode: UserSetting<Boolean> = UserSetting.BoolPref(
sharedPrefKey = Settings.PREFERENCE_GLOBAL_STREAMING_MODE,
defaultValue = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ abstract class UserSetting<T>(
toInt = { it },
)

class LongPref(
sharedPrefKey: String,
defaultValue: Long,
sharedPrefs: SharedPreferences,
) : PrefFromLong<Long>(
sharedPrefKey = sharedPrefKey,
defaultValue = defaultValue,
sharedPrefs = sharedPrefs,
fromLong = { it },
toLong = { it },
)

class StringPref(
sharedPrefKey: String,
defaultValue: String,
Expand Down Expand Up @@ -171,6 +183,36 @@ abstract class UserSetting<T>(
}
}

// This persists the parameterized object as a Long in shared preferences.
open class PrefFromLong<T>(
sharedPrefKey: String,
private val defaultValue: T,
sharedPrefs: SharedPreferences,
private val fromLong: (Long) -> T,
private val toLong: (T) -> Long,
) : UserSetting<T>(
sharedPrefKey = sharedPrefKey,
sharedPrefs = sharedPrefs,
) {
override fun get(): T {
val persistedLong = sharedPrefs.getLong(sharedPrefKey, toLong(defaultValue))
return fromLong(persistedLong)
}

@SuppressLint("ApplySharedPref")
override fun persist(value: T, commit: Boolean) {
val longValue = toLong(value)
sharedPrefs.edit().run {
putLong(sharedPrefKey, longValue)
if (commit) {
commit()
} else {
apply()
}
}
}
}

// This persists the parameterized object as a String in shared preferences.
open class PrefFromString<T>(
sharedPrefKey: String,
Expand Down