For #636 - Allow a user to delete a session
parent
2ac2f9a9da
commit
d5aeec417b
|
@ -71,11 +71,13 @@ class HomeFragment : Fragment() {
|
||||||
tabsComponent.view.isNestedScrollingEnabled = false
|
tabsComponent.view.isNestedScrollingEnabled = false
|
||||||
sessionsComponent.view.isNestedScrollingEnabled = false
|
sessionsComponent.view.isNestedScrollingEnabled = false
|
||||||
|
|
||||||
val bundles = requireComponents.core.sessionStorage.bundles(40)
|
val bundles = requireComponents.core.sessionStorage.bundles(temporaryNumberOfSessions)
|
||||||
|
|
||||||
bundles.observe(this, Observer {sessionBundles ->
|
bundles.observe(this, Observer { sessionBundles ->
|
||||||
val archivedSessions = sessionBundles.mapNotNull { sessionBundle ->
|
val archivedSessions = sessionBundles.mapNotNull { sessionBundle ->
|
||||||
sessionBundle.id?.let { ArchivedSession(it, sessionBundle, sessionBundle.lastSavedAt, sessionBundle.urls) }
|
sessionBundle.id?.let {
|
||||||
|
ArchivedSession(it, sessionBundle, sessionBundle.lastSavedAt, sessionBundle.urls)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getManagedEmitter<SessionsChange>().onNext(SessionsChange.Changed(archivedSessions))
|
getManagedEmitter<SessionsChange>().onNext(SessionsChange.Changed(archivedSessions))
|
||||||
|
@ -171,6 +173,9 @@ class HomeFragment : Fragment() {
|
||||||
requireComponents.core.sessionManager.restore(this)
|
requireComponents.core.sessionManager.restore(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is SessionsAction.Delete -> {
|
||||||
|
requireComponents.core.sessionStorage.remove(it.archivedSession.bundle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,5 +251,7 @@ class HomeFragment : Fragment() {
|
||||||
const val toolbarPaddingDp = 12f
|
const val toolbarPaddingDp = 12f
|
||||||
const val firstKeyTriggerFrame = 55
|
const val firstKeyTriggerFrame = 55
|
||||||
const val secondKeyTriggerFrame = 90
|
const val secondKeyTriggerFrame = 90
|
||||||
|
|
||||||
|
const val temporaryNumberOfSessions = 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* 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.home.sessions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||||
|
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
class SessionItemMenu(
|
||||||
|
private val context: Context,
|
||||||
|
private val onItemTapped: (Item) -> Unit = {}
|
||||||
|
) {
|
||||||
|
sealed class Item {
|
||||||
|
object Delete : Item()
|
||||||
|
}
|
||||||
|
|
||||||
|
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
|
||||||
|
|
||||||
|
private val menuItems by lazy {
|
||||||
|
listOf(
|
||||||
|
SimpleBrowserMenuItem(
|
||||||
|
context.getString(R.string.session_item_delete)
|
||||||
|
) {
|
||||||
|
onItemTapped.invoke(Item.Delete)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,9 @@ import kotlinx.android.extensions.LayoutContainer
|
||||||
import kotlinx.android.synthetic.main.session_item.*
|
import kotlinx.android.synthetic.main.session_item.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
class SessionsAdapter(private val actionEmitter: Observer<SessionsAction>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class SessionsAdapter(
|
||||||
|
private val actionEmitter: Observer<SessionsAction>
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
private var items = listOf<ArchivedSession>()
|
private var items = listOf<ArchivedSession>()
|
||||||
|
|
||||||
fun reloadDatat(items: List<ArchivedSession>) {
|
fun reloadDatat(items: List<ArchivedSession>) {
|
||||||
|
@ -89,20 +91,27 @@ class SessionsAdapter(private val actionEmitter: Observer<SessionsAction>) : Rec
|
||||||
view: View,
|
view: View,
|
||||||
private val actionEmitter: Observer<SessionsAction>,
|
private val actionEmitter: Observer<SessionsAction>,
|
||||||
override val containerView: View? = view
|
override val containerView: View? = view
|
||||||
) :RecyclerView.ViewHolder(view), LayoutContainer {
|
) : RecyclerView.ViewHolder(view), LayoutContainer {
|
||||||
private var session: ArchivedSession? = null
|
private var session: ArchivedSession? = null
|
||||||
|
private lateinit var sessionMenu: SessionItemMenu
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
setupMenu()
|
||||||
|
|
||||||
session_item.setOnClickListener {
|
session_item.setOnClickListener {
|
||||||
session?.apply { actionEmitter.onNext(SessionsAction.Select(this)) }
|
session?.apply { actionEmitter.onNext(SessionsAction.Select(this)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session_card_overflow_button.setOnClickListener {
|
||||||
|
sessionMenu.menuBuilder.build(itemView.context).show(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(session: ArchivedSession) {
|
fun bind(session: ArchivedSession) {
|
||||||
this.session = session
|
this.session = session
|
||||||
|
|
||||||
session_card_thumbnail.setColorFilter(
|
session_card_thumbnail.setColorFilter(
|
||||||
ContextCompat.getColor(itemView.context, AVAILABLE_COLOR_IDS.random()),
|
ContextCompat.getColor(itemView.context, availableColors.random()),
|
||||||
PorterDuff.Mode.MULTIPLY)
|
PorterDuff.Mode.MULTIPLY)
|
||||||
session_card_timestamp.text = session.formattedSavedAt
|
session_card_timestamp.text = session.formattedSavedAt
|
||||||
session_card_titles.text = session.titles
|
session_card_titles.text = session.titles
|
||||||
|
@ -111,8 +120,18 @@ class SessionsAdapter(private val actionEmitter: Observer<SessionsAction>) : Rec
|
||||||
} else { "" }
|
} else { "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupMenu() {
|
||||||
|
sessionMenu = SessionItemMenu(itemView.context) {
|
||||||
|
if (it is SessionItemMenu.Item.Delete) {
|
||||||
|
session?.apply {
|
||||||
|
actionEmitter.onNext(SessionsAction.Delete(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val AVAILABLE_COLOR_IDS = listOf(
|
private val availableColors = listOf(
|
||||||
R.color.photonBlue40, R.color.photonGreen50, R.color.photonYellow50, R.color.photonOrange50,
|
R.color.photonBlue40, R.color.photonGreen50, R.color.photonYellow50, R.color.photonOrange50,
|
||||||
R.color.photonPurple50, R.color.photonInk70)
|
R.color.photonPurple50, R.color.photonInk70)
|
||||||
const val LAYOUT_ID = R.layout.session_item
|
const val LAYOUT_ID = R.layout.session_item
|
||||||
|
|
|
@ -14,19 +14,21 @@ import org.mozilla.fenix.mvi.UIComponent
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
data class ArchivedSession(
|
data class ArchivedSession(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val bundle: SessionBundle,
|
val bundle: SessionBundle,
|
||||||
private val savedAt: Long,
|
private val savedAt: Long,
|
||||||
private val _urls: List<String>) {
|
private val _urls: List<String>
|
||||||
|
) {
|
||||||
val formattedSavedAt by lazy {
|
val formattedSavedAt by lazy {
|
||||||
val isSameDay: (Calendar, Calendar) -> Boolean = { a, b ->
|
val isSameDay: (Calendar, Calendar) -> Boolean = { a, b ->
|
||||||
a.get(Calendar.ERA) == b.get(Calendar.ERA) &&
|
a.get(Calendar.ERA) == b.get(Calendar.ERA) &&
|
||||||
a.get(Calendar.YEAR) == b.get(Calendar.YEAR) &&
|
a.get(Calendar.YEAR) == b.get(Calendar.YEAR) &&
|
||||||
a.get(Calendar.DAY_OF_YEAR) == b.get(Calendar.DAY_OF_YEAR)
|
a.get(Calendar.DAY_OF_YEAR) == b.get(Calendar.DAY_OF_YEAR)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val parse: (Date) -> String = { date ->
|
val parse: (Date) -> String = { date ->
|
||||||
|
@ -34,11 +36,10 @@ data class ArchivedSession(
|
||||||
val today = Calendar.getInstance()
|
val today = Calendar.getInstance()
|
||||||
val yesterday = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -1) }
|
val yesterday = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -1) }
|
||||||
|
|
||||||
val time = TIME_FORMATTER.format(date)
|
val time = timeFormatter.format(date)
|
||||||
val month = MONTH_FORMATTER.format(date)
|
val month = monthFormatter.format(date)
|
||||||
val day = DAY_FORMATTER.format(date)
|
val day = dayFormatter.format(date)
|
||||||
val dayOfWeek = DAY_OF_WEEK_FORMATTER.format(date)
|
val dayOfWeek = dayOfWeekFormatter.format(date)
|
||||||
|
|
||||||
|
|
||||||
when {
|
when {
|
||||||
isSameDay(dateCal, today) -> "Today @ $time"
|
isSameDay(dateCal, today) -> "Today @ $time"
|
||||||
|
@ -51,6 +52,9 @@ data class ArchivedSession(
|
||||||
}
|
}
|
||||||
|
|
||||||
val titles by lazy {
|
val titles by lazy {
|
||||||
|
// Until we resolve (https://github.com/mozilla-mobile/fenix/issues/532) we
|
||||||
|
// just want to grab the host from the URL
|
||||||
|
@SuppressWarnings("TooGenericExceptionCaught")
|
||||||
val urlFormatter: (String) -> String = { url ->
|
val urlFormatter: (String) -> String = { url ->
|
||||||
try {
|
try {
|
||||||
URL(url).host
|
URL(url).host
|
||||||
|
@ -69,10 +73,10 @@ data class ArchivedSession(
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val NUMBER_OF_URLS_TO_DISPLAY = 5
|
private const val NUMBER_OF_URLS_TO_DISPLAY = 5
|
||||||
|
|
||||||
private val TIME_FORMATTER = SimpleDateFormat("h:mm a", Locale.US)
|
private val timeFormatter = SimpleDateFormat("h:mm a", Locale.US)
|
||||||
private val MONTH_FORMATTER = SimpleDateFormat("M", Locale.US)
|
private val monthFormatter = SimpleDateFormat("M", Locale.US)
|
||||||
private val DAY_FORMATTER = SimpleDateFormat("d", Locale.US)
|
private val dayFormatter = SimpleDateFormat("d", Locale.US)
|
||||||
private val DAY_OF_WEEK_FORMATTER = SimpleDateFormat("EEEE", Locale.US)
|
private val dayOfWeekFormatter = SimpleDateFormat("EEEE", Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +92,7 @@ class SessionsComponent(
|
||||||
|
|
||||||
override val reducer: (SessionsState, SessionsChange) -> SessionsState = { state, change ->
|
override val reducer: (SessionsState, SessionsChange) -> SessionsState = { state, change ->
|
||||||
when (change) {
|
when (change) {
|
||||||
is SessionsChange.Changed -> state.copy(archivedSessions = change.archivedSessions) // copy state with changes here
|
is SessionsChange.Changed -> state.copy(archivedSessions = change.archivedSessions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +109,7 @@ data class SessionsState(val archivedSessions: List<ArchivedSession>) : ViewStat
|
||||||
|
|
||||||
sealed class SessionsAction : Action {
|
sealed class SessionsAction : Action {
|
||||||
data class Select(val archivedSession: ArchivedSession) : SessionsAction()
|
data class Select(val archivedSession: ArchivedSession) : SessionsAction()
|
||||||
|
data class Delete(val archivedSession: ArchivedSession) : SessionsAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SessionsChange : Change {
|
sealed class SessionsChange : Change {
|
||||||
|
|
|
@ -163,6 +163,14 @@ people before profit. Our mission: keep the Internet open and accessible to all.
|
||||||
<!-- Content description (not visible, for screen readers etc.): Title icon for current session menu -->
|
<!-- Content description (not visible, for screen readers etc.): Title icon for current session menu -->
|
||||||
<string name="current_session_image">Current session image</string>
|
<string name="current_session_image">Current session image</string>
|
||||||
|
|
||||||
|
<!-- Text for the button to delete a single session -->
|
||||||
|
<string name="session_item_delete">Delete</string>
|
||||||
|
|
||||||
|
<!-- Text to tell the user how many more tabs this session has.
|
||||||
|
The first parameter is how many extra tabs the session has. -->
|
||||||
|
<string name="session_items_more">%1$d sites…</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Text for the button to clear all history -->
|
<!-- Text for the button to clear all history -->
|
||||||
<string name="history_delete_all">Delete History</string>
|
<string name="history_delete_all">Delete History</string>
|
||||||
<!-- Text for the button to delete a single history item -->
|
<!-- Text for the button to delete a single history item -->
|
||||||
|
|
Loading…
Reference in New Issue