* For #2205: Adds TabCollectionStorage * For #1578: Adds delete to TabCollectionmaster
parent
892a4b7bf4
commit
72d29c2a43
|
@ -309,6 +309,7 @@ dependencies {
|
|||
implementation Deps.mozilla_feature_session_bundling
|
||||
implementation Deps.mozilla_feature_site_permissions
|
||||
implementation Deps.mozilla_feature_readerview
|
||||
implementation Deps.mozilla_feature_tab_collections
|
||||
|
||||
implementation Deps.mozilla_service_firefox_accounts
|
||||
implementation Deps.mozilla_service_fretboard
|
||||
|
|
|
@ -687,7 +687,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope {
|
|||
launch {
|
||||
val host = session.url.toUri()?.host
|
||||
val sitePermissions: SitePermissions? = host?.let {
|
||||
val storage = requireContext().components.storage
|
||||
val storage = requireContext().components.core.permissionStorage
|
||||
storage.findSitePermissionsBy(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,20 +14,26 @@ import androidx.fragment.app.DialogFragment
|
|||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_create_collection.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mozilla.fenix.FenixViewModelProvider
|
||||
import mozilla.components.browser.session.Session
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
import java.util.Random
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class CreateCollectionFragment : DialogFragment() {
|
||||
// Temporary callback. In the future we will just directly add the collection to the core session manager.
|
||||
var onCollectionSaved: ((TabCollection) -> Unit)? = null
|
||||
class CreateCollectionFragment : DialogFragment(), CoroutineScope {
|
||||
private lateinit var collectionCreationComponent: CollectionCreationComponent
|
||||
private lateinit var job: Job
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + job
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -40,6 +46,7 @@ class CreateCollectionFragment : DialogFragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
job = Job()
|
||||
val view = inflater.inflate(R.layout.fragment_create_collection, container, false)
|
||||
|
||||
val viewModel = activity?.run {
|
||||
|
@ -76,6 +83,12 @@ class CreateCollectionFragment : DialogFragment() {
|
|||
subscribeToActions()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun subscribeToActions() {
|
||||
getAutoDisposeObservable<CollectionCreationAction>().subscribe {
|
||||
when (it) {
|
||||
|
@ -97,8 +110,17 @@ class CreateCollectionFragment : DialogFragment() {
|
|||
is CollectionCreationAction.SaveCollectionName -> {
|
||||
showSavedSnackbar(it.tabs.size)
|
||||
dismiss()
|
||||
val newCollection = TabCollection(Random().nextInt(), it.name, it.tabs.toMutableList())
|
||||
onCollectionSaved?.invoke(newCollection)
|
||||
|
||||
val sessionBundle = mutableListOf<Session>()
|
||||
it.tabs.forEach {
|
||||
requireComponents.core.sessionManager.findSessionById(it.sessionId)?.let { session ->
|
||||
sessionBundle.add(session)
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
requireComponents.core.tabCollectionStorage.createCollection(it.name, sessionBundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,5 +19,4 @@ class Components(private val context: Context) {
|
|||
val useCases by lazy { UseCases(context, core.sessionManager, search.searchEngineManager) }
|
||||
val utils by lazy { Utilities(context, core.sessionManager, useCases.sessionUseCases, useCases.searchUseCases) }
|
||||
val analytics by lazy { Analytics(context) }
|
||||
val storage by lazy { Storage(context) }
|
||||
}
|
||||
|
|
|
@ -127,8 +127,11 @@ class Core(private val context: Context) {
|
|||
*/
|
||||
val historyStorage by lazy { PlacesHistoryStorage(context) }
|
||||
|
||||
val bookmarksStorage
|
||||
by lazy { PlacesBookmarksStorage(context) }
|
||||
val bookmarksStorage by lazy { PlacesBookmarksStorage(context) }
|
||||
|
||||
val tabCollectionStorage by lazy { TabCollectionStorage(context, sessionManager) }
|
||||
|
||||
val permissionStorage by lazy { PermissionStorage(context) }
|
||||
|
||||
/**
|
||||
* Constructs a [TrackingProtectionPolicy] based on current preferences.
|
||||
|
|
|
@ -12,7 +12,7 @@ import mozilla.components.feature.sitepermissions.SitePermissionsStorage
|
|||
import org.mozilla.fenix.test.Mockable
|
||||
|
||||
@Mockable
|
||||
class Storage(private val context: Context) {
|
||||
class PermissionStorage(private val context: Context) {
|
||||
|
||||
private val permissionsStorage by lazy {
|
||||
SitePermissionsStorage(context)
|
|
@ -0,0 +1,55 @@
|
|||
/* 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.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.DataSource
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.feature.tab.collections.Tab
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import mozilla.components.feature.tab.collections.TabCollectionStorage
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
|
||||
@Mockable
|
||||
class TabCollectionStorage(private val context: Context, private val sessionManager: SessionManager) {
|
||||
|
||||
private val collectionStorage by lazy {
|
||||
TabCollectionStorage(context, sessionManager)
|
||||
}
|
||||
|
||||
fun createCollection(title: String, sessions: List<Session>) {
|
||||
collectionStorage.createCollection(title, sessions)
|
||||
}
|
||||
|
||||
fun addTabsToCollection(tabCollection: TabCollection, sessions: List<Session>) {
|
||||
collectionStorage.addTabsToCollection(tabCollection, sessions)
|
||||
}
|
||||
|
||||
fun getCollections(limit: Int = 20): LiveData<List<TabCollection>> {
|
||||
return collectionStorage.getCollections(limit)
|
||||
}
|
||||
|
||||
fun getCollectionsPaged(): DataSource.Factory<Int, TabCollection> {
|
||||
return collectionStorage.getCollectionsPaged()
|
||||
}
|
||||
|
||||
fun removeCollection(tabCollection: TabCollection) {
|
||||
collectionStorage.removeCollection(tabCollection)
|
||||
}
|
||||
|
||||
fun removeTabFromCollection(tabCollection: TabCollection, tab: Tab) {
|
||||
if (tabCollection.tabs.size == 1) {
|
||||
removeCollection(tabCollection)
|
||||
} else {
|
||||
collectionStorage.removeTabFromCollection(tabCollection, tab)
|
||||
}
|
||||
}
|
||||
|
||||
fun renameCollection(tabCollection: TabCollection, title: String) {
|
||||
collectionStorage.renameCollection(tabCollection, title)
|
||||
}
|
||||
}
|
|
@ -164,7 +164,7 @@ class DefaultToolbarMenu(
|
|||
|
||||
BrowserMenuImageText(
|
||||
context.getString(R.string.browser_menu_save_to_collection),
|
||||
R.drawable.ic_archive,
|
||||
R.drawable.ic_tab_collection,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.primaryText, context)
|
||||
) {
|
||||
onItemTapped.invoke(ToolbarMenu.Item.SaveToCollection)
|
||||
|
|
|
@ -13,6 +13,7 @@ import android.view.ViewGroup
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.Navigation
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
|
@ -68,6 +69,8 @@ import kotlin.math.roundToInt
|
|||
class HomeFragment : Fragment(), CoroutineScope {
|
||||
private val bus = ActionBusFactory.get(this)
|
||||
private var sessionObserver: SessionManager.Observer? = null
|
||||
private var tabCollectionObserver: Observer<List<TabCollection>>? = null
|
||||
|
||||
private var homeMenu: HomeMenu? = null
|
||||
|
||||
var deleteSessionJob: (suspend () -> Unit)? = null
|
||||
|
@ -79,9 +82,6 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + job
|
||||
|
||||
// TODO Remove this stub when we have the a-c version!
|
||||
var storedCollections = mutableListOf<TabCollection>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -99,7 +99,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
this,
|
||||
SessionControlViewModel::class.java
|
||||
) {
|
||||
SessionControlViewModel(SessionControlState(listOf(), listOf(), mode))
|
||||
SessionControlViewModel(SessionControlState(listOf(), setOf(), listOf(), mode))
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -190,13 +190,6 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
) ?: arrayListOf()).toList()
|
||||
)
|
||||
)
|
||||
getManagedEmitter<SessionControlChange>().onNext(
|
||||
SessionControlChange.CollectionsChange(
|
||||
(savedInstanceState.getParcelableArrayList<TabCollection>(
|
||||
KEY_COLLECTIONS
|
||||
) ?: arrayListOf()).toList()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,12 +223,17 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
|
||||
emitSessionChanges()
|
||||
sessionObserver = subscribeToSessions()
|
||||
tabCollectionObserver = subscribeToTabCollections()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
sessionObserver?.let {
|
||||
requireComponents.core.sessionManager.unregister(it)
|
||||
}
|
||||
tabCollectionObserver?.let {
|
||||
requireComponents.core.tabCollectionStorage.getCollections().removeObserver(it)
|
||||
}
|
||||
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
@ -317,13 +315,17 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
private fun handleCollectionAction(action: CollectionAction) {
|
||||
when (action) {
|
||||
is CollectionAction.Expand -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.apply { expanded = true }
|
||||
getManagedEmitter<SessionControlChange>()
|
||||
.onNext(SessionControlChange.ExpansionChange(action.collection, true))
|
||||
}
|
||||
is CollectionAction.Collapse -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.apply { expanded = false }
|
||||
getManagedEmitter<SessionControlChange>()
|
||||
.onNext(SessionControlChange.ExpansionChange(action.collection, false))
|
||||
}
|
||||
is CollectionAction.Delete -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.let { storedCollections.remove(it) }
|
||||
launch(Dispatchers.IO) {
|
||||
requireComponents.core.tabCollectionStorage.removeCollection(action.collection)
|
||||
}
|
||||
}
|
||||
is CollectionAction.AddTab -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1575")
|
||||
|
@ -338,17 +340,11 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1585")
|
||||
}
|
||||
is CollectionAction.RemoveTab -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1578")
|
||||
launch(Dispatchers.IO) {
|
||||
requireComponents.core.tabCollectionStorage.removeTabFromCollection(action.collection, action.tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitCollectionChange()
|
||||
}
|
||||
|
||||
private fun emitCollectionChange() {
|
||||
storedCollections.map { it.copy() }.let {
|
||||
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.CollectionsChange(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -396,6 +392,14 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
return getString(resourceId)
|
||||
}
|
||||
|
||||
private fun subscribeToTabCollections(): Observer<List<TabCollection>> {
|
||||
val observer = Observer<List<TabCollection>> {
|
||||
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.CollectionsChange(it))
|
||||
}
|
||||
requireComponents.core.tabCollectionStorage.getCollections().observe(this, observer)
|
||||
return observer
|
||||
}
|
||||
|
||||
private fun subscribeToSessions(): SessionManager.Observer {
|
||||
val observer = object : SessionManager.Observer {
|
||||
override fun onSessionAdded(session: Session) {
|
||||
|
@ -520,6 +524,5 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
companion object {
|
||||
private const val toolbarPaddingDp = 12f
|
||||
private const val KEY_TABS = "tabs"
|
||||
private const val KEY_COLLECTIONS = "collections"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class SessionBottomSheetFragment : BottomSheetDialogFragment(), LayoutContainer
|
|||
view.current_session_card_title.text = getCardTitle()
|
||||
view.current_session_card_tab_list.text = getTabTitles()
|
||||
view.archive_session_button.apply {
|
||||
val drawable = ContextCompat.getDrawable(context!!, R.drawable.ic_archive)
|
||||
val drawable = ContextCompat.getDrawable(context!!, R.drawable.ic_tab_collection)
|
||||
drawable?.setColorFilter(
|
||||
ContextCompat.getColor(
|
||||
context!!,
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingPr
|
|||
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
|
||||
import mozilla.components.feature.tab.collections.Tab as ComponentTab
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
sealed class AdapterItem {
|
||||
|
@ -42,7 +43,11 @@ sealed class AdapterItem {
|
|||
object CollectionHeader : AdapterItem()
|
||||
object NoCollectionMessage : AdapterItem()
|
||||
data class CollectionItem(val collection: TabCollection) : AdapterItem()
|
||||
data class TabInCollectionItem(val collection: TabCollection, val tab: Tab, val isLastTab: Boolean) : AdapterItem()
|
||||
data class TabInCollectionItem(
|
||||
val collection: TabCollection,
|
||||
val tab: ComponentTab,
|
||||
val isLastTab: Boolean
|
||||
) : AdapterItem()
|
||||
|
||||
object OnboardingHeader : AdapterItem()
|
||||
data class OnboardingSectionHeader(val labelBuilder: (Context) -> String) : AdapterItem()
|
||||
|
@ -82,9 +87,11 @@ class SessionControlAdapter(
|
|||
|
||||
private var items: List<AdapterItem> = listOf()
|
||||
private lateinit var job: Job
|
||||
private lateinit var expandedCollections: Set<Long>
|
||||
|
||||
fun reloadData(items: List<AdapterItem>) {
|
||||
fun reloadData(items: List<AdapterItem>, expandedCollections: Set<Long>) {
|
||||
this.items = items
|
||||
this.expandedCollections = expandedCollections
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
@ -138,9 +145,10 @@ class SessionControlAdapter(
|
|||
is TabViewHolder -> holder.bindSession(
|
||||
(items[position] as AdapterItem.TabItem).tab
|
||||
)
|
||||
is CollectionViewHolder -> holder.bindSession(
|
||||
(items[position] as AdapterItem.CollectionItem).collection
|
||||
)
|
||||
is CollectionViewHolder -> {
|
||||
val collection = (items[position] as AdapterItem.CollectionItem).collection
|
||||
holder.bindSession(collection, expandedCollections.contains(collection.id))
|
||||
}
|
||||
is TabInCollectionViewHolder -> {
|
||||
val item = items[position] as AdapterItem.TabInCollectionItem
|
||||
holder.bindSession(item.collection, item.tab, item.isLastTab)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.mozilla.fenix.home.sessioncontrol
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Parcelable
|
||||
import android.view.ViewGroup
|
||||
|
@ -12,6 +13,10 @@ import io.reactivex.Observer
|
|||
import kotlinx.android.parcel.Parcelize
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.feature.tab.collections.TabCollection as ACTabCollection
|
||||
import mozilla.components.feature.tab.collections.Tab as ComponentTab
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.mvi.Action
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.UIComponent
|
||||
|
@ -49,15 +54,6 @@ data class Tab(
|
|||
val thumbnail: Bitmap? = null
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class TabCollection(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val tabs: MutableList<Tab>,
|
||||
val iconColor: Int = 0,
|
||||
var expanded: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
sealed class Mode {
|
||||
object Normal : Mode()
|
||||
object Private : Mode()
|
||||
|
@ -66,10 +62,13 @@ sealed class Mode {
|
|||
|
||||
data class SessionControlState(
|
||||
val tabs: List<Tab>,
|
||||
val expandedCollections: Set<Long>,
|
||||
val collections: List<TabCollection>,
|
||||
val mode: Mode
|
||||
) : ViewState
|
||||
|
||||
typealias TabCollection = ACTabCollection
|
||||
|
||||
sealed class TabAction : Action {
|
||||
data class SaveTabGroup(val selectedTabSessionId: String?) : TabAction()
|
||||
object Add : TabAction()
|
||||
|
@ -89,7 +88,7 @@ sealed class CollectionAction : Action {
|
|||
data class Rename(val collection: TabCollection) : CollectionAction()
|
||||
data class OpenTabs(val collection: TabCollection) : CollectionAction()
|
||||
data class ShareTabs(val collection: TabCollection) : CollectionAction()
|
||||
data class RemoveTab(val collection: TabCollection, val tab: Tab) : CollectionAction()
|
||||
data class RemoveTab(val collection: TabCollection, val tab: ComponentTab) : CollectionAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingAction : Action {
|
||||
|
@ -118,17 +117,33 @@ sealed class SessionControlChange : Change {
|
|||
data class TabsChange(val tabs: List<Tab>) : SessionControlChange()
|
||||
data class ModeChange(val mode: Mode) : SessionControlChange()
|
||||
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
||||
data class ExpansionChange(val collection: TabCollection, val expand: Boolean) : SessionControlChange()
|
||||
}
|
||||
|
||||
class SessionControlViewModel(
|
||||
initialState: SessionControlState
|
||||
) : UIComponentViewModelBase<SessionControlState, SessionControlChange>(initialState, reducer) {
|
||||
companion object {
|
||||
fun getSessionFromTab(context: Context, tab: Tab): Session? {
|
||||
return context.components.core.sessionManager.findSessionById(tab.sessionId)
|
||||
}
|
||||
|
||||
val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
||||
when (change) {
|
||||
is SessionControlChange.CollectionsChange -> state.copy(collections = change.collections)
|
||||
is SessionControlChange.TabsChange -> state.copy(tabs = change.tabs)
|
||||
is SessionControlChange.ModeChange -> state.copy(mode = change.mode)
|
||||
is SessionControlChange.ExpansionChange -> {
|
||||
val newExpandedCollection = state.expandedCollections.toMutableSet()
|
||||
|
||||
if (change.expand) {
|
||||
newExpandedCollection.add(change.collection.id)
|
||||
} else {
|
||||
newExpandedCollection.remove(change.collection.id)
|
||||
}
|
||||
|
||||
state.copy(expandedCollections = newExpandedCollection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,11 @@ import org.mozilla.fenix.mvi.UIView
|
|||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
|
||||
private fun normalModeAdapterItems(tabs: List<Tab>, collections: List<TabCollection>): List<AdapterItem> {
|
||||
private fun normalModeAdapterItems(
|
||||
tabs: List<Tab>,
|
||||
collections: List<TabCollection>,
|
||||
expandedCollections: Set<Long>
|
||||
): List<AdapterItem> {
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
items.add(AdapterItem.TabHeader(false, tabs.isNotEmpty()))
|
||||
|
||||
|
@ -33,9 +37,9 @@ private fun normalModeAdapterItems(tabs: List<Tab>, collections: List<TabCollect
|
|||
if (collections.isNotEmpty()) {
|
||||
|
||||
// If the collection is expanded, we want to add all of its tabs beneath it in the adapter
|
||||
collections.reversed().map(AdapterItem::CollectionItem).forEach {
|
||||
collections.map(AdapterItem::CollectionItem).forEach {
|
||||
items.add(it)
|
||||
if (it.collection.expanded) {
|
||||
if (it.collection.isExpanded(expandedCollections)) {
|
||||
items.addAll(collectionTabItems(it.collection))
|
||||
}
|
||||
}
|
||||
|
@ -76,14 +80,18 @@ private fun onboardingAdapterItems(): List<AdapterItem> = listOf(
|
|||
)
|
||||
|
||||
private fun SessionControlState.toAdapterList(): List<AdapterItem> = when (mode) {
|
||||
is Mode.Normal -> normalModeAdapterItems(tabs, collections)
|
||||
is Mode.Normal -> normalModeAdapterItems(tabs, collections, expandedCollections)
|
||||
is Mode.Private -> privateModeAdapterItems(tabs)
|
||||
is Mode.Onboarding -> onboardingAdapterItems()
|
||||
}
|
||||
|
||||
private fun collectionTabItems(collection: TabCollection) = collection.tabs.mapIndexed { index, tab ->
|
||||
AdapterItem.TabInCollectionItem(collection, tab, index == collection.tabs.lastIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TabCollection.isExpanded(expandedCollections: Set<Long>): Boolean {
|
||||
return expandedCollections.contains(this.id)
|
||||
}
|
||||
|
||||
class SessionControlUIView(
|
||||
container: ViewGroup,
|
||||
|
@ -101,6 +109,7 @@ class SessionControlUIView(
|
|||
.findViewById(R.id.home_component)
|
||||
|
||||
private val sessionControlAdapter = SessionControlAdapter(actionEmitter)
|
||||
private var expandedCollections = setOf<Long>()
|
||||
|
||||
init {
|
||||
view.apply {
|
||||
|
@ -117,8 +126,8 @@ class SessionControlUIView(
|
|||
}
|
||||
|
||||
override fun updateView() = Consumer<SessionControlState> {
|
||||
sessionControlAdapter.reloadData(it.toAdapterList())
|
||||
|
||||
sessionControlAdapter.reloadData(it.toAdapterList(), it.expandedCollections)
|
||||
expandedCollections = it.expandedCollections
|
||||
// There is a current bug in the combination of MotionLayout~alhpa4 and RecyclerView where it doesn't think
|
||||
// it has to redraw itself. For some reason calling scrollBy forces this to happen every time
|
||||
// https://stackoverflow.com/a/42549611
|
||||
|
|
|
@ -21,6 +21,7 @@ import mozilla.components.browser.menu.BrowserMenuBuilder
|
|||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import org.mozilla.fenix.DefaultThemeManager
|
||||
import org.mozilla.fenix.R
|
||||
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
|
||||
|
@ -40,6 +41,7 @@ class CollectionViewHolder(
|
|||
get() = Dispatchers.IO + job
|
||||
|
||||
private lateinit var collection: TabCollection
|
||||
private var expanded = false
|
||||
private var state = CollectionState.Collapsed
|
||||
private var collectionMenu: CollectionItemMenu
|
||||
|
||||
|
@ -78,8 +80,9 @@ class CollectionViewHolder(
|
|||
)
|
||||
}
|
||||
|
||||
fun bindSession(collection: TabCollection) {
|
||||
fun bindSession(collection: TabCollection, expanded: Boolean) {
|
||||
this.collection = collection
|
||||
this.expanded = expanded
|
||||
updateCollectionUI()
|
||||
}
|
||||
|
||||
|
@ -89,7 +92,7 @@ class CollectionViewHolder(
|
|||
var hostNameList = listOf<String>()
|
||||
|
||||
collection.tabs.forEach {
|
||||
hostNameList += it.hostname.capitalize()
|
||||
hostNameList += it.url.urlToTrimmedHost().capitalize()
|
||||
}
|
||||
|
||||
var tabsDisplayed = 0
|
||||
|
@ -106,7 +109,7 @@ class CollectionViewHolder(
|
|||
|
||||
view.collection_description.text = titleList
|
||||
|
||||
if (collection.expanded) {
|
||||
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)
|
||||
|
|
|
@ -23,12 +23,13 @@ import org.mozilla.fenix.R
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getColorFromAttr
|
||||
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.Tab
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.home.sessioncontrol.onNext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import mozilla.components.feature.tab.collections.Tab as ComponentTab
|
||||
|
||||
class TabInCollectionViewHolder(
|
||||
val view: View,
|
||||
|
@ -42,7 +43,7 @@ class TabInCollectionViewHolder(
|
|||
|
||||
lateinit var collection: TabCollection
|
||||
private set
|
||||
lateinit var tab: Tab
|
||||
lateinit var tab: ComponentTab
|
||||
private set
|
||||
var isLastTab = false
|
||||
|
||||
|
@ -66,7 +67,7 @@ class TabInCollectionViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
fun bindSession(collection: TabCollection, tab: Tab, isLastTab: Boolean) {
|
||||
fun bindSession(collection: TabCollection, tab: ComponentTab, isLastTab: Boolean) {
|
||||
this.collection = collection
|
||||
this.tab = tab
|
||||
this.isLastTab = isLastTab
|
||||
|
@ -74,7 +75,7 @@ class TabInCollectionViewHolder(
|
|||
}
|
||||
|
||||
private fun updateTabUI() {
|
||||
collection_tab_hostname.text = tab.hostname
|
||||
collection_tab_hostname.text = tab.url.urlToTrimmedHost()
|
||||
collection_tab_title.text = tab.title
|
||||
launch(Dispatchers.IO) {
|
||||
val bitmap = collection_tab_icon.context.components.utils.icons
|
||||
|
|
|
@ -47,7 +47,7 @@ import org.mozilla.fenix.ext.allowUndo
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.share
|
||||
import org.mozilla.fenix.ext.urlToHost
|
||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
|
@ -238,10 +238,12 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve
|
|||
is BookmarkAction.Delete -> {
|
||||
val components = context?.applicationContext?.components!!
|
||||
|
||||
getManagedEmitter<BookmarkChange>().onNext(BookmarkChange.Change(currentRoot - it.item.guid))
|
||||
getManagedEmitter<BookmarkChange>()
|
||||
.onNext(BookmarkChange.Change(currentRoot - it.item.guid))
|
||||
|
||||
CoroutineScope(Main).allowUndo(
|
||||
view!!, getString(R.string.bookmark_deletion_snackbar_message, it.item.url.urlToHost()),
|
||||
view!!,
|
||||
getString(R.string.bookmark_deletion_snackbar_message, it.item.url.urlToTrimmedHost()),
|
||||
getString(R.string.bookmark_undo_deletion), { refreshBookmarks(components) }
|
||||
) {
|
||||
components.core.bookmarksStorage.deleteNode(it.item.guid)
|
||||
|
|
|
@ -53,7 +53,8 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat(), Cor
|
|||
super.onResume()
|
||||
launch(IO) {
|
||||
val context = requireContext()
|
||||
sitePermissions = requireNotNull(context.components.storage.findSitePermissionsBy(sitePermissions.origin))
|
||||
sitePermissions =
|
||||
requireNotNull(context.components.core.permissionStorage.findSitePermissionsBy(sitePermissions.origin))
|
||||
launch(Main) {
|
||||
bindCategoryPhoneFeatures()
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat(), Cor
|
|||
|
||||
private fun clearSitePermissions() {
|
||||
launch(IO) {
|
||||
requireContext().components.storage.deleteSitePermissions(sitePermissions)
|
||||
requireContext().components.core.permissionStorage.deleteSitePermissions(sitePermissions)
|
||||
launch(Main) {
|
||||
Navigation.findNavController(requireNotNull(view)).popBackStack()
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class SitePermissionsExceptionsFragment : Fragment(), View.OnClickListener, Coro
|
|||
recyclerView = rootView.findViewById(R.id.exceptions)
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
val sitePermissionsPaged = requireContext().components.storage.getSitePermissionsPaged()
|
||||
val sitePermissionsPaged = requireContext().components.core.permissionStorage.getSitePermissionsPaged()
|
||||
|
||||
val adapter = ExceptionsAdapter(this)
|
||||
val liveData = LivePagedListBuilder(sitePermissionsPaged, MAX_ITEMS_PER_PAGE).build()
|
||||
|
@ -124,7 +124,7 @@ class SitePermissionsExceptionsFragment : Fragment(), View.OnClickListener, Coro
|
|||
|
||||
private fun deleteAllSitePermissions() {
|
||||
launch(IO) {
|
||||
requireContext().components.storage.deleteAllSitePermissions()
|
||||
requireContext().components.core.permissionStorage.deleteAllSitePermissions()
|
||||
launch(Main) {
|
||||
showEmptyListMessage()
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment(), Coroutin
|
|||
PhoneFeature.NOTIFICATION -> sitePermissions.copy(notification = status)
|
||||
}
|
||||
launch(IO) {
|
||||
requireContext().components.storage.updateSitePermissions(updatedSitePermissions)
|
||||
requireContext().components.core.permissionStorage.updateSitePermissions(updatedSitePermissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,10 +60,11 @@ class QuickSettingsComponent(
|
|||
PhoneFeature.MICROPHONE -> microphone = microphone.toggle()
|
||||
PhoneFeature.NOTIFICATION -> notification = notification.toggle()
|
||||
}
|
||||
context.components.storage.addSitePermissionException(origin, location, notification, microphone, camera)
|
||||
context.components.core.permissionStorage
|
||||
.addSitePermissionException(origin, location, notification, microphone, camera)
|
||||
} else {
|
||||
val updatedSitePermissions = sitePermissions.toggle(featurePhone)
|
||||
context.components.storage.updateSitePermissions(updatedSitePermissions)
|
||||
context.components.core.permissionStorage.updateSitePermissions(updatedSitePermissions)
|
||||
updatedSitePermissions
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment(), CoroutineSco
|
|||
launch {
|
||||
val host = session.url.toUri()?.host
|
||||
val sitePermissions: SitePermissions? = host?.let {
|
||||
val storage = requireContext().components.storage
|
||||
val storage = requireContext().components.core.permissionStorage
|
||||
storage.findSitePermissionsBy(it)
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,20 @@
|
|||
<?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/. -->
|
||||
|
||||
<vector android:height="24dp" android:viewportHeight="20"
|
||||
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M18,6L2,6L2,17C2,17.5523 2.4477,18 3,18L17,18C17.5523,18 18,17.5523 18,17L18,6ZM16.7908,4L15.333,2.3401C15.1432,2.1239 14.8694,2 14.5817,2L5.8284,2C5.5632,2 5.3089,2.1054 5.1213,2.2929L3.4142,4L16.7908,4ZM0,17L0,5.8284C0,5.0328 0.3161,4.2697 0.8787,3.7071L3.7071,0.8787C4.2697,0.3161 5.0328,0 5.8284,0L14.5817,0C15.4448,0 16.2662,0.3718 16.8358,1.0203L19.2541,3.7739C19.7349,4.3213 20,5.025 20,5.7536L20,17C20,18.6569 18.6569,20 17,20L3,20C1.3431,20 0,18.6569 0,17Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M14.95,8.5L5.05,8.5C4.7462,8.5 4.5,8.7239 4.5,9C4.5,9.2761 4.7462,9.5 5.05,9.5L14.95,9.5C15.2538,9.5 15.5,9.2761 15.5,9C15.5,8.7239 15.2538,8.5 14.95,8.5Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M14.95,14.5L5.05,14.5C4.7462,14.5 4.5,14.7239 4.5,15C4.5,15.2761 4.7462,15.5 5.05,15.5L14.95,15.5C15.2538,15.5 15.5,15.2761 15.5,15C15.5,14.7239 15.2538,14.5 14.95,14.5Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M14.95,11.5L5.05,11.5C4.7462,11.5 4.5,11.7239 4.5,12C4.5,12.2761 4.7462,12.5 5.05,12.5L14.95,12.5C15.2538,12.5 15.5,12.2761 15.5,12C15.5,11.7239 15.2538,11.5 14.95,11.5Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
|
@ -27,7 +27,7 @@
|
|||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:tint="@null"
|
||||
android:src="@drawable/ic_archive"
|
||||
android:src="@drawable/ic_tab_collection"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?foundation"
|
||||
android:drawableStart="@drawable/ic_archive"
|
||||
android:drawableStart="@drawable/ic_tab_collection"
|
||||
android:drawablePadding="14dp"
|
||||
android:drawableTint="?accent"
|
||||
android:paddingStart="20dp"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
android:id="@+id/no_collection_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableEnd="@drawable/ic_archive"
|
||||
android:drawableEnd="@drawable/ic_tab_collection"
|
||||
android:drawableTint="?primaryText"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/no_collections_header"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:drawableTint="?foundation"
|
||||
android:drawableStart="@drawable/ic_archive"
|
||||
android:drawableStart="@drawable/ic_tab_collection"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?foundation"
|
||||
android:drawableStart="@drawable/ic_archive"
|
||||
android:drawableStart="@drawable/ic_tab_collection"
|
||||
android:drawablePadding="14dp"
|
||||
android:drawableTint="?accent"
|
||||
android:paddingStart="20dp"
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:tint="@null"
|
||||
android:src="@drawable/ic_archive"
|
||||
android:src="@drawable/ic_tab_collection"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
|
|
|
@ -20,5 +20,4 @@ class TestComponents(private val context: Context) : Components(context) {
|
|||
)
|
||||
}
|
||||
override val analytics by lazy { Analytics(context) }
|
||||
override val storage by lazy { Storage(context) }
|
||||
}
|
|
@ -109,6 +109,7 @@ object Deps {
|
|||
const val mozilla_feature_session_bundling = "org.mozilla.components:feature-session-bundling:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_site_permissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_readerview = "org.mozilla.components:feature-readerview:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}"
|
||||
|
||||
const val mozilla_service_firefox_accounts = "org.mozilla.components:service-firefox-accounts:${Versions.mozilla_android_components}"
|
||||
const val mozilla_service_fretboard = "org.mozilla.components:service-fretboard:${Versions.mozilla_android_components}"
|
||||
|
|
Loading…
Reference in New Issue