Skip to content

Commit

Permalink
Implement paywall on media feeds grid (#243)
Browse files Browse the repository at this point in the history
* Extract premiumMembership check to ext function
* Implement paywall on MediaFeedGrid
  • Loading branch information
markocic authored Dec 2, 2024
1 parent 16e5781 commit f4a4fd7
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import net.primal.android.articles.ArticleRepository
import net.primal.android.articles.feed.ArticleFeedContract.UiState
import net.primal.android.articles.feed.ui.mapAsFeedArticleUi
import net.primal.android.feeds.domain.isPremiumFeedSpec
import net.primal.android.premium.utils.hasPremiumMembership
import net.primal.android.user.accounts.active.ActiveAccountStore

@HiltViewModel(assistedFactory = ArticleFeedViewModel.Factory::class)
Expand Down Expand Up @@ -47,9 +48,8 @@ class ArticleFeedViewModel @AssistedInject constructor(
private fun observeActiveAccount() {
viewModelScope.launch {
activeAccountStore.activeUserAccount.collect {
val hasPremiumMembership = it.premiumMembership?.isExpired() == false
setState {
copy(paywall = spec.isPremiumFeedSpec() && !hasPremiumMembership)
copy(paywall = spec.isPremiumFeedSpec() && !it.hasPremiumMembership())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ private fun ExploreNoteFeed(
feedSpec = feedSpec,
contentPadding = contentPadding,
onNoteClick = { noteCallbacks.onNoteClick?.invoke(it) },
onGetPrimalPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ private fun ExploreHomeScreen(
feedSpec = exploreMediaFeedSpec,
contentPadding = paddingValues,
onNoteClick = { noteCallbacks.onNoteClick?.invoke(it) },
onGetPrimalPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() },
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ interface MediaFeedContract {

data class UiState(
val notes: Flow<PagingData<FeedPostUi>>,
val paywall: Boolean = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
Expand All @@ -25,14 +26,19 @@ import androidx.paging.compose.itemKey
import net.primal.android.R
import net.primal.android.core.compose.GridLoadingPlaceholder
import net.primal.android.core.compose.ListNoContent
import net.primal.android.core.compose.PremiumFeedPaywall
import net.primal.android.core.compose.isEmpty
import net.primal.android.notes.feed.model.FeedPostUi
import timber.log.Timber

const val MAX_NOTES_BEFORE_PAYWALL = 25
const val PAYWALL_COLUMN_SPAN = 3

@Composable
fun MediaFeedGrid(
feedSpec: String,
onNoteClick: (String) -> Unit,
onGetPrimalPremiumClick: () -> Unit,
modifier: Modifier = Modifier,
gridState: LazyGridState = rememberLazyGridState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
Expand All @@ -49,6 +55,7 @@ fun MediaFeedGrid(
MediaFeedGrid(
state = uiState.value,
onNoteClick = onNoteClick,
onGetPrimalPremiumClick = onGetPrimalPremiumClick,
gridState = gridState,
modifier = modifier,
contentPadding = contentPadding,
Expand All @@ -62,6 +69,7 @@ private fun MediaFeedGrid(
modifier: Modifier = Modifier,
state: MediaFeedContract.UiState,
onNoteClick: (String) -> Unit,
onGetPrimalPremiumClick: () -> Unit,
gridState: LazyGridState = rememberLazyGridState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
noContentVerticalArrangement: Arrangement.Vertical = Arrangement.Center,
Expand All @@ -86,7 +94,7 @@ private fun MediaFeedGrid(
contentPadding = contentPadding,
) {
items(
count = pagingItems.itemCount,
count = pagingItems.itemCount.ifPaywallCoerceAtMost(state.paywall),
key = pagingItems.itemKey(key = { "${it.postId}${it.repostId}" }),
contentType = pagingItems.itemContentType(),
) { index ->
Expand All @@ -108,6 +116,9 @@ private fun MediaFeedGrid(
else -> {}
}
}
item(span = { GridItemSpan(PAYWALL_COLUMN_SPAN) }) {
PremiumFeedPaywall(onClick = onGetPrimalPremiumClick)
}
}
}
}
Expand Down Expand Up @@ -153,3 +164,6 @@ private fun EmptyItemsContent(
}
}
}

private fun Int.ifPaywallCoerceAtMost(paywall: Boolean) =
run { if (paywall) this.coerceAtMost(MAX_NOTES_BEFORE_PAYWALL) else this }
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import net.primal.android.feeds.domain.isPremiumFeedSpec
import net.primal.android.notes.feed.grid.MediaFeedContract.UiState
import net.primal.android.notes.feed.model.asFeedPostUi
import net.primal.android.notes.repository.FeedRepository
import net.primal.android.premium.utils.hasPremiumMembership
import net.primal.android.user.accounts.active.ActiveAccountStore

@HiltViewModel(assistedFactory = MediaFeedViewModel.Factory::class)
class MediaFeedViewModel @AssistedInject constructor(
@Assisted private val feedSpec: String,
private val feedRepository: FeedRepository,
private val activeAccountStore: ActiveAccountStore,
) : ViewModel() {

@AssistedFactory
Expand All @@ -33,4 +39,16 @@ class MediaFeedViewModel @AssistedInject constructor(

private val _state = MutableStateFlow(UiState(notes = buildFeedByDirective(feedSpec = feedSpec)))
val state = _state.asStateFlow()
private fun setState(reducer: UiState.() -> UiState) = _state.getAndUpdate { it.reducer() }

init {
observeActiveAccount()
}

private fun observeActiveAccount() =
viewModelScope.launch {
activeAccountStore.activeUserAccount.collect {
setState { copy(paywall = feedSpec.isPremiumFeedSpec() && !it.hasPremiumMembership()) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import net.primal.android.notes.feed.list.NoteFeedContract.UiState
import net.primal.android.notes.feed.model.FeedPostsSyncStats
import net.primal.android.notes.feed.model.asFeedPostUi
import net.primal.android.notes.repository.FeedRepository
import net.primal.android.premium.utils.hasPremiumMembership
import net.primal.android.user.accounts.active.ActiveAccountStore
import timber.log.Timber

Expand Down Expand Up @@ -79,9 +80,8 @@ class NoteFeedViewModel @AssistedInject constructor(
private fun observeActiveAccount() {
viewModelScope.launch {
activeAccountStore.activeUserAccount.collect {
val hasPremiumMembership = it.premiumMembership?.isExpired() == false
setState {
copy(paywall = feedSpec.isPremiumFeedSpec() && !hasPremiumMembership)
copy(paywall = feedSpec.isPremiumFeedSpec() && !it.hasPremiumMembership())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package net.primal.android.premium.utils

import net.primal.android.premium.domain.PremiumMembership
import net.primal.android.user.domain.UserAccount

fun String?.isPrimalLegend() = this?.lowercase() == "primal legend"
fun String?.isPremiumFree() = this?.lowercase() == "free"
fun String?.isOriginAndroid() = this?.lowercase() == "android"
fun String?.isOriginIOS() = this?.lowercase() == "ios"
fun String?.isOriginWeb() = this?.lowercase() == "web"

fun PremiumMembership.hasPremiumMembership() = !this.isExpired()
fun UserAccount.hasPremiumMembership() = this.premiumMembership?.hasPremiumMembership() == true
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,9 @@ fun ProfileDetailsScreen(
onNoteClick = { naddr -> noteCallbacks.onNoteClick?.let { it(naddr) } },
noContentVerticalArrangement = Arrangement.Top,
noContentPaddingValues = PaddingValues(top = 16.dp),
onGetPrimalPremiumClick = {
noteCallbacks.onGetPrimalPremiumClick?.invoke()
},
)
}
}
Expand Down

0 comments on commit f4a4fd7

Please sign in to comment.