Skip to content

Commit

Permalink
Merge pull request #514 from chrisbanes/cb/show-details-refresh
Browse files Browse the repository at this point in the history
Redesign the show details header
  • Loading branch information
chrisbanes committed Nov 7, 2019
2 parents 42a52ca + f3034af commit d72f561
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 103 deletions.
Expand Up @@ -16,6 +16,9 @@

package app.tivi.common.epoxy

import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel

/** Add models to a CarouselModel_ by transforming a list of items into EpoxyModels.
Expand All @@ -28,4 +31,15 @@ inline fun <T> TiviCarouselModelBuilder.withModelsFrom(
modelBuilder: (T) -> EpoxyModel<*>
) {
models(items.map { modelBuilder(it) })
}

fun RecyclerView.syncSpanSizes(controller: EpoxyController) {
val layout = layoutManager
if (layout is GridLayoutManager) {
if (controller.spanCount != layout.spanCount ||
layout.spanSizeLookup !== controller.spanSizeLookup) {
controller.spanCount = layout.spanCount
layout.spanSizeLookup = controller.spanSizeLookup
}
}
}
Expand Up @@ -20,4 +20,8 @@ import com.airbnb.epoxy.EpoxyModel

object TotalSpanOverride : EpoxyModel.SpanSizeOverrideCallback {
override fun getSpanSize(totalSpanCount: Int, position: Int, itemCount: Int) = totalSpanCount
}

object HalfSpanOverride : EpoxyModel.SpanSizeOverrideCallback {
override fun getSpanSize(totalSpanCount: Int, position: Int, itemCount: Int) = (totalSpanCount / 2).coerceAtLeast(1)
}
Expand Up @@ -22,6 +22,7 @@ import app.tivi.data.entities.ImageType
import app.tivi.data.entities.ShowTmdbImage
import app.tivi.data.entities.TmdbImageEntity
import coil.api.loadAny
import coil.transform.RoundedCornersTransformation

@BindingAdapter(
"tmdbBackdropPath",
Expand All @@ -38,30 +39,40 @@ fun ImageView.loadBackdrop(
loadImage(
null,
oldSaturateOnLoad,
0f,
path?.let { ShowTmdbImage(path = path, type = ImageType.BACKDROP, showId = 0) },
saturateOnLoad
saturateOnLoad,
0f
)
}
}

@BindingAdapter(
"image",
"imageSaturateOnLoad",
"imageCornerRadius",
requireAll = false
)
fun ImageView.loadImage(
oldImage: TmdbImageEntity?,
oldSaturateOnLoad: Boolean?,
oldCornerRadius: Float,
image: TmdbImageEntity?,
saturateOnLoad: Boolean?
saturateOnLoad: Boolean?,
cornerRadius: Float
) {
if (oldImage == image && oldSaturateOnLoad == saturateOnLoad) return
if (oldImage == image &&
oldSaturateOnLoad == saturateOnLoad &&
oldCornerRadius == cornerRadius) return

loadAny(image) {
if (saturateOnLoad == null || saturateOnLoad == true) {
val saturatingTarget = SaturatingImageViewTarget(this@loadImage)
target(saturatingTarget)
listener(saturatingTarget)
}
if (cornerRadius > 0) {
transformations(RoundedCornersTransformation(cornerRadius))
}
}
}
9 changes: 4 additions & 5 deletions common-layouts/src/main/res/layout/layout_badge_holder.xml
Expand Up @@ -16,10 +16,9 @@
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:animateLayoutChanges="true">

</LinearLayout>
android:animateLayoutChanges="true"
android:orientation="vertical"
android:paddingVertical="@dimen/spacing_small"
android:paddingEnd="@dimen/spacing_normal" />
Expand Up @@ -36,7 +36,6 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:contentDescription="@{contentDescription}"
android:transitionGroup="true">

Expand Down
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="title"
type="CharSequence" />

<variable
name="label"
type="CharSequence" />
</data>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/spacing_small"
android:transitionGroup="true">

<app.tivi.ui.widget.BaselineGridTextView
android:id="@+id/details_info_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_micro"
android:text="@{title}"
android:textAppearance="?attr/textAppearanceSubtitle2"
tools:text="Network" />

<app.tivi.ui.widget.BaselineGridTextView
android:id="@+id/details_info_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:text="@{label}"
android:textAppearance="?attr/textAppearanceBody2"
tools:text="HBO" />

</LinearLayout>
</layout>
5 changes: 5 additions & 0 deletions common-ui/src/main/res/values/strings.xml
Expand Up @@ -61,6 +61,11 @@
<string name="percentage_format">%1$d%%</string>
<string name="minutes_format">%1$dm</string>

<string name="rating_title">Rating</string>
<string name="network_title">Network</string>
<string name="certificate_title">Certificate</string>
<string name="runtime_title">Length</string>

<string name="rating_content_description_format">Rating: %1$d%%</string>
<string name="network_content_description_format">Network: %1$s</string>
<string name="certificate_content_description_format">Certificate: %1$s</string>
Expand Down
Expand Up @@ -21,10 +21,11 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.forEach
import app.tivi.common.epoxy.HalfSpanOverride
import app.tivi.common.epoxy.TotalSpanOverride
import app.tivi.common.epoxy.tiviCarousel
import app.tivi.common.epoxy.withModelsFrom
import app.tivi.common.layouts.DetailsBadgeBindingModel_
import app.tivi.common.layouts.DetailsInfoItemBindingModel_
import app.tivi.common.layouts.detailsHeader
import app.tivi.data.entities.ActionDate
import app.tivi.data.entities.Episode
Expand All @@ -40,6 +41,7 @@ import app.tivi.showdetails.details.databinding.ViewHolderDetailsSeasonBinding
import app.tivi.ui.widget.PopupMenuButton
import com.airbnb.epoxy.Carousel
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyModelGroup
import com.airbnb.epoxy.IdUtils
import com.airbnb.mvrx.Async
Expand Down Expand Up @@ -67,7 +69,7 @@ class ShowDetailsEpoxyController @Inject constructor(
}

override fun buildModels() {
buildShowModels(state.show)
buildShowModels(state)

val episodeWithSeason = state.nextEpisodeToWatch()
if (episodeWithSeason?.episode != null) {
Expand All @@ -93,46 +95,48 @@ class ShowDetailsEpoxyController @Inject constructor(
buildSeasonsModels(state.viewStats, state.seasons, state.expandedSeasonIds)
}

private fun buildShowModels(show: TiviShow) {
val badges = ArrayList<DetailsBadgeBindingModel_>()
private fun buildShowModels(state: ShowDetailsViewState) {
detailsPosterItem {
id("poster")
posterImage(state.posterImage)
spanSizeOverride(HalfSpanOverride)
}

val show = state.show
val badges = ArrayList<EpoxyModel<*>>()
show.traktRating?.let { rating ->
badges += DetailsBadgeBindingModel_().apply {
badges += DetailsInfoItemBindingModel_().apply {
val ratingOutOfOneHundred = (rating * 10).roundToInt()
id("rating")
label(context.getString(R.string.percentage_format, ratingOutOfOneHundred))
icon(R.drawable.ic_details_rating)
contentDescription(context.getString(R.string.rating_content_description_format,
ratingOutOfOneHundred))
title(textCreator.getString(R.string.rating_title))
}
}
show.network?.let { network ->
badges += DetailsBadgeBindingModel_().apply {
badges += DetailsInfoItemBindingModel_().apply {
id("network")
label(network)
icon(R.drawable.ic_details_network)
contentDescription(context.getString(R.string.network_content_description_format, network))
title(textCreator.getString(R.string.network_title))
}
}
show.certification?.let { certificate ->
badges += DetailsBadgeBindingModel_().apply {
badges += DetailsInfoItemBindingModel_().apply {
id("cert")
label(certificate)
icon(R.drawable.ic_details_certificate)
contentDescription(context.getString(R.string.certificate_content_description_format, certificate))
title(textCreator.getString(R.string.certificate_title))
}
}
show.runtime?.let { runtime ->
badges += DetailsBadgeBindingModel_().apply {
badges += DetailsInfoItemBindingModel_().apply {
val runtimeMinutes = context.getString(R.string.minutes_format, runtime)
id("runtime")
label(runtimeMinutes)
icon(R.drawable.ic_details_runtime)
contentDescription(context.resources?.getQuantityString(
R.plurals.runtime_content_description_format, runtime, runtime))
title(textCreator.getString(R.string.runtime_title))
}
}
if (badges.isNotEmpty()) {
EpoxyModelGroup(R.layout.layout_badge_holder, badges).addTo(this)
EpoxyModelGroup(R.layout.layout_badge_holder, badges)
.addTo(this)
}

detailsHeader {
Expand Down Expand Up @@ -168,6 +172,7 @@ class ShowDetailsEpoxyController @Inject constructor(
id("related_shows")
itemWidth(context.resources.getDimensionPixelSize(R.dimen.related_shows_item_width))
hasFixedSize(true)
spanSizeOverride(TotalSpanOverride)

val vert = context.resources.getDimensionPixelSize(R.dimen.spacing_small)
val horiz = context.resources.getDimensionPixelSize(R.dimen.spacing_normal)
Expand Down
Expand Up @@ -31,6 +31,7 @@ import androidx.fragment.app.commitNow
import androidx.recyclerview.widget.LinearSmoothScroller
import app.tivi.SharedElementHelper
import app.tivi.TiviFragmentWithBinding
import app.tivi.common.epoxy.syncSpanSizes
import app.tivi.data.entities.ActionDate
import app.tivi.data.entities.Episode
import app.tivi.data.entities.Season
Expand Down Expand Up @@ -175,6 +176,9 @@ class ShowDetailsFragment : TiviFragmentWithBinding<FragmentShowDetailsBinding>(

binding.detailsRv.apply {
adapter = controller.adapter
syncSpanSizes(controller)
setHasFixedSize(true)

tintPainter = TintPainter.completeList(
context.resolveThemeColor(R.attr.colorSurface),
opacity = 0.7f
Expand Down
Expand Up @@ -18,6 +18,7 @@ package app.tivi.showdetails.details

import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.core.text.parseAsHtml
Expand Down Expand Up @@ -121,4 +122,6 @@ class ShowDetailsTextCreator @Inject constructor(
return ""
}
}

fun getString(@StringRes stringRes: Int) = context.getString(stringRes)
}
25 changes: 5 additions & 20 deletions ui-showdetails/src/main/res/layout/fragment_show_details.xml
Expand Up @@ -33,8 +33,7 @@
android:id="@+id/details_motion"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/scene_show_details"
app:layout_optimizationLevel="standard">
app:layoutDescription="@xml/scene_show_details">

<ImageView
android:id="@+id/details_backdrop"
Expand All @@ -59,20 +58,6 @@
app:layout_constraintStart_toStartOf="@id/details_backdrop"
app:layout_constraintTop_toTopOf="@id/details_backdrop" />

<ImageView
android:id="@+id/details_poster"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/placeholder_48dp"
android:scaleType="centerCrop"
android:transformPivotX="0px"
android:transformPivotY="0px"
android:transitionName="poster"
app:clipToOutline="@{true}"
app:imageSaturateOnLoad="@{false}"
app:outlineProviderInstance="@{app.tivi.ui.RoundRectViewOutline.INSTANCE}"
app:image="@{state.posterImage}" />

<!-- Needed to fill a rounding error gap in MotionLayout. See https://issuetracker.google.com/112728689 -->
<View
android:id="@+id/details_gap_filler"
Expand All @@ -81,12 +66,11 @@
android:background="?android:attr/colorBackground"
app:layout_constraintBottom_toTopOf="@id/details_rv" />

<app.tivi.ui.widget.TopLeftCutoutBackgroundView
<View
android:id="@+id/details_appbar_background"
android:layout_width="0dp"
android:layout_height="0dp"
app:backgroundColor="?android:attr/colorBackground"
app:topLeftCutSize="@dimen/details_corner_cutout" />
android:background="?android:attr/colorBackground" />

<TextView
android:id="@+id/details_title"
Expand Down Expand Up @@ -126,7 +110,8 @@
android:background="?android:attr/colorBackground"
android:clipToPadding="false"
android:paddingBottom="@dimen/spacing_normal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
Expand Down

0 comments on commit d72f561

Please sign in to comment.