Skip to content

Commit

Permalink
[Project] Up Next Shuffle - Update Shuffle Button for Free users (#3129)
Browse files Browse the repository at this point in the history
  • Loading branch information
mebarbosa authored Oct 30, 2024
1 parent fa6380c commit 668426d
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class UpNextAdapter(
notifyDataSetChanged()
}

private var isSignedInAsPaidUser: Boolean = false

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
Expand All @@ -100,8 +102,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 +159,10 @@ class UpNextAdapter(
(holder as? UpNextEpisodeViewHolder)?.clearDisposable()
}

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

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

fun bind(header: PlayerViewModel.UpNextSummary) {
Expand Down Expand Up @@ -190,19 +195,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 @@ -309,6 +312,13 @@ class UpNextFragment : BaseFragment(), UpNextListener, UpNextTouchCallback.ItemT
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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package au.com.shiftyjelly.pocketcasts.player.viewmodel

import au.com.shiftyjelly.pocketcasts.models.to.SignInState
import au.com.shiftyjelly.pocketcasts.models.to.SubscriptionStatus
import au.com.shiftyjelly.pocketcasts.models.type.SubscriptionFrequency
import au.com.shiftyjelly.pocketcasts.models.type.SubscriptionPlatform
import au.com.shiftyjelly.pocketcasts.models.type.SubscriptionTier
import au.com.shiftyjelly.pocketcasts.models.type.SubscriptionType
import au.com.shiftyjelly.pocketcasts.repositories.user.UserManager
import io.reactivex.Flowable
import java.util.Date
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

class UpNextViewModelTest {

@Test
fun `initial state isSignedInAsPaidUser should be true for paid user`() = runTest {
val viewModel = initViewModel(isPaidUser = true)

assertEquals(true, viewModel.isSignedInAsPaidUser.value)
}

@Test
fun `initial state isSignedInAsPaidUser should be false for free user`() = runTest {
val viewModel = initViewModel(isPaidUser = false)

assertEquals(false, viewModel.isSignedInAsPaidUser.value)
}

@OptIn(ExperimentalCoroutinesApi::class)
private fun initViewModel(
isPaidUser: Boolean = false,
): UpNextViewModel {
val userManager = mock<UserManager>()

whenever(userManager.getSignInState())
.thenReturn(
Flowable.just(
SignInState.SignedIn(
email = "",
subscriptionStatus = if (isPaidUser) {
SubscriptionStatus.Paid(
expiry = Date(),
autoRenew = true,
giftDays = 0,
frequency = SubscriptionFrequency.MONTHLY,
platform = SubscriptionPlatform.ANDROID,
subscriptionList = emptyList(),
type = SubscriptionType.PLUS,
tier = SubscriptionTier.PLUS,
index = 0,
)
} else {
SubscriptionStatus.Free()
},
),
),
)
return UpNextViewModel(userManager, UnconfinedTestDispatcher())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.961,16.523C2.961,16.07 3.313,15.742 3.797,15.742H5.578C6.445,15.742 7.008,15.438 7.68,14.641L9.578,12.383L7.68,10.125C7.008,9.328 6.398,9.023 5.523,9.023H3.797C3.313,9.023 2.961,8.695 2.961,8.242C2.961,7.789 3.313,7.469 3.797,7.469H5.586C6.922,7.469 7.766,7.859 8.688,8.945L10.578,11.195L12.398,9.031C13.391,7.844 14.242,7.461 15.633,7.461H17.188V5.617C17.188,5.234 17.414,5.023 17.789,5.023C17.961,5.023 18.117,5.078 18.25,5.188L21.313,7.742C21.609,8 21.609,8.375 21.313,8.617L18.25,11.172C18.117,11.273 17.961,11.336 17.789,11.336C17.414,11.336 17.188,11.117 17.188,10.742V9.023H15.625C14.719,9.023 14.172,9.313 13.492,10.117L11.578,12.383L13.484,14.641C14.148,15.438 14.75,15.742 15.672,15.742H17.188V14.055C17.188,13.672 17.414,13.461 17.789,13.461C17.961,13.461 18.117,13.516 18.25,13.625L21.313,16.18C21.609,16.438 21.609,16.813 21.313,17.055L18.25,19.609C18.117,19.711 17.961,19.773 17.789,19.773C17.414,19.773 17.188,19.555 17.188,19.18V17.297H15.625C14.281,17.297 13.391,16.906 12.445,15.781L10.578,13.57L8.688,15.82C7.766,16.906 6.977,17.297 5.633,17.297H3.797C3.313,17.297 2.961,16.977 2.961,16.523Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="4.62"
android:startY="5.023"
android:endX="21.347"
android:endY="6.846"
android:type="linear">
<item android:offset="0" android:color="#FFFED745"/>
<item android:offset="1" android:color="#FFFEB525"/>
</gradient>
</aapt:attr>
</path>
</vector>

0 comments on commit 668426d

Please sign in to comment.