1
0
Fork 0

For #356 - Gives a user the ability to delete their history

master
Jeff Boek 2019-03-29 09:46:34 -07:00
parent 6491adf029
commit 00ad9d3f6f
6 changed files with 103 additions and 91 deletions

View File

@ -13,8 +13,6 @@ import android.widget.CompoundButton
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer import io.reactivex.Observer
import org.mozilla.fenix.R import org.mozilla.fenix.R
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.history_delete.view.*
import kotlinx.android.synthetic.main.history_header.view.* import kotlinx.android.synthetic.main.history_header.view.*
import kotlinx.android.synthetic.main.history_list_item.view.* import kotlinx.android.synthetic.main.history_list_item.view.*
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
@ -226,48 +224,6 @@ class HistoryAdapter(
} }
} }
class HistoryDeleteViewHolder(
view: View,
private val actionEmitter: Observer<HistoryAction>
) : RecyclerView.ViewHolder(view) {
private lateinit var mode: HistoryState.Mode
private val button = view.delete_history_button.apply {
setOnClickListener {
val mode = mode
if (mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) {
actionEmitter.onNext(HistoryAction.Delete.Some(mode.selectedItems))
} else {
actionEmitter.onNext(HistoryAction.Delete.All)
}
}
}
private val text = view.delete_history_button_text.apply {
val color = ContextCompat.getColor(context, R.color.photonRed60)
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_delete)
drawable?.setTint(color)
this.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
}
fun bind(mode: HistoryState.Mode) {
this.mode = mode
val text = if (mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) {
text.context.resources.getString(R.string.history_delete_some, mode.selectedItems.size)
} else {
text.context.resources.getString(R.string.history_delete_all)
}
button.contentDescription = text
this.text.text = text
}
companion object {
const val LAYOUT_ID = R.layout.history_delete
}
}
private var historyList: HistoryList = HistoryList(emptyList()) private var historyList: HistoryList = HistoryList(emptyList())
private var mode: HistoryState.Mode = HistoryState.Mode.Normal private var mode: HistoryState.Mode = HistoryState.Mode.Normal

View File

@ -39,7 +39,7 @@ class HistoryComponent(
override val reducer: (HistoryState, HistoryChange) -> HistoryState = { state, change -> override val reducer: (HistoryState, HistoryChange) -> HistoryState = { state, change ->
when (change) { when (change) {
is HistoryChange.Change -> state.copy(items = change.list) is HistoryChange.Change -> state.copy(mode = HistoryState.Mode.Normal, items = change.list)
is HistoryChange.EnterEditMode -> state.copy(mode = HistoryState.Mode.Editing(listOf(change.item))) is HistoryChange.EnterEditMode -> state.copy(mode = HistoryState.Mode.Editing(listOf(change.item)))
is HistoryChange.AddItemForRemoval -> { is HistoryChange.AddItemForRemoval -> {
val mode = state.mode val mode = state.mode

View File

@ -17,9 +17,10 @@ import androidx.navigation.NavOptions
import androidx.navigation.Navigation import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_history.view.* import kotlinx.android.synthetic.main.fragment_history.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.coroutineScope
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.ItsNotBrokenSnack
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -29,6 +30,7 @@ import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@SuppressWarnings("TooManyFunctions")
class HistoryFragment : Fragment(), CoroutineScope, BackHandler { class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
private lateinit var job: Job private lateinit var job: Job
@ -81,18 +83,13 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val items = requireComponents.core.historyStorage.getDetailedVisits(0) reloadData()
.asReversed()
.mapIndexed { id, item -> HistoryItem(id, item.url, item.visitTime) }
launch(Dispatchers.Main) {
getManagedEmitter<HistoryChange>().onNext(HistoryChange.Change(items))
}
} }
} }
// This method triggers the complexity warning. However it's actually not that hard to understand.
@SuppressWarnings("ComplexMethod")
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
getAutoDisposeObservable<HistoryAction>() getAutoDisposeObservable<HistoryAction>()
@ -107,6 +104,20 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
.onNext(HistoryChange.RemoveItemForRemoval(it.item)) .onNext(HistoryChange.RemoveItemForRemoval(it.item))
is HistoryAction.BackPressed -> getManagedEmitter<HistoryChange>() is HistoryAction.BackPressed -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.ExitEditMode) .onNext(HistoryChange.ExitEditMode)
is HistoryAction.Delete.All -> launch(Dispatchers.IO) {
requireComponents.core.historyStorage.deleteEverything()
reloadData()
}
is HistoryAction.Delete.One -> launch(Dispatchers.IO) {
requireComponents.core.historyStorage.deleteVisit(it.item.url, it.item.visitedAt)
reloadData()
}
is HistoryAction.Delete.Some -> launch(Dispatchers.IO) {
it.items.forEach { item ->
requireComponents.core.historyStorage.deleteVisit(item.url, item.visitedAt)
}
reloadData()
}
} }
} }
} }
@ -128,4 +139,16 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
} }
override fun onBackPressed(): Boolean = (historyComponent.uiView as HistoryUIView).onBackPressed() override fun onBackPressed(): Boolean = (historyComponent.uiView as HistoryUIView).onBackPressed()
private suspend fun reloadData() {
val items = requireComponents.core.historyStorage.getDetailedVisits(0)
.asReversed()
.mapIndexed { id, item -> HistoryItem(id, item.url, item.visitTime) }
coroutineScope {
launch(Dispatchers.Main) {
getManagedEmitter<HistoryChange>().onNext(HistoryChange.Change(items))
}
}
}
} }

View File

@ -6,11 +6,12 @@ package org.mozilla.fenix.library.history
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
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 kotlinx.android.synthetic.main.component_history.view.*
import mozilla.components.support.base.feature.BackHandler 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
@ -26,22 +27,48 @@ class HistoryUIView(
var mode: HistoryState.Mode = HistoryState.Mode.Normal var mode: HistoryState.Mode = HistoryState.Mode.Normal
private set private set
override val view: RecyclerView = LayoutInflater.from(container.context) override val view: NestedScrollView = LayoutInflater.from(container.context)
.inflate(R.layout.component_history, container, true) .inflate(R.layout.component_history, container, true)
.findViewById(R.id.history_list) .findViewById(R.id.history_wrapper)
init { init {
view.apply { view.history_list.apply {
adapter = HistoryAdapter(actionEmitter) adapter = HistoryAdapter(actionEmitter)
layoutManager = LinearLayoutManager(container.context) layoutManager = LinearLayoutManager(container.context)
} }
view.delete_history_button.setOnClickListener {
val mode = mode
val action = when (mode) {
is HistoryState.Mode.Normal -> HistoryAction.Delete.All
is HistoryState.Mode.Editing -> HistoryAction.Delete.Some(mode.selectedItems)
}
actionEmitter.onNext(action)
}
} }
override fun updateView() = Consumer<HistoryState> { override fun updateView() = Consumer<HistoryState> {
mode = it.mode mode = it.mode
(view.adapter as HistoryAdapter).updateData(it.items, it.mode) updateDeleteButton()
(view.history_list.adapter as HistoryAdapter).updateData(it.items, it.mode)
} }
private fun updateDeleteButton() {
val mode = mode
val text = if (mode is HistoryState.Mode.Editing && mode.selectedItems.isNotEmpty()) {
view.delete_history_button_text.context.resources.getString(
R.string.history_delete_some,
mode.selectedItems.size
)
} else {
view.delete_history_button_text.context.resources.getString(R.string.history_delete_all)
}
view.delete_history_button.contentDescription = text
view.delete_history_button_text.text = text
}
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
if (mode is HistoryState.Mode.Editing) { if (mode is HistoryState.Mode.Editing) {
actionEmitter.onNext(HistoryAction.BackPressed) actionEmitter.onNext(HistoryAction.BackPressed)

View File

@ -3,8 +3,42 @@
- 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/. -->
<androidx.recyclerview.widget.RecyclerView <androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/history_wrapper"
android:id="@+id/history_list" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/delete_history_button"
android:foreground="?android:attr/selectableItemBackground"
android:background="@drawable/button_background"
android:layout_margin="16dp"
android:padding="6dp"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/delete_history_button_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/history_delete_all"
android:textColor="@color/photonRed60"
android:drawablePadding="8dp"
android:textSize="16sp"
android:gravity="center"
android:clickable="false"
android:focusable="false"
android:layout_gravity="center" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/history_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/delete_history_button"
android:foreground="?android:attr/selectableItemBackground"
android:background="@drawable/button_background"
android:layout_margin="16dp"
android:padding="6dp"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/delete_history_button_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/history_delete_all"
android:textColor="@color/photonRed60"
android:drawablePadding="8dp"
android:textSize="16sp"
android:gravity="center"
android:clickable="false"
android:focusable="false"
android:layout_gravity="center" />
</FrameLayout>