diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index ffae809ac..2ddd5bc37 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -745,21 +745,24 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { } private fun showSaveToCollection() { + val context = context ?: return getSessionById()?.let { - val tabs = Tab(it.id, it.url, it.url.urlToTrimmedHost(), it.title) - val viewModel = activity?.run { - ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java) - } - viewModel?.tabs = listOf(tabs) - val selectedSet = mutableSetOf(tabs) - viewModel?.selectedTabs = selectedSet - viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed() - viewModel?.saveCollectionStep = - viewModel?.tabCollections?.getStepForCollectionsSize() ?: SaveCollectionStep.SelectCollection - viewModel?.snackbarAnchorView = nestedScrollQuickAction - view?.let { - val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment() - nav(R.id.browserFragment, directions) + launch(Dispatchers.Main) { + val tabs = Tab(it.id, it.url, it.url.urlToTrimmedHost(context), it.title) + val viewModel = activity?.run { + ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java) + } + viewModel?.tabs = listOf(tabs) + val selectedSet = mutableSetOf(tabs) + viewModel?.selectedTabs = selectedSet + viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed() + viewModel?.saveCollectionStep = + viewModel?.tabCollections?.getStepForCollectionsSize() ?: SaveCollectionStep.SelectCollection + viewModel?.snackbarAnchorView = nestedScrollQuickAction + view?.let { + val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment() + nav(R.id.browserFragment, directions) + } } } } diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationUIView.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationUIView.kt index fa9434dec..d2fa8649a 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationUIView.kt @@ -22,6 +22,10 @@ import io.reactivex.Observer import io.reactivex.functions.Consumer import kotlinx.android.synthetic.main.component_collection_creation.* import kotlinx.android.synthetic.main.component_collection_creation.view.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.showKeyboard import org.mozilla.fenix.R @@ -30,6 +34,7 @@ import org.mozilla.fenix.ext.urlToTrimmedHost import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.mvi.UIView +import kotlin.coroutines.CoroutineContext class CollectionCreationUIView( container: ViewGroup, @@ -39,7 +44,11 @@ class CollectionCreationUIView( container, actionEmitter, changesObservable -) { +), CoroutineScope { + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + override val view = LayoutInflater.from(container.context) .inflate(R.layout.component_collection_creation, container, true) @@ -258,16 +267,18 @@ class CollectionCreationUIView( is SaveCollectionStep.RenameCollection -> { view.tab_list.isClickable = false - it.selectedTabCollection?.let { tabCollection -> - tabCollection.tabs.map { tab -> - Tab( - tab.id.toString(), - tab.url, - tab.url.urlToTrimmedHost(), - tab.title - ) - }.let { tabs -> - collectionCreationTabListAdapter.updateData(tabs, tabs.toSet(), true) + launch(Dispatchers.Main) { + it.selectedTabCollection?.let { tabCollection -> + tabCollection.tabs.map { tab -> + Tab( + tab.id.toString(), + tab.url, + tab.url.urlToTrimmedHost(view.context), + tab.title + ) + }.let { tabs -> + collectionCreationTabListAdapter.updateData(tabs, tabs.toSet(), true) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt b/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt index cf4a69404..cab965612 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt @@ -16,8 +16,9 @@ import kotlinx.android.synthetic.main.collections_list_item.view.collection_icon import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.mozilla.fenix.R -import org.mozilla.fenix.ext.urlToTrimmedHost +import org.mozilla.fenix.components.description import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.TabCollection import kotlin.coroutines.CoroutineContext @@ -81,36 +82,23 @@ class CollectionViewHolder( fun bind(collection: TabCollection) { this.collection = collection - view.collection_item.text = collection.title + launch(Dispatchers.Main) { + view.collection_item.text = collection.title + view.collection_description.text = collection.description(view.context) - val hostNameList = collection.tabs.map { it.url.urlToTrimmedHost().capitalize() } - - var tabsDisplayed = 0 - val tabTitlesList = hostNameList.joinToString(", ") { - if (it.length > maxTitleLength) { - it.substring( - 0, - maxTitleLength - ) + "..." - } else { - tabsDisplayed += 1 - it - } + view.collection_icon.setColorFilter( + ContextCompat.getColor( + view.context, + getIconColor(collection.id) + ), + android.graphics.PorterDuff.Mode.SRC_IN + ) } - view.collection_description.text = tabTitlesList - - view.collection_icon.setColorFilter( - ContextCompat.getColor( - view.context, - getIconColor(collection.id) - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) } @Suppress("ComplexMethod", "MagicNumber") private fun getIconColor(id: Long): Int { - return when ((id % 4).toInt()) { + return when ((id % 5).toInt()) { 0 -> R.color.collection_icon_color_violet 1 -> R.color.collection_icon_color_blue 2 -> R.color.collection_icon_color_pink diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 084b25b27..3bde60986 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components import android.content.Context +import mozilla.components.lib.publicsuffixlist.PublicSuffixList import org.mozilla.fenix.test.Mockable /** @@ -21,4 +22,5 @@ class Components(private val context: Context) { val useCases by lazy { UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager) } val utils by lazy { Utilities(context, core.sessionManager, useCases.sessionUseCases, useCases.searchUseCases) } val analytics by lazy { Analytics(context) } + val publicSuffixList by lazy { PublicSuffixList(context) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt index 74bdfd8a8..2a4b72c2e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -14,6 +14,8 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollectionStorage import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.ObserverRegistry +import org.mozilla.fenix.ext.urlToTrimmedHost +import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.test.Mockable @Mockable @@ -88,3 +90,18 @@ class TabCollectionStorage( notifyObservers { onCollectionRenamed(tabCollection, title) } } } + +suspend fun TabCollection.description(context: Context): String { + return this.tabs + .map { it.url.urlToTrimmedHost(context).capitalize() } + .map { + if (it.length > CollectionViewHolder.maxTitleLength) { + it.substring( + 0, + CollectionViewHolder.maxTitleLength + ) + "…" + } else { + it + } + }.joinToString(", ") +} diff --git a/app/src/main/java/org/mozilla/fenix/ext/String.kt b/app/src/main/java/org/mozilla/fenix/ext/String.kt index c43ea5dfd..c2f57a6ec 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/String.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/String.kt @@ -6,8 +6,11 @@ package org.mozilla.fenix.ext +import android.content.Context import java.net.MalformedURLException import java.net.URL +import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes +import mozilla.components.support.ktx.kotlin.toUri /** * Replaces the keys with the values with the map provided. @@ -28,21 +31,14 @@ fun String?.getHostFromUrl(): String? = try { null } -fun String?.urlToTrimmedHost(): String { +/** + * Trim a host's prefix and suffix + */ +suspend fun String.urlToTrimmedHost(context: Context): String { return try { - val url = URL(this) - val firstIndex = url.host.indexOfFirst { it == '.' } + 1 - val lastIndex = url.host.indexOfLast { it == '.' } - - // Trim all but the title of the website from the hostname. 'www.mozilla.org' becomes 'mozilla' - when { - firstIndex - 1 == lastIndex -> url.host.substring(0, lastIndex) - firstIndex < lastIndex -> url.host.substring(firstIndex, lastIndex) - else -> url.host - } + val host = this.toUri().hostWithoutCommonPrefixes ?: return this + context.components.publicSuffixList.stripPublicSuffix(host).await() } catch (e: MalformedURLException) { - this.getHostFromUrl() ?: "" - } catch (e: StringIndexOutOfBoundsException) { - this.getHostFromUrl() ?: "" + this } } diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index d49a9a43e..1cada79cf 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -134,7 +134,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { ) { SessionControlViewModel( SessionControlState( - getListOfTabs(requireComponents.core.sessionManager), + listOf(), setOf(), requireComponents.core.tabCollectionStorage.cachedTabCollections, mode @@ -280,13 +280,15 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } } - getManagedEmitter().onNext( - SessionControlChange.Change( - tabs = getListOfTabs(sessionManager = requireComponents.core.sessionManager), - mode = currentMode(), - collections = requireComponents.core.tabCollectionStorage.cachedTabCollections + launch(Dispatchers.Main) { + getManagedEmitter().onNext( + SessionControlChange.Change( + tabs = getListOfTabs(sessionManager = requireComponents.core.sessionManager), + mode = currentMode(), + collections = requireComponents.core.tabCollectionStorage.cachedTabCollections + ) ) - ) + } requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this) sessionObserver.onStart() @@ -560,82 +562,84 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { val useCases = context?.components?.useCases?.tabsUseCases ?: return getManagedEmitter().onNext(SessionControlChange.TabsChange(listOf())) - deleteAllSessionsJob = { + + val deleteOperation: (suspend () -> Unit) = { currentFilteredSessions.forEach { useCases.removeTab.invoke(it) } } + deleteAllSessionsJob = deleteOperation + allowUndo( view!!, getString(R.string.snackbar_tabs_deleted), getString(R.string.snackbar_deleted_undo), { deleteAllSessionsJob = null emitSessionChanges() - } - ) { - deleteAllSessionsJob = { - currentFilteredSessions.forEach { - useCases.removeTab.invoke(it) - } - } - } + }, + operation = deleteOperation + ) } private fun removeTabWithUndo(sessionId: String) { - val sessionManager = requireComponents.core.sessionManager + launch(Dispatchers.Main) { + val sessionManager = requireComponents.core.sessionManager - // Update the UI with the tab removed, but don't remove it from storage yet - getManagedEmitter().onNext( - SessionControlChange.TabsChange( - sessionManager.sessions - .filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private } - .filter { it.id != sessionId } - .map { - val selected = - it == sessionManager.selectedSession - Tab( - it.id, - it.url, - it.url.urlToTrimmedHost(), - it.title, - selected, - it.thumbnail - ) - } + // Update the UI with the tab removed, but don't remove it from storage yet + getManagedEmitter().onNext( + + SessionControlChange.TabsChange( + sessionManager.sessions + .filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private } + .filter { it.id != sessionId } + .map { + val selected = it == sessionManager.selectedSession + Tab( + it.id, + it.url, + it.url.urlToTrimmedHost(context!!), + it.title, + selected, + it.thumbnail + ) + } + ) ) - ) - deleteSessionJob = { - sessionManager.findSessionById(sessionId) - ?.let { session -> - sessionManager.remove(session) - } - } - - allowUndo( - view!!, getString(R.string.snackbar_tab_deleted), - getString(R.string.snackbar_deleted_undo), { - deleteSessionJob = null - emitSessionChanges() + val deleteOperation: (suspend () -> Unit) = { + sessionManager.findSessionById(sessionId) + ?.let { session -> + sessionManager.remove(session) + } } - ) { - sessionManager.findSessionById(sessionId) - ?.let { session -> - sessionManager.remove(session) - } + + deleteSessionJob = deleteOperation + + allowUndo( + view!!, getString(R.string.snackbar_tab_deleted), + getString(R.string.snackbar_deleted_undo), { + deleteSessionJob = null + emitSessionChanges() + }, + operation = deleteOperation + ) } } private fun emitSessionChanges() { val sessionManager = context?.components?.core?.sessionManager ?: return - getManagedEmitter().onNext( - SessionControlChange.TabsChange( - getListOfTabs(sessionManager) + + launch(Dispatchers.Main) { + getManagedEmitter().onNext( + SessionControlChange.TabsChange( + getListOfTabs(sessionManager) + ) ) - ) + } } - private fun getListOfTabs(sessionManager: SessionManager): List { + private suspend fun getListOfTabs(sessionManager: SessionManager): List { + val context = context ?: return listOf() return sessionManager.sessions .filter { (activity as HomeActivity).browsingModeManager.isPrivate == it.private } .map { @@ -643,7 +647,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { Tab( it.id, it.url, - it.url.urlToTrimmedHost(), + it.url.urlToTrimmedHost(context), it.title, selected, it.thumbnail @@ -663,24 +667,29 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { ) { if (findNavController(this).currentDestination?.id == R.id.createCollectionFragment) return - val tabs = requireComponents.core.sessionManager.sessions.filter { !it.private } - .map { Tab(it.id, it.url, it.url.urlToTrimmedHost(), it.title) } + val context = context?.let { it } ?: return - val viewModel = activity?.run { - ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java) - } - viewModel?.tabs = tabs - val selectedTabs = tabs.find { tab -> tab.sessionId == selectedTabId } ?: if (tabs.size == 1) tabs[0] else null - val selectedSet = if (selectedTabs == null) mutableSetOf() else mutableSetOf(selectedTabs) - viewModel?.selectedTabs = selectedSet - viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed() - viewModel?.selectedTabCollection = selectedTabCollection - viewModel?.saveCollectionStep = - step ?: viewModel?.getStepForTabsAndCollectionSize() ?: SaveCollectionStep.SelectTabs + launch(Dispatchers.Main) { + val tabs = requireComponents.core.sessionManager.sessions.filter { !it.private } + .map { Tab(it.id, it.url, it.url.urlToTrimmedHost(context), it.title) } - view?.let { - val directions = HomeFragmentDirections.actionHomeFragmentToCreateCollectionFragment() - nav(R.id.homeFragment, directions) + val viewModel = activity?.run { + ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java) + } + viewModel?.tabs = tabs + val selectedTabs = + tabs.find { tab -> tab.sessionId == selectedTabId } ?: if (tabs.size == 1) tabs[0] else null + val selectedSet = if (selectedTabs == null) mutableSetOf() else mutableSetOf(selectedTabs) + viewModel?.selectedTabs = selectedSet + viewModel?.tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections.reversed() + viewModel?.selectedTabCollection = selectedTabCollection + viewModel?.saveCollectionStep = + step ?: viewModel?.getStepForTabsAndCollectionSize() ?: SaveCollectionStep.SelectTabs + + view?.let { + val directions = HomeFragmentDirections.actionHomeFragmentToCreateCollectionFragment() + nav(R.id.homeFragment, directions) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt index 376433a61..1dda88ecb 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt @@ -16,13 +16,14 @@ import kotlinx.android.synthetic.main.collection_home_list_row.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import org.mozilla.fenix.R import org.mozilla.fenix.ThemeManager +import org.mozilla.fenix.components.description import org.mozilla.fenix.ext.increaseTapArea -import org.mozilla.fenix.ext.urlToTrimmedHost import org.mozilla.fenix.home.sessioncontrol.CollectionAction import org.mozilla.fenix.home.sessioncontrol.SessionControlAction import org.mozilla.fenix.home.sessioncontrol.TabCollection @@ -92,47 +93,33 @@ class CollectionViewHolder( } private fun updateCollectionUI() { - view.collection_title.text = collection.title + launch(Dispatchers.Main) { + view.collection_title.text = collection.title + view.collection_description.text = collection.description(view.context) - val hostNameList = collection.tabs.map { it.url.urlToTrimmedHost().capitalize() } + if (expanded) { + (view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0 + collection_title.setPadding(0, 0, 0, EXPANDED_PADDING) + view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_top_corners) + view.collection_description.visibility = View.GONE - var tabsDisplayed = 0 - val tabTitlesList = hostNameList.joinToString(", ") { - if (it.length > maxTitleLength) { - it.substring( - 0, - maxTitleLength - ) + "..." + view.chevron.setBackgroundResource(R.drawable.ic_chevron_up) } else { - tabsDisplayed += 1 - it + (view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = COLLAPSED_MARGIN + view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_all_corners) + view.collection_description.visibility = View.VISIBLE + + view.chevron.setBackgroundResource(R.drawable.ic_chevron_down) } + + view.collection_icon.setColorFilter( + ContextCompat.getColor( + view.context, + getIconColor(collection.id) + ), + android.graphics.PorterDuff.Mode.SRC_IN + ) } - - view.collection_description.text = tabTitlesList - - if (expanded) { - (view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0 - collection_title.setPadding(0, 0, 0, EXPANDED_PADDING) - view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_top_corners) - view.collection_description.visibility = View.GONE - - view.chevron.setBackgroundResource(R.drawable.ic_chevron_up) - } else { - (view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = COLLAPSED_MARGIN - view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_all_corners) - view.collection_description.visibility = View.VISIBLE - - view.chevron.setBackgroundResource(R.drawable.ic_chevron_down) - } - - view.collection_icon.setColorFilter( - ContextCompat.getColor( - view.context, - getIconColor(collection.id) - ), - android.graphics.PorterDuff.Mode.SRC_IN - ) } private fun handleExpansion(isExpanded: Boolean) { @@ -145,7 +132,7 @@ class CollectionViewHolder( @Suppress("ComplexMethod", "MagicNumber") private fun getIconColor(id: Long): Int { - val sessionColorIndex = (id % 4).toInt() + val sessionColorIndex = (id % 5).toInt() return when (sessionColorIndex) { 0 -> R.color.collection_icon_color_violet 1 -> R.color.collection_icon_color_blue diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TabInCollectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TabInCollectionViewHolder.kt index 51f18fa9a..0a955432e 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TabInCollectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TabInCollectionViewHolder.kt @@ -79,7 +79,9 @@ class TabInCollectionViewHolder( } private fun updateTabUI() { - collection_tab_hostname.text = tab.url.urlToTrimmedHost() + launch(Dispatchers.Main) { + collection_tab_hostname.text = tab.url.urlToTrimmedHost(view.context) + } collection_tab_title.text = tab.title launch(Dispatchers.IO) { val bitmap = collection_tab_icon.context.components.core.icons diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index c431ee3f4..a85ecb14a 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -24,6 +24,7 @@ import androidx.navigation.NavController import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_bookmark.view.* import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job @@ -262,17 +263,22 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve } } is BookmarkAction.Delete -> { - getManagedEmitter() - .onNext(BookmarkChange.Change(currentRoot - it.item.guid)) + context?.let { context -> + getManagedEmitter() + .onNext(BookmarkChange.Change(currentRoot - it.item.guid)) - allowUndo( - view!!, - getString(R.string.bookmark_deletion_snackbar_message, it.item.url.urlToTrimmedHost()), - getString(R.string.bookmark_undo_deletion), { refreshBookmarks() } - ) { - bookmarkStorage()?.deleteNode(it.item.guid) - metrics()?.track(Event.RemoveBookmark) - refreshBookmarks() + launch(Dispatchers.Main) { + allowUndo( + view!!, + getString(R.string.bookmark_deletion_snackbar_message, + it.item.url?.urlToTrimmedHost(context)), + getString(R.string.bookmark_undo_deletion), { refreshBookmarks() } + ) { + bookmarkStorage()?.deleteNode(it.item.guid) + metrics()?.track(Event.RemoveBookmark) + refreshBookmarks() + } + } } } is BookmarkAction.SwitchMode -> {