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

Create percentage bars in Stats cards #10073

Merged
merged 5 commits into from Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASE-NOTES.txt
@@ -1,6 +1,6 @@
12.8
-----

Add percentage bar to following Stats cards - Posts and Pages, Authors, Tags and Categories

12.7
-----
Expand Down
Expand Up @@ -115,10 +115,9 @@ sealed class BlockListItem(val type: Type) {
val iconStyle: IconStyle = NORMAL,
@StringRes val textResource: Int? = null,
val text: String? = null,
@StringRes val subTextResource: Int? = null,
val subText: String? = null,
@StringRes val valueResource: Int? = null,
val value: String? = null,
val barWidth: Int? = null,
val showDivider: Boolean = true,
val textStyle: TextStyle = TextStyle.NORMAL,
val navigationAction: NavigationAction? = null
Expand Down
Expand Up @@ -102,11 +102,13 @@ constructor(
items.add(Empty(R.string.stats_no_data_for_period))
} else {
items.add(Header(R.string.stats_author_label, R.string.stats_author_views_label))
val maxViews = domainModel.authors.maxBy { it.views }?.views ?: 0
domainModel.authors.forEachIndexed { index, author ->
val headerItem = ListItemWithIcon(
iconUrl = author.avatarUrl,
iconStyle = AVATAR,
text = author.name,
barWidth = getBarWidth(author.views, maxViews),
value = author.views.toFormattedString(),
showDivider = index < domainModel.authors.size - 1
)
Expand Down Expand Up @@ -148,6 +150,17 @@ constructor(
return items
}

private fun getBarWidth(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can extract this function to some Utils class, as we are using it all around the place?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about it :-), I think it makes sense

views: Int,
maxViews: Int
): Int? {
return if (maxViews > 0) {
((views.toDouble() / maxViews.toDouble()) * 100).toInt()
} else {
null
}
}

private fun onViewMoreClicked(statsGranularity: StatsGranularity) {
analyticsTracker.trackGranular(AnalyticsTracker.Stat.STATS_AUTHORS_VIEW_MORE_TAPPED, statsGranularity)
navigateTo(
Expand Down
Expand Up @@ -102,6 +102,7 @@ constructor(
items.add(Empty(R.string.stats_no_data_for_period))
} else {
items.add(Header(R.string.stats_posts_and_pages_title_label, R.string.stats_posts_and_pages_views_label))
val maxViews = domainModel.views.maxBy { it.views }?.views ?: 0
items.addAll(domainModel.views.mapIndexed { index, viewsModel ->
val icon = when (viewsModel.type) {
POST -> R.drawable.ic_posts_white_24dp
Expand All @@ -112,6 +113,7 @@ constructor(
text = viewsModel.title,
value = viewsModel.views.toFormattedString(),
showDivider = index < domainModel.views.size - 1,
barWidth = getBarWidth(viewsModel.views, maxViews),
navigationAction = create(
LinkClickParams(viewsModel.id, viewsModel.url, viewsModel.title, viewsModel.type),
this::onLinkClicked
Expand All @@ -130,6 +132,17 @@ constructor(
return items
}

private fun getBarWidth(
views: Int,
maxViews: Int
): Int? {
return if (maxViews > 0) {
((views.toDouble() / maxViews.toDouble()) * 100).toInt()
} else {
null
}
}

private fun onViewMoreClicked(statsGranularity: StatsGranularity) {
analyticsTracker.trackGranular(AnalyticsTracker.Stat.STATS_POSTS_AND_PAGES_VIEW_MORE_TAPPED, statsGranularity)
navigateTo(
Expand Down
Expand Up @@ -95,15 +95,16 @@ class TagsAndCategoriesUseCase
)
)
val tagsList = mutableListOf<BlockListItem>()
val maxViews = domainModel.tags.maxBy { it.views }?.views ?: 0
domainModel.tags.forEachIndexed { index, tag ->
when {
tag.items.size == 1 -> {
tagsList.add(mapTag(tag, index, domainModel.tags.size))
tagsList.add(mapTag(tag, index, domainModel.tags.size, maxViews))
}
else -> {
val isExpanded = areTagsEqual(tag, uiState.expandedTag)
tagsList.add(ExpandableItem(
mapCategory(tag, index, domainModel.tags.size),
mapCategory(tag, index, domainModel.tags.size, maxViews),
isExpanded
) { changedExpandedState ->
onUiState(uiState.copy(expandedTag = if (changedExpandedState) tag else null))
Expand All @@ -129,24 +130,36 @@ class TagsAndCategoriesUseCase
return items
}

private fun getBarWidth(
views: Long,
maxViews: Long
): Int? {
return if (maxViews > 0) {
((views.toDouble() / maxViews.toDouble()) * 100).toInt()
} else {
null
}
}

private fun buildTitle() = Title(string.stats_insights_tags_and_categories, menuAction = this::onMenuClick)

private fun areTagsEqual(tagA: TagModel, tagB: TagModel?): Boolean {
return tagA.items == tagB?.items && tagA.views == tagB.views
}

private fun mapTag(tag: TagsModel.TagModel, index: Int, listSize: Int): ListItemWithIcon {
private fun mapTag(tag: TagModel, index: Int, listSize: Int, maxViews: Long): ListItemWithIcon {
val item = tag.items.first()
return ListItemWithIcon(
icon = getIcon(item.type),
text = item.name,
value = tag.views.toFormattedString(),
barWidth = getBarWidth(tag.views, maxViews),
showDivider = index < listSize - 1,
navigationAction = NavigationAction.create(item.link, this::onTagClick)
)
}

private fun mapCategory(tag: TagsModel.TagModel, index: Int, listSize: Int): ListItemWithIcon {
private fun mapCategory(tag: TagModel, index: Int, listSize: Int, maxViews: Long): ListItemWithIcon {
val text = tag.items.foldIndexed("") { itemIndex, acc, item ->
when (itemIndex) {
0 -> item.name
Expand All @@ -157,6 +170,7 @@ class TagsAndCategoriesUseCase
icon = R.drawable.ic_folder_multiple_white_24dp,
text = text,
value = tag.views.toFormattedString(),
barWidth = getBarWidth(tag.views, maxViews),
showDivider = index < listSize - 1
)
}
Expand Down
Expand Up @@ -4,6 +4,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import org.wordpress.android.R
import org.wordpress.android.R.id
Expand All @@ -20,6 +21,7 @@ class ExpandableItemViewHolder(parent: ViewGroup, val imageManager: ImageManager
private val value = itemView.findViewById<TextView>(id.value)
private val divider = itemView.findViewById<View>(id.divider)
private val expandButton = itemView.findViewById<ImageView>(id.expand_button)
private var bar = itemView.findViewById<ProgressBar>(R.id.bar)

fun bind(
expandableItem: ExpandableItem,
Expand All @@ -31,6 +33,12 @@ class ExpandableItemViewHolder(parent: ViewGroup, val imageManager: ImageManager
expandButton.visibility = View.VISIBLE
value.setTextOrHide(header.valueResource, header.value)
divider.setVisible(header.showDivider && !expandableItem.isExpanded)
if (header.barWidth != null) {
bar.visibility = View.VISIBLE
bar.progress = header.barWidth
} else {
bar.visibility = View.GONE
}

if (expandChanged) {
val rotationAngle = if (expandButton.rotation == 0F) 180 else 0
Expand Down
Expand Up @@ -3,10 +3,10 @@ package org.wordpress.android.ui.stats.refresh.lists.sections.viewholders
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.wordpress.android.R
import org.wordpress.android.R.id
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithIcon
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithIcon.TextStyle
import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ListItemWithIcon.TextStyle.LIGHT
Expand All @@ -16,11 +16,12 @@ class ListItemWithIconViewHolder(parent: ViewGroup, val imageManager: ImageManag
parent,
R.layout.stats_block_list_item
) {
private val iconContainer = itemView.findViewById<LinearLayout>(id.icon_container)
private val text = itemView.findViewById<TextView>(id.text)
private val subtext = itemView.findViewById<TextView>(id.subtext)
private val value = itemView.findViewById<TextView>(id.value)
private val divider = itemView.findViewById<View>(id.divider)
private val iconContainer = itemView.findViewById<LinearLayout>(R.id.icon_container)
private val text = itemView.findViewById<TextView>(R.id.text)
private val value = itemView.findViewById<TextView>(R.id.value)
private val divider = itemView.findViewById<View>(R.id.divider)
private var topMargin = itemView.findViewById<View>(R.id.top_margin)
private var bar = itemView.findViewById<ProgressBar>(R.id.bar)

fun bind(item: ListItemWithIcon) {
iconContainer.setIconOrAvatar(item, imageManager)
Expand All @@ -30,7 +31,6 @@ class ListItemWithIconViewHolder(parent: ViewGroup, val imageManager: ImageManag
LIGHT -> R.color.neutral_500
}
text.setTextColor(ContextCompat.getColor(text.context, textColor))
subtext.setTextOrHide(item.subTextResource, item.subText)
value.setTextOrHide(item.valueResource, item.value)
divider.visibility = if (item.showDivider) {
View.VISIBLE
Expand All @@ -46,5 +46,13 @@ class ListItemWithIconViewHolder(parent: ViewGroup, val imageManager: ImageManag
itemView.background = null
itemView.setOnClickListener(null)
}
if (item.barWidth != null) {
bar.visibility = View.VISIBLE
topMargin.visibility = View.VISIBLE
bar.progress = item.barWidth
} else {
bar.visibility = View.GONE
topMargin.visibility = View.GONE
}
}
}
22 changes: 22 additions & 0 deletions WordPress/src/main/res/drawable/stats_bar_background.xml
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/transparent"/>
</shape>
</item>

<item
android:id="@android:id/progress"
android:bottom="@dimen/margin_extra_extra_small"
android:left="@dimen/margin_extra_extra_small"
android:right="@dimen/margin_extra_extra_small"
android:top="@dimen/margin_extra_extra_small">
<scale android:scaleWidth="100%">
<shape>
<corners android:radius="2dp"/>
<solid android:color="@color/gray_100"/>
</shape>
</scale>
</item>
</layer-list>
38 changes: 27 additions & 11 deletions WordPress/src/main/res/layout/stats_block_list_item.xml
Expand Up @@ -7,29 +7,42 @@
android:background="?android:attr/selectableItemBackground"
android:minHeight="@dimen/one_line_list_item_height">

<View
android:id="@+id/top_margin"
android:layout_width="0dp"
android:layout_height="@dimen/margin_medium_large"
android:background="@color/transparent"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintEnd_toStartOf="@+id/value"
app:layout_constraintStart_toStartOf="@+id/text"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible"/>

<TextView
android:id="@+id/text"
style="@style/StatsBlockItem"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_extra_large"
android:layout_marginStart="@dimen/stats_list_item_left_margin"
android:layout_marginTop="@dimen/margin_large"
android:ellipsize="end"
android:lines="1"
android:minHeight="@dimen/margin_extra_large"
android:textAlignment="viewStart"
android:textSize="@dimen/text_sz_large"
app:layout_constraintBottom_toTopOf="@+id/subtext"
app:layout_constraintBottom_toTopOf="@+id/bar"
app:layout_constraintEnd_toStartOf="@+id/value"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@+id/icon_container"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_margin"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginBottom="@dimen/margin_large"
app:layout_goneMarginStart="@dimen/margin_extra_large"
app:layout_goneMarginTop="@dimen/margin_large"
tools:text="@string/unknown"/>

<LinearLayout
Expand All @@ -38,7 +51,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="@dimen/margin_extra_large"
android:layout_marginTop="8dp"
android:layout_marginTop="@dimen/margin_medium"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
Expand Down Expand Up @@ -81,7 +94,7 @@
android:layout_width="16dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_extra_large"
android:layout_marginStart="8dp"
android:layout_marginStart="@dimen/margin_medium"
android:contentDescription="@string/icon"
android:src="@drawable/ic_chevron_down_white_16dp"
android:tint="@color/neutral_500"
Expand All @@ -93,21 +106,24 @@
app:layout_constraintStart_toEndOf="@+id/value"
app:layout_constraintTop_toTopOf="parent"/>

<TextView
android:id="@+id/subtext"
<ProgressBar
android:id="@+id/bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="5dp"
android:layout_marginBottom="@dimen/margin_large"
android:minHeight="@dimen/margin_extra_large"
android:layout_marginTop="@dimen/margin_key"
android:progressDrawable="@drawable/stats_bar_background"
android:text="@string/unknown"
android:textAlignment="viewStart"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/text"
app:layout_constraintStart_toStartOf="@+id/text"
app:layout_constraintTop_toBottomOf="@+id/text"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"/>
app:layout_constraintVertical_chainStyle="packed"
tools:progress="75"
tools:visibility="visible"/>

<View
android:id="@+id/divider"
Expand Down