From 72d29c2a435e524ef40fa2b500ce495bf45dce4b Mon Sep 17 00:00:00 2001 From: Sawyer Blatz Date: Thu, 16 May 2019 14:02:24 -0700 Subject: [PATCH] For #2205 & #1578: Integrates tab collection storage (#2478) * For #2205: Adds TabCollectionStorage * For #1578: Adds delete to TabCollection --- app/build.gradle | 1 + .../mozilla/fenix/browser/BrowserFragment.kt | 2 +- .../collections/CreateCollectionFragment.kt | 36 +++++++++--- .../mozilla/fenix/components/Components.kt | 1 - .../java/org/mozilla/fenix/components/Core.kt | 7 ++- .../{Storage.kt => PermissionStorage.kt} | 2 +- .../fenix/components/TabCollectionStorage.kt | 55 +++++++++++++++++++ .../components/toolbar/DefaultToolbarMenu.kt | 2 +- .../org/mozilla/fenix/home/HomeFragment.kt | 51 +++++++++-------- .../fenix/home/SessionBottomSheetFragment.kt | 2 +- .../sessioncontrol/SessionControlAdapter.kt | 18 ++++-- .../sessioncontrol/SessionControlComponent.kt | 35 ++++++++---- .../sessioncontrol/SessionControlUIView.kt | 23 +++++--- .../viewholders/CollectionViewHolder.kt | 9 ++- .../viewholders/TabInCollectionViewHolder.kt | 9 +-- .../library/bookmarks/BookmarkFragment.kt | 8 ++- ...itePermissionsDetailsExceptionsFragment.kt | 5 +- .../SitePermissionsExceptionsFragment.kt | 4 +- ...onsManageExceptionsPhoneFeatureFragment.kt | 2 +- .../quicksettings/QuickSettingsComponent.kt | 5 +- .../QuickSettingsSheetDialogFragment.kt | 2 +- app/src/main/res/drawable/ic_archive.xml | 13 ----- .../main/res/drawable/ic_tab_collection.xml | 20 +++++++ .../res/layout/collection_home_list_row.xml | 2 +- .../main/res/layout/collections_list_item.xml | 2 +- .../main/res/layout/no_collection_message.xml | 2 +- .../main/res/layout/save_tab_group_button.xml | 2 +- .../main/res/layout/session_bottom_sheet.xml | 2 +- app/src/main/res/layout/tab_in_collection.xml | 2 +- .../fenix/components/TestComponents.kt | 1 - buildSrc/src/main/java/Dependencies.kt | 1 + 31 files changed, 228 insertions(+), 98 deletions(-) rename app/src/main/java/org/mozilla/fenix/components/{Storage.kt => PermissionStorage.kt} (97%) create mode 100644 app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt delete mode 100644 app/src/main/res/drawable/ic_archive.xml create mode 100644 app/src/main/res/drawable/ic_tab_collection.xml diff --git a/app/build.gradle b/app/build.gradle index c5432dec4..96f2a3b3d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 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 402957236..c917a9bf4 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -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) } diff --git a/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt b/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt index 9f4763552..9d8e7029f 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt @@ -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().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() + it.tabs.forEach { + requireComponents.core.sessionManager.findSessionById(it.sessionId)?.let { session -> + sessionBundle.add(session) + } + } + + launch(Dispatchers.IO) { + requireComponents.core.tabCollectionStorage.createCollection(it.name, sessionBundle) + } } } } 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 beb850b7e..81aa421ec 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -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) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 98d8f3b85..42dbc4e83 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -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. diff --git a/app/src/main/java/org/mozilla/fenix/components/Storage.kt b/app/src/main/java/org/mozilla/fenix/components/PermissionStorage.kt similarity index 97% rename from app/src/main/java/org/mozilla/fenix/components/Storage.kt rename to app/src/main/java/org/mozilla/fenix/components/PermissionStorage.kt index 1a4f9b308..f81cf4487 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Storage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/PermissionStorage.kt @@ -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) diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt new file mode 100644 index 000000000..f9d4ecf1f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -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) { + collectionStorage.createCollection(title, sessions) + } + + fun addTabsToCollection(tabCollection: TabCollection, sessions: List) { + collectionStorage.addTabsToCollection(tabCollection, sessions) + } + + fun getCollections(limit: Int = 20): LiveData> { + return collectionStorage.getCollections(limit) + } + + fun getCollectionsPaged(): DataSource.Factory { + 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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index a12de91d8..5cadfd7eb 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -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) 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 d3b554d24..9f9a11720 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -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>? = 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() - 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().onNext( - SessionControlChange.CollectionsChange( - (savedInstanceState.getParcelableArrayList( - 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() + .onNext(SessionControlChange.ExpansionChange(action.collection, true)) } is CollectionAction.Collapse -> { - storedCollections.find { it.id == action.collection.id }?.apply { expanded = false } + getManagedEmitter() + .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().onNext(SessionControlChange.CollectionsChange(it)) - } } override fun onPause() { @@ -396,6 +392,14 @@ class HomeFragment : Fragment(), CoroutineScope { return getString(resourceId) } + private fun subscribeToTabCollections(): Observer> { + val observer = Observer> { + getManagedEmitter().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" } } diff --git a/app/src/main/java/org/mozilla/fenix/home/SessionBottomSheetFragment.kt b/app/src/main/java/org/mozilla/fenix/home/SessionBottomSheetFragment.kt index a3c16a9e7..569793810 100644 --- a/app/src/main/java/org/mozilla/fenix/home/SessionBottomSheetFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/SessionBottomSheetFragment.kt @@ -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!!, diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index 4ea0db123..3655412f2 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -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 = listOf() private lateinit var job: Job + private lateinit var expandedCollections: Set - fun reloadData(items: List) { + fun reloadData(items: List, expandedCollections: Set) { 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) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt index 64aa779b6..8f6d382b7 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt @@ -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, - 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, + val expandedCollections: Set, val collections: List, 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) : SessionControlChange() data class ModeChange(val mode: Mode) : SessionControlChange() data class CollectionsChange(val collections: List) : SessionControlChange() + data class ExpansionChange(val collection: TabCollection, val expand: Boolean) : SessionControlChange() } class SessionControlViewModel( initialState: SessionControlState ) : UIComponentViewModelBase(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) + } } } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt index 83bd11754..00ca46f21 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt @@ -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, collections: List): List { +private fun normalModeAdapterItems( + tabs: List, + collections: List, + expandedCollections: Set +): List { val items = mutableListOf() items.add(AdapterItem.TabHeader(false, tabs.isNotEmpty())) @@ -33,9 +37,9 @@ private fun normalModeAdapterItems(tabs: List, collections: List = listOf( ) private fun SessionControlState.toAdapterList(): List = 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): 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() init { view.apply { @@ -117,8 +126,8 @@ class SessionControlUIView( } override fun updateView() = Consumer { - 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 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 5c4169c28..e2dde6547 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 @@ -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() 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) 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 728686b60..9406b016c 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 @@ -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 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 3284267c0..81d79efc4 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 @@ -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().onNext(BookmarkChange.Change(currentRoot - it.item.guid)) + getManagedEmitter() + .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) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt index ea4a82d07..96bb8ba7c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsDetailsExceptionsFragment.kt @@ -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() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt index 50be9fd0d..a8fec1daa 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsExceptionsFragment.kt @@ -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() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt index eef204e69..d90f305d3 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt @@ -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) } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt index 27aef5308..0f2b04196 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt @@ -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 } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt index cba214dd0..120eee874 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt @@ -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) { diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml deleted file mode 100644 index 170c954d7..000000000 --- a/app/src/main/res/drawable/ic_archive.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_tab_collection.xml b/app/src/main/res/drawable/ic_tab_collection.xml new file mode 100644 index 000000000..d712f1766 --- /dev/null +++ b/app/src/main/res/drawable/ic_tab_collection.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/collection_home_list_row.xml b/app/src/main/res/layout/collection_home_list_row.xml index 3f79e62e4..fa11b6544 100644 --- a/app/src/main/res/layout/collection_home_list_row.xml +++ b/app/src/main/res/layout/collection_home_list_row.xml @@ -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"/> diff --git a/app/src/main/res/layout/collections_list_item.xml b/app/src/main/res/layout/collections_list_item.xml index 78dffb26b..73710d540 100644 --- a/app/src/main/res/layout/collections_list_item.xml +++ b/app/src/main/res/layout/collections_list_item.xml @@ -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" diff --git a/app/src/main/res/layout/no_collection_message.xml b/app/src/main/res/layout/no_collection_message.xml index 18e1a407d..0a5c7c9c0 100644 --- a/app/src/main/res/layout/no_collection_message.xml +++ b/app/src/main/res/layout/no_collection_message.xml @@ -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" diff --git a/app/src/main/res/layout/save_tab_group_button.xml b/app/src/main/res/layout/save_tab_group_button.xml index d23b185c8..f56886c03 100644 --- a/app/src/main/res/layout/save_tab_group_button.xml +++ b/app/src/main/res/layout/save_tab_group_button.xml @@ -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" diff --git a/app/src/main/res/layout/session_bottom_sheet.xml b/app/src/main/res/layout/session_bottom_sheet.xml index 31436f912..411c944fa 100644 --- a/app/src/main/res/layout/session_bottom_sheet.xml +++ b/app/src/main/res/layout/session_bottom_sheet.xml @@ -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" diff --git a/app/src/main/res/layout/tab_in_collection.xml b/app/src/main/res/layout/tab_in_collection.xml index ee977c794..6e8a597cb 100644 --- a/app/src/main/res/layout/tab_in_collection.xml +++ b/app/src/main/res/layout/tab_in_collection.xml @@ -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"/> diff --git a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt index 63bc9c3b5..2cbe901c1 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt @@ -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) } } \ No newline at end of file diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index a7e37779c..a83b4c009 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -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}"