[fenix] For https://github.com/mozilla-mobile/fenix/issues/357 - Handles back press in edit mode

pull/600/head
Jeff Boek 5 years ago
parent 244b43db35
commit 40238c9f35

@ -0,0 +1,152 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.library.history
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer
import org.mozilla.fenix.R
class HistoryAdapter(
private val actionEmitter: Observer<HistoryAction>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class HistoryListItemViewHolder(
view: View,
private val actionEmitter: Observer<HistoryAction>
) : RecyclerView.ViewHolder(view) {
private val checkbox = view.findViewById<CheckBox>(R.id.should_remove_checkbox)
private val favicon = view.findViewById<ImageView>(R.id.history_favicon)
private val title = view.findViewById<TextView>(R.id.history_title)
private val url = view.findViewById<TextView>(R.id.history_url)
private var item: HistoryItem? = null
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
private val checkListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
if (mode is HistoryState.Mode.Normal) {
return@OnCheckedChangeListener
}
item?.apply {
val action = if (isChecked) {
HistoryAction.AddItemForRemoval(this)
} else {
HistoryAction.RemoveItemForRemoval(this)
}
actionEmitter.onNext(action)
}
}
init {
view.setOnClickListener {
if (mode is HistoryState.Mode.Editing) {
checkbox.isChecked = !checkbox.isChecked
return@setOnClickListener
}
item?.apply {
actionEmitter.onNext(HistoryAction.Select(this))
}
}
view.setOnLongClickListener {
item?.apply {
actionEmitter.onNext(HistoryAction.EnterEditMode(this))
}
true
}
checkbox.setOnCheckedChangeListener(checkListener)
}
fun bind(item: HistoryItem, mode: HistoryState.Mode) {
this.item = item
this.mode = mode
title.text = item.title
url.text = item.url
val isEditing = mode is HistoryState.Mode.Editing
checkbox.visibility = if (isEditing) { View.VISIBLE } else { View.GONE }
favicon.visibility = if (isEditing) { View.INVISIBLE } else { View.VISIBLE }
if (mode is HistoryState.Mode.Editing) {
checkbox.setOnCheckedChangeListener(null)
// Don't set the checkbox if it already contains the right value.
// This prevent us from cutting off the animation
val shouldCheck = mode.selectedItems.contains(item)
if (checkbox.isChecked != shouldCheck) {
checkbox.isChecked = mode.selectedItems.contains(item)
}
checkbox.setOnCheckedChangeListener(checkListener)
}
}
companion object {
const val LAYOUT_ID = R.layout.history_list_item
}
}
class HistoryHeaderViewHolder(
view: View
) : RecyclerView.ViewHolder(view) {
private val title = view.findViewById<TextView>(R.id.history_header_title)
fun bind(title: String) {
this.title.text = title
}
companion object {
const val LAYOUT_ID = R.layout.history_header
}
}
private var items: List<HistoryItem> = emptyList()
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
fun updateData(items: List<HistoryItem>, mode: HistoryState.Mode) {
this.items = items
this.mode = mode
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view)
HistoryListItemViewHolder.LAYOUT_ID -> HistoryListItemViewHolder(view, actionEmitter)
else -> throw IllegalStateException("viewType $viewType does not match to a ViewHolder")
}
}
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> HistoryHeaderViewHolder.LAYOUT_ID
else -> HistoryListItemViewHolder.LAYOUT_ID
}
}
override fun getItemCount(): Int = items.count() + NUMBER_OF_SECTIONS
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HistoryHeaderViewHolder -> holder.bind("Today")
is HistoryListItemViewHolder -> holder.bind(items[position - NUMBER_OF_SECTIONS], mode)
}
}
companion object {
private const val NUMBER_OF_SECTIONS = 1
}
}

@ -57,6 +57,7 @@ class HistoryComponent(
state state
} }
} }
is HistoryChange.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal)
} }
} }
@ -77,6 +78,7 @@ data class HistoryState(val items: List<HistoryItem>, val mode: Mode) : ViewStat
sealed class HistoryAction : Action { sealed class HistoryAction : Action {
data class Select(val item: HistoryItem) : HistoryAction() data class Select(val item: HistoryItem) : HistoryAction()
data class EnterEditMode(val item: HistoryItem) : HistoryAction() data class EnterEditMode(val item: HistoryItem) : HistoryAction()
object onBackPressed : HistoryAction()
data class AddItemForRemoval(val item: HistoryItem) : HistoryAction() data class AddItemForRemoval(val item: HistoryItem) : HistoryAction()
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryAction() data class RemoveItemForRemoval(val item: HistoryItem) : HistoryAction()
} }
@ -84,6 +86,7 @@ sealed class HistoryAction : Action {
sealed class HistoryChange : Change { sealed class HistoryChange : Change {
data class Change(val list: List<HistoryItem>) : HistoryChange() data class Change(val list: List<HistoryItem>) : HistoryChange()
data class EnterEditMode(val item: HistoryItem) : HistoryChange() data class EnterEditMode(val item: HistoryItem) : HistoryChange()
object ExitEditMode : HistoryChange()
data class AddItemForRemoval(val item: HistoryItem) : HistoryChange() data class AddItemForRemoval(val item: HistoryItem) : HistoryChange()
data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange() data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange()
} }

@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
@ -29,9 +30,11 @@ import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.mvi.getSafeManagedObservable import org.mozilla.fenix.mvi.getSafeManagedObservable
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class HistoryFragment : Fragment(), CoroutineScope { class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
private lateinit var job: Job private lateinit var job: Job
private lateinit var historyComponent: HistoryComponent
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job get() = Dispatchers.Main + job
@ -41,7 +44,7 @@ class HistoryFragment : Fragment(), CoroutineScope {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_history, container, false) val view = inflater.inflate(R.layout.fragment_history, container, false)
HistoryComponent(view.history_layout, ActionBusFactory.get(this)) historyComponent = HistoryComponent(view.history_layout, ActionBusFactory.get(this))
return view return view
} }
@ -64,6 +67,8 @@ class HistoryFragment : Fragment(), CoroutineScope {
.onNext(HistoryChange.AddItemForRemoval(it.item)) .onNext(HistoryChange.AddItemForRemoval(it.item))
is HistoryAction.RemoveItemForRemoval -> getManagedEmitter<HistoryChange>() is HistoryAction.RemoveItemForRemoval -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.RemoveItemForRemoval(it.item)) .onNext(HistoryChange.RemoveItemForRemoval(it.item))
is HistoryAction.onBackPressed -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.ExitEditMode)
} }
} }
} }
@ -118,4 +123,9 @@ class HistoryFragment : Fragment(), CoroutineScope {
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
override fun onBackPressed(): Boolean {
if((historyComponent.uiView as HistoryUIView).onBackPressed()) { return true }
return false
}
} }

@ -1,4 +1,3 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -6,17 +5,13 @@
package org.mozilla.fenix.library.history package org.mozilla.fenix.library.history
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.mvi.UIView import org.mozilla.fenix.mvi.UIView
@ -25,7 +20,11 @@ class HistoryUIView(
actionEmitter: Observer<HistoryAction>, actionEmitter: Observer<HistoryAction>,
changesObservable: Observable<HistoryChange> changesObservable: Observable<HistoryChange>
) : ) :
UIView<HistoryState, HistoryAction, HistoryChange>(container, actionEmitter, changesObservable) { UIView<HistoryState, HistoryAction, HistoryChange>(container, actionEmitter, changesObservable),
BackHandler {
var mode: HistoryState.Mode = HistoryState.Mode.Normal
private set
override val view: RecyclerView = LayoutInflater.from(container.context) override val view: RecyclerView = LayoutInflater.from(container.context)
.inflate(R.layout.component_history, container, true) .inflate(R.layout.component_history, container, true)
@ -39,143 +38,16 @@ class HistoryUIView(
} }
override fun updateView() = Consumer<HistoryState> { override fun updateView() = Consumer<HistoryState> {
mode = it.mode
(view.adapter as HistoryAdapter).updateData(it.items, it.mode) (view.adapter as HistoryAdapter).updateData(it.items, it.mode)
} }
}
private class HistoryAdapter(
private val actionEmitter: Observer<HistoryAction>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class HistoryListItemViewHolder(
view: View,
private val actionEmitter: Observer<HistoryAction>
) : RecyclerView.ViewHolder(view) {
private val checkbox = view.findViewById<CheckBox>(R.id.should_remove_checkbox)
private val favicon = view.findViewById<ImageView>(R.id.history_favicon)
private val title = view.findViewById<TextView>(R.id.history_title)
private val url = view.findViewById<TextView>(R.id.history_url)
private var item: HistoryItem? = null
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
private val checkListener = object : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
if (mode is HistoryState.Mode.Normal) { return }
item?.apply {
val action = if (isChecked) {
HistoryAction.AddItemForRemoval(this)
} else {
HistoryAction.RemoveItemForRemoval(this)
}
actionEmitter.onNext(action)
}
}
}
init {
view.setOnClickListener {
if (mode is HistoryState.Mode.Editing) {
checkbox.isChecked = !checkbox.isChecked
return@setOnClickListener
}
item?.apply {
actionEmitter.onNext(HistoryAction.Select(this))
}
}
view.setOnLongClickListener {
item?.apply {
actionEmitter.onNext(HistoryAction.EnterEditMode(this))
}
true
}
checkbox.setOnCheckedChangeListener(checkListener)
}
fun bind(item: HistoryItem, mode: HistoryState.Mode) {
this.item = item
this.mode = mode
title.text = item.title
url.text = item.url
val isEditing = mode is HistoryState.Mode.Editing
checkbox.visibility = if (isEditing) { View.VISIBLE } else { View.GONE }
favicon.visibility = if (isEditing) { View.INVISIBLE } else { View.VISIBLE }
if (mode is HistoryState.Mode.Editing) {
checkbox.setOnCheckedChangeListener(null)
// Don't set the checkbox if it already contains the right value.
// This prevent us from cutting off the animation
val shouldCheck = mode.selectedItems.contains(item)
if (checkbox.isChecked != shouldCheck) {
checkbox.isChecked = mode.selectedItems.contains(item)
}
checkbox.setOnCheckedChangeListener(checkListener)
}
}
companion object {
const val LAYOUT_ID = R.layout.history_list_item
}
}
class HistoryHeaderViewHolder(
view: View
) : RecyclerView.ViewHolder(view) {
private val title = view.findViewById<TextView>(R.id.history_header_title)
fun bind(title: String) { override fun onBackPressed(): Boolean {
this.title.text = title if (mode is HistoryState.Mode.Editing) {
actionEmitter.onNext(HistoryAction.onBackPressed)
return true
} }
companion object { return false
const val LAYOUT_ID = R.layout.history_header
}
}
private var items: List<HistoryItem> = emptyList()
private var mode: HistoryState.Mode = HistoryState.Mode.Normal
fun updateData(items: List<HistoryItem>, mode: HistoryState.Mode) {
this.items = items
this.mode = mode
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view)
HistoryListItemViewHolder.LAYOUT_ID -> HistoryListItemViewHolder(view, actionEmitter)
else -> throw IllegalStateException("viewType $viewType does not match to a ViewHolder")
}
}
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> HistoryHeaderViewHolder.LAYOUT_ID
else -> HistoryListItemViewHolder.LAYOUT_ID
}
}
override fun getItemCount(): Int = items.count() + NUMBER_OF_SECTIONS
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HistoryHeaderViewHolder -> holder.bind("Today")
is HistoryListItemViewHolder -> holder.bind(items[position - NUMBER_OF_SECTIONS], mode)
}
}
companion object {
private const val NUMBER_OF_SECTIONS = 1
} }
} }

Loading…
Cancel
Save