diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt new file mode 100644 index 000000000..3f45b275b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt @@ -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 +) : RecyclerView.Adapter() { + class HistoryListItemViewHolder( + view: View, + private val actionEmitter: Observer + ) : RecyclerView.ViewHolder(view) { + + private val checkbox = view.findViewById(R.id.should_remove_checkbox) + private val favicon = view.findViewById(R.id.history_favicon) + private val title = view.findViewById(R.id.history_title) + private val url = view.findViewById(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(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 = emptyList() + private var mode: HistoryState.Mode = HistoryState.Mode.Normal + + fun updateData(items: List, 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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryComponent.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryComponent.kt index 6cb027ed0..46061d62e 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryComponent.kt @@ -57,6 +57,7 @@ class HistoryComponent( state } } + is HistoryChange.ExitEditMode -> state.copy(mode = HistoryState.Mode.Normal) } } @@ -77,6 +78,7 @@ data class HistoryState(val items: List, val mode: Mode) : ViewStat sealed class HistoryAction : Action { data class Select(val item: HistoryItem) : HistoryAction() data class EnterEditMode(val item: HistoryItem) : HistoryAction() + object onBackPressed : HistoryAction() data class AddItemForRemoval(val item: HistoryItem) : HistoryAction() data class RemoveItemForRemoval(val item: HistoryItem) : HistoryAction() } @@ -84,6 +86,7 @@ sealed class HistoryAction : Action { sealed class HistoryChange : Change { data class Change(val list: List) : HistoryChange() data class EnterEditMode(val item: HistoryItem) : HistoryChange() + object ExitEditMode : HistoryChange() data class AddItemForRemoval(val item: HistoryItem) : HistoryChange() data class RemoveItemForRemoval(val item: HistoryItem) : HistoryChange() } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index 0e3b6a777..972768a19 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.mozilla.fenix.HomeActivity +import mozilla.components.support.base.feature.BackHandler import org.mozilla.fenix.R import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.mvi.ActionBusFactory @@ -29,9 +30,11 @@ import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getSafeManagedObservable import kotlin.coroutines.CoroutineContext -class HistoryFragment : Fragment(), CoroutineScope { +class HistoryFragment : Fragment(), CoroutineScope, BackHandler { private lateinit var job: Job + private lateinit var historyComponent: HistoryComponent + override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job @@ -41,7 +44,7 @@ class HistoryFragment : Fragment(), CoroutineScope { savedInstanceState: Bundle? ): View? { 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 } @@ -64,6 +67,8 @@ class HistoryFragment : Fragment(), CoroutineScope { .onNext(HistoryChange.AddItemForRemoval(it.item)) is HistoryAction.RemoveItemForRemoval -> getManagedEmitter() .onNext(HistoryChange.RemoveItemForRemoval(it.item)) + is HistoryAction.onBackPressed -> getManagedEmitter() + .onNext(HistoryChange.ExitEditMode) } } } @@ -118,4 +123,9 @@ class HistoryFragment : Fragment(), CoroutineScope { else -> super.onOptionsItemSelected(item) } } + + override fun onBackPressed(): Boolean { + if((historyComponent.uiView as HistoryUIView).onBackPressed()) { return true } + return false + } } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryUIView.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryUIView.kt index 0f818200a..191a07bce 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryUIView.kt @@ -1,4 +1,3 @@ - /* 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/. */ @@ -6,17 +5,13 @@ 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.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.reactivex.Observable import io.reactivex.Observer import io.reactivex.functions.Consumer +import mozilla.components.support.base.feature.BackHandler import org.mozilla.fenix.R import org.mozilla.fenix.mvi.UIView @@ -25,7 +20,11 @@ class HistoryUIView( actionEmitter: Observer, changesObservable: Observable ) : - UIView(container, actionEmitter, changesObservable) { + UIView(container, actionEmitter, changesObservable), + BackHandler { + + var mode: HistoryState.Mode = HistoryState.Mode.Normal + private set override val view: RecyclerView = LayoutInflater.from(container.context) .inflate(R.layout.component_history, container, true) @@ -39,143 +38,16 @@ class HistoryUIView( } override fun updateView() = Consumer { + mode = it.mode (view.adapter as HistoryAdapter).updateData(it.items, it.mode) } -} -private class HistoryAdapter( - private val actionEmitter: Observer -) : RecyclerView.Adapter() { - class HistoryListItemViewHolder( - view: View, - private val actionEmitter: Observer - ) : RecyclerView.ViewHolder(view) { - - private val checkbox = view.findViewById(R.id.should_remove_checkbox) - private val favicon = view.findViewById(R.id.history_favicon) - private val title = view.findViewById(R.id.history_title) - private val url = view.findViewById(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) - } - } + override fun onBackPressed(): Boolean { + if (mode is HistoryState.Mode.Editing) { + actionEmitter.onNext(HistoryAction.onBackPressed) + return true } - 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(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 = emptyList() - private var mode: HistoryState.Mode = HistoryState.Mode.Normal - - fun updateData(items: List, 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 + return false } }