Skip to content
Permalink
Browse files

Start of multi-select on watched shows

  • Loading branch information...
chrisbanes committed Aug 26, 2019
1 parent 8a1a073 commit c9854ef7f8a32c069206cc619b9f48bcfd0aca25
@@ -0,0 +1,28 @@
/*
* 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.
*/

package app.tivi.extensions

import kotlin.properties.ObservableProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

inline fun <T> observable(
initialValue: T,
crossinline onChange: () -> Unit
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange()
}
@@ -27,6 +27,8 @@ import app.tivi.ui.widget.PopupMenuButton
BindingMethod(type = SwipeRefreshLayout::class, attribute = "isRefreshing", method = "setRefreshing"),
BindingMethod(type = View::class, attribute = "clipToOutline", method = "setClipToOutline"),
BindingMethod(type = View::class, attribute = "activated", method = "setActivated"),
BindingMethod(type = View::class, attribute = "selected", method = "setSelected"),
BindingMethod(type = View::class, attribute = "onLongClick", method = "setOnLongClickListener"),
BindingMethod(type = PopupMenuButton::class, attribute = "popupMenuClickListener", method = "setMenuItemClickListener"),
BindingMethod(type = PopupMenuButton::class, attribute = "popupMenuListener", method = "setPopupMenuListener")
)
@@ -0,0 +1,20 @@
<?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.
-->

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorSecondary" android:alpha="0.25" />
</selector>
@@ -0,0 +1,43 @@
<?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.
-->


<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<ripple android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="#FF00FF" />
</item>
</ripple>
</item>

<item>
<selector>
<item
android:state_enabled="true"
android:state_selected="true">
<shape android:tint="@color/color_secondary_25">
<solid android:color="#FF00FF" />
</shape>
</item>

<item>
<color android:color="#00FFFFFF" />
</item>
</selector>
</item>
</layer-list>
@@ -28,8 +28,8 @@ import app.tivi.common.layouts.filter
import app.tivi.common.layouts.header
import app.tivi.home.HomeTextCreator
import app.tivi.ui.SortPopupMenuListener
import app.tivi.common.epoxy.EpoxyModelProperty
import app.tivi.common.epoxy.TotalSpanOverride
import app.tivi.extensions.observable
import app.tivi.ui.popupMenuItemIdToSortOption
import app.tivi.util.TiviDateFormatter
import com.airbnb.epoxy.EpoxyModel
@@ -40,8 +40,8 @@ class WatchedEpoxyController @Inject constructor(
private val textCreator: HomeTextCreator,
private val dateFormatter: TiviDateFormatter
) : PagedListEpoxyController<WatchedShowEntryWithShow>() {
var viewState by EpoxyModelProperty { WatchedViewState() }
var callbacks: Callbacks? = null
var viewState by observable(WatchedViewState()) { requestForcedModelBuild() }
var callbacks: Callbacks? by observable(null) { requestForcedModelBuild() }

override fun addModels(models: List<EpoxyModel<*>>) {
if (viewState.isEmpty) {
@@ -86,9 +86,11 @@ class WatchedEpoxyController @Inject constructor(
tiviShow(item.show)
posterImage(item.images.findHighestRatedPoster())
posterTransitionName("show_${item.show.homepage}")
clickListener(View.OnClickListener {
callbacks?.onItemClicked(item)
})
selected(item.entry.id in viewState.selectedEntryIds)
callbacks?.also { cb ->
clickListener(View.OnClickListener { cb.onItemClicked(item) })
longClickListener(View.OnLongClickListener { cb.onItemLongClicked(item) })
}
} else {
id("item_placeholder_$currentPosition")
}
@@ -105,6 +107,7 @@ class WatchedEpoxyController @Inject constructor(

interface Callbacks {
fun onItemClicked(item: WatchedShowEntryWithShow)
fun onItemLongClicked(item: WatchedShowEntryWithShow): Boolean
fun onFilterChanged(filter: String)
fun onSortSelected(sort: SortOption)
}
@@ -59,6 +59,11 @@ class WatchedFragment : TiviMvRxFragment() {

controller.callbacks = object : WatchedEpoxyController.Callbacks {
override fun onItemClicked(item: WatchedShowEntryWithShow) {
// Let the ViewModel have the first go
if (viewModel.onItemClick(item.entry)) {
return
}

val extras = listItemSharedElementHelper.createForItem(item, "poster") {
it.findViewById(R.id.show_poster)
}
@@ -71,6 +76,10 @@ class WatchedFragment : TiviMvRxFragment() {
)
}

override fun onItemLongClicked(item: WatchedShowEntryWithShow): Boolean {
return viewModel.onItemLongClick(item.entry)
}

override fun onFilterChanged(filter: String) = viewModel.setFilter(filter)

override fun onSortSelected(sort: SortOption) = viewModel.setSort(sort)
@@ -20,6 +20,7 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.PagedList
import app.tivi.TiviMvRxViewModel
import app.tivi.data.entities.SortOption
import app.tivi.data.entities.WatchedShowEntry
import app.tivi.data.resultentities.WatchedShowEntryWithShow
import app.tivi.domain.interactors.UpdateWatchedShows
import app.tivi.domain.invoke
@@ -64,6 +65,8 @@ class WatchedViewModel @AssistedInject constructor(

private val loadingState = ObservableLoadingCounter()

private var selectionOpen = false

init {
viewModelScope.launch {
loadingState.observable
@@ -130,6 +133,40 @@ class WatchedViewModel @AssistedInject constructor(
setState { copy(sort = sort) }
}

fun onItemClick(entry: WatchedShowEntry): Boolean {
if (selectionOpen) {
setState {
val currentSelection = when {
entry.id in selectedEntryIds -> selectedEntryIds.minus(entry.id)
else -> selectedEntryIds.plus(entry.id)
}
this@WatchedViewModel.selectionOpen = currentSelection.isNotEmpty()
copy(
selectionOpen = this@WatchedViewModel.selectionOpen,
selectedEntryIds = currentSelection
)
}
return true
}
return false
}

fun onItemLongClick(entry: WatchedShowEntry): Boolean {
if (!selectionOpen) {
selectionOpen = true

setState {
var currentSelection = selectedEntryIds
if (entry.id !in currentSelection) {
currentSelection = currentSelection.plus(entry.id)
}
copy(selectionOpen = selectionOpen, selectedEntryIds = currentSelection)
}
return true
}
return false
}

private fun refreshWatched(fromUser: Boolean) {
updateWatchedShows(UpdateWatchedShows.Params(fromUser)).also {
viewModelScope.launch {
@@ -27,6 +27,8 @@ data class WatchedViewState(
val isLoading: Boolean = false,
val isEmpty: Boolean = false,
val watchedShows: PagedList<WatchedShowEntryWithShow>? = null,
val selectionOpen: Boolean = false,
val selectedEntryIds: List<Long> = emptyList(),
val filterActive: Boolean = false,
val filter: String? = null,
val availableSorts: List<SortOption> = emptyList(),
@@ -35,6 +35,10 @@
name="tiviShow"
type="app.tivi.data.entities.TiviShow" />

<variable
name="selected"
type="boolean" />

<variable
name="posterImage"
type="app.tivi.data.entities.TmdbImageEntity" />
@@ -51,6 +55,10 @@
name="clickListener"
type="android.view.View.OnClickListener" />

<variable
name="longClickListener"
type="android.view.View.OnLongClickListener" />

<variable
name="dateFormatter"
type="app.tivi.util.TiviDateFormatter" />
@@ -59,8 +67,10 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:onClick="@{clickListener}">
android:foreground="@drawable/ripple_selectable"
android:onClick="@{clickListener}"
app:onLongClick="@{longClickListener}"
app:selected="@{selected}">

<View
android:id="@+id/background"

0 comments on commit c9854ef

Please sign in to comment.
You can’t perform that action at this time.