Skip to content

Commit

Permalink
Merge pull request #20728 from wordpress-mobile/issue/20688-emails-card
Browse files Browse the repository at this point in the history
Emails stats card
  • Loading branch information
irfano authored May 1, 2024
2 parents 1b68b1c + 63dbc3e commit 3be32da
Show file tree
Hide file tree
Showing 19 changed files with 490 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ enum class StatsViewType {
ANNUAL_STATS,
TOTAL_LIKES,
TOTAL_COMMENTS,
TOTAL_FOLLOWERS
TOTAL_FOLLOWERS,
EMAILS,
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.INFO
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINE_CHART
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINK
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_HEADER
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_ICON
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_IMAGE
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_TWO_VALUES
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LOADING_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP_LEGEND
Expand Down Expand Up @@ -74,11 +76,13 @@ class BlockDiffCallback(
LINE_CHART,
SUBSCRIBERS_CHART,
ACTIVITY_ITEM,
LIST_ITEM_WITH_TWO_VALUES,
LIST_ITEM -> oldItem.itemId == newItem.itemId
LINK,
TEXT,
INFO,
HEADER,
LIST_HEADER,
TITLE,
TITLE_WITH_MORE,
BIG_TITLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ sealed class NavigationTarget {
val selectedDate: Date?
) : NavigationTarget()

data object EmailsStats : NavigationTarget()

object SetBloggingReminders : NavigationTarget()
object CheckCourse : NavigationTarget()
object SchedulePost : NavigationTarget()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.T
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TotalFollowersUseCase.TotalFollowersUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TotalLikesUseCase.TotalLikesUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.ViewsAndVisitorsUseCase.ViewsAndVisitorsUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.subscribers.usecases.EmailsUseCase.EmailsUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.subscribers.usecases.SubscribersUseCase
import org.wordpress.android.ui.stats.refresh.utils.SelectedTrafficGranularityManager
import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider
Expand Down Expand Up @@ -178,7 +179,8 @@ class StatsModule {
postMonthsAndYearsUseCaseFactory: PostMonthsAndYearsUseCaseFactory,
postAverageViewsPerDayUseCaseFactory: PostAverageViewsPerDayUseCaseFactory,
postRecentWeeksUseCaseFactory: PostRecentWeeksUseCaseFactory,
annualSiteStatsUseCaseFactory: AnnualSiteStatsUseCaseFactory
annualSiteStatsUseCaseFactory: AnnualSiteStatsUseCaseFactory,
emailsUseCaseFactory: EmailsUseCaseFactory
): List<@JvmSuppressWildcards BaseStatsUseCase<*, *>> {
return listOf(
followersUseCaseFactory.build(VIEW_ALL),
Expand All @@ -187,7 +189,8 @@ class StatsModule {
postMonthsAndYearsUseCaseFactory.build(VIEW_ALL),
postAverageViewsPerDayUseCaseFactory.build(VIEW_ALL),
postRecentWeeksUseCaseFactory.build(VIEW_ALL),
annualSiteStatsUseCaseFactory.build(VIEW_ALL)
annualSiteStatsUseCaseFactory.build(VIEW_ALL),
emailsUseCaseFactory.build(VIEW_ALL)
)
}

Expand Down Expand Up @@ -255,8 +258,10 @@ class StatsModule {
@Named(BLOCK_SUBSCRIBERS_USE_CASES)
@Suppress("LongParameterList")
fun provideBlockSubscribersUseCases(
subscribersUseCase: SubscribersUseCase
): List<@JvmSuppressWildcards BaseStatsUseCase<*, *>> = listOf(subscribersUseCase)
subscribersUseCase: SubscribersUseCase,
emailsUseCaseFactory: EmailsUseCaseFactory
): List<@JvmSuppressWildcards BaseStatsUseCase<*, *>> =
listOf(subscribersUseCase, emailsUseCaseFactory.build(BLOCK))

/**
* Provides a singleton usecase that represents the Insights screen. It consists of list of use cases that build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.wordpress.android.ui.stats.StatsViewType.CLICKS
import org.wordpress.android.ui.stats.StatsViewType.DETAIL_AVERAGE_VIEWS_PER_DAY
import org.wordpress.android.ui.stats.StatsViewType.DETAIL_MONTHS_AND_YEARS
import org.wordpress.android.ui.stats.StatsViewType.DETAIL_RECENT_WEEKS
import org.wordpress.android.ui.stats.StatsViewType.EMAILS
import org.wordpress.android.ui.stats.StatsViewType.FILE_DOWNLOADS
import org.wordpress.android.ui.stats.StatsViewType.FOLLOWERS
import org.wordpress.android.ui.stats.StatsViewType.GEOVIEWS
Expand Down Expand Up @@ -52,6 +53,7 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.P
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TagsAndCategoriesUseCase
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TodayStatsUseCase
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.ViewsAndVisitorsUseCase
import org.wordpress.android.ui.stats.refresh.lists.sections.subscribers.usecases.EmailsUseCase
import org.wordpress.android.ui.stats.refresh.utils.StatsDateSelector
import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider
import java.security.InvalidParameterException
Expand Down Expand Up @@ -205,6 +207,11 @@ class StatsViewAllViewModelFactory(
insightsUseCases.first {
it is PostRecentWeeksUseCase
} to R.string.stats_detail_recent_weeks

EMAILS -> Pair(
insightsUseCases.first { it is EmailsUseCase },
R.string.stats_view_emails
)
else -> throw InvalidParameterException("Invalid insights stats type: ${type.name}")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Image
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Information
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.LineChartItem
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Link
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListHeader
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItem
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemActionCard
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemGuideCard
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithIcon
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithImage
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithTwoValues
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.LoadingItem
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.MapItem
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.MapLegend
Expand Down Expand Up @@ -58,9 +60,11 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.INFO
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINE_CHART
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINK
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_HEADER
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_ICON
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_IMAGE
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_TWO_VALUES
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LOADING_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP_LEGEND
Expand Down Expand Up @@ -99,9 +103,11 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ImageIt
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.InformationViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.LineChartViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.LinkViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ListHeaderViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ListItemViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ListItemWithIconViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ListItemWithImageViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.ListItemWithTwoValuesViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.LoadingItemViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.MapLegendViewHolder
import org.wordpress.android.ui.stats.refresh.lists.sections.viewholders.MapViewHolder
Expand Down Expand Up @@ -141,7 +147,9 @@ class BlockListAdapter(val imageManager: ImageManager) : Adapter<BlockListItemVi
IMAGE_ITEM -> ImageItemViewHolder(parent, imageManager)
LIST_ITEM_WITH_IMAGE -> ListItemWithImageViewHolder(parent, imageManager = imageManager)
LIST_ITEM_WITH_ICON -> ListItemWithIconViewHolder(parent, imageManager)
LIST_ITEM_WITH_TWO_VALUES -> ListItemWithTwoValuesViewHolder(parent)
LIST_ITEM -> ListItemViewHolder(parent)
LIST_HEADER -> ListHeaderViewHolder(parent)
EMPTY -> EmptyViewHolder(parent)
TEXT -> TextViewHolder(parent)
COLUMNS -> FourColumnsViewHolder(parent)
Expand Down Expand Up @@ -191,9 +199,11 @@ class BlockListAdapter(val imageManager: ImageManager) : Adapter<BlockListItemVi
is ValueViewHolder -> holder.bind(item as ValueItem)
is ValueWithChartViewHolder -> holder.bind(item as ValueWithChartItem)
is ValuesViewHolder -> holder.bind(item as ValuesItem)
is ListItemWithTwoValuesViewHolder -> holder.bind(item as ListItemWithTwoValues)
is ListItemWithImageViewHolder -> holder.bind(item as ListItemWithImage)
is ListItemWithIconViewHolder -> holder.bind(item as ListItemWithIcon)
is ListItemViewHolder -> holder.bind(item as ListItem)
is ListHeaderViewHolder -> holder.bind(item as ListHeader)
is TextViewHolder -> holder.bind(item as Text)
is FourColumnsViewHolder -> holder.bind(item as Columns, payloads)
is ChipsViewHolder -> holder.bind(item as Chips)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.INFO
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINE_CHART
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LINK
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_HEADER
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_ICON
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_IMAGE
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LIST_ITEM_WITH_TWO_VALUES
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.LOADING_ITEM
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type.MAP_LEGEND
Expand Down Expand Up @@ -62,7 +64,9 @@ sealed class BlockListItem(val type: Type) {
VALUE_ITEM,
VALUE_WITH_CHART_ITEM,
VALUES_ITEM,
LIST_HEADER,
LIST_ITEM,
LIST_ITEM_WITH_TWO_VALUES,
LIST_ITEM_WITH_ICON,
LIST_ITEM_WITH_IMAGE,
INFO,
Expand Down Expand Up @@ -145,6 +149,12 @@ sealed class BlockListItem(val type: Type) {
val contentDescription2: String? = null
) : BlockListItem(VALUES_ITEM)

data class ListHeader(
@StringRes val label: Int,
@StringRes val valueLabel1: Int,
@StringRes val valueLabel2: Int
) : BlockListItem(LIST_HEADER)

data class ListItem(
val text: String,
val value: String,
Expand All @@ -155,6 +165,15 @@ sealed class BlockListItem(val type: Type) {
get() = text.hashCode()
}

data class ListItemWithTwoValues(
val text: String,
val value1: String,
val value2: String
) : BlockListItem(LIST_ITEM_WITH_TWO_VALUES) {
override val itemId: Int
get() = text.hashCode()
}

data class ListItemWithIcon(
@DrawableRes val icon: Int? = null,
val iconUrl: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.wordpress.android.ui.stats.refresh.lists.sections.subscribers.usecases

import kotlinx.coroutines.CoroutineDispatcher
import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.fluxc.model.stats.LimitMode
import org.wordpress.android.fluxc.model.stats.subscribers.PostsModel
import org.wordpress.android.fluxc.network.rest.wpcom.stats.subscribers.EmailsRestClient.SortField
import org.wordpress.android.fluxc.store.StatsStore.SubscriberType.EMAILS
import org.wordpress.android.fluxc.store.stats.subscribers.EmailsStore
import org.wordpress.android.modules.BG_THREAD
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.ui.stats.refresh.NavigationTarget
import org.wordpress.android.ui.stats.refresh.lists.BLOCK_ITEM_COUNT
import org.wordpress.android.ui.stats.refresh.lists.sections.BaseStatsUseCase.StatelessUseCase
import org.wordpress.android.ui.stats.refresh.lists.sections.BaseStatsUseCase.UseCaseMode.VIEW_ALL
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.InsightUseCaseFactory
import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider
import org.wordpress.android.ui.stats.refresh.utils.StatsUtils
import org.wordpress.android.ui.utils.ListItemInteraction
import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
import javax.inject.Inject
import javax.inject.Named

class EmailsUseCase @Inject constructor(
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
private val emailsStore: EmailsStore,
private val statsSiteProvider: StatsSiteProvider,
private val statsUtils: StatsUtils,
private val analyticsTracker: AnalyticsTrackerWrapper,
private val useCaseMode: UseCaseMode
) : StatelessUseCase<PostsModel>(EMAILS, mainDispatcher, bgDispatcher) {
private val itemsToShow = if (useCaseMode == VIEW_ALL) VIEW_ALL_ITEM_SIZE else BLOCK_ITEM_COUNT
private val sortField = if (useCaseMode == VIEW_ALL) SortField.OPENS else SortField.POST_ID

override suspend fun fetchRemoteData(forced: Boolean): State<PostsModel> {
val response = emailsStore.fetchEmails(
statsSiteProvider.siteModel,
LimitMode.Top(VIEW_ALL_ITEM_SIZE),
sortField,
forced
)
val model = response.model
val error = response.error

return when {
error != null -> State.Error(error.message ?: error.type.name)
model != null && model.posts.isNotEmpty() -> State.Data(model)
else -> State.Empty()
}
}

override suspend fun loadCachedData() =
emailsStore.getEmails(statsSiteProvider.siteModel, LimitMode.Top(VIEW_ALL_ITEM_SIZE), sortField)

override fun buildLoadingItem() = listOf(BlockListItem.Title(R.string.stats_subscribers_emails))

override fun buildEmptyItem() = listOf(buildTitle(), BlockListItem.Empty())

override fun buildUiModel(domainModel: PostsModel): List<BlockListItem> {
val items = mutableListOf<BlockListItem>()

if (useCaseMode == UseCaseMode.BLOCK) {
items.add(buildTitle())
}

if (domainModel.posts.isEmpty()) {
items.add(BlockListItem.Empty())
} else {
val header = BlockListItem.ListHeader(
R.string.stats_emails_latest_emails_label,
R.string.stats_emails_opens_label,
R.string.stats_emails_clicks_label
)
items.add(header)
val postsList = mutableListOf<BlockListItem>()
domainModel.posts.take(itemsToShow).forEach { post -> postsList.add(mapPost(post)) }

items.addAll(postsList)
if (useCaseMode == UseCaseMode.BLOCK && domainModel.posts.size > BLOCK_ITEM_COUNT) {
items.add(
BlockListItem.Link(
text = R.string.stats_insights_view_more,
navigateAction = ListItemInteraction.create(this::onLinkClick)
)
)
}
}
return items
}

private fun buildTitle() = BlockListItem.Title(R.string.stats_subscribers_emails)

private fun mapPost(post: PostsModel.PostModel) = BlockListItem.ListItemWithTwoValues(
text = post.title,
value1 = statsUtils.toFormattedString(post.opens),
value2 = statsUtils.toFormattedString(post.clicks)
)

private fun onLinkClick() {
analyticsTracker.track(AnalyticsTracker.Stat.STATS_EMAILS_VIEW_MORE_TAPPED)
navigateTo(NavigationTarget.EmailsStats)
}

class EmailsUseCaseFactory @Inject constructor(
@Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher,
@Named(BG_THREAD) private val backgroundDispatcher: CoroutineDispatcher,
private val emailsStore: EmailsStore,
private val statsSiteProvider: StatsSiteProvider,
private val statsUtils: StatsUtils,
private val analyticsTracker: AnalyticsTrackerWrapper
) : InsightUseCaseFactory {
override fun build(useCaseMode: UseCaseMode) = EmailsUseCase(
mainDispatcher,
backgroundDispatcher,
emailsStore,
statsSiteProvider,
statsUtils,
analyticsTracker,
useCaseMode
)
}

companion object {
private const val VIEW_ALL_ITEM_SIZE = 30
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.wordpress.android.ui.stats.refresh.lists.sections.viewholders

import android.view.ViewGroup
import android.widget.TextView
import org.wordpress.android.R
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListHeader

class ListHeaderViewHolder(parent: ViewGroup) : BlockListItemViewHolder(
parent,
R.layout.stats_block_list_header_item
) {
private val label = itemView.findViewById<TextView>(R.id.label)
private val valueLabel1 = itemView.findViewById<TextView>(R.id.valueLabel1)
private val valueLabel2 = itemView.findViewById<TextView>(R.id.valueLabel2)
fun bind(item: ListHeader) {
label.setText(item.label)
valueLabel1.setText(item.valueLabel1)
valueLabel2.setText(item.valueLabel2)
}
}
Loading

0 comments on commit 3be32da

Please sign in to comment.