For #10834 - Adding Sync Tabs Feature in Fenix
parent
0300f15df1
commit
4da22c605a
|
@ -488,6 +488,7 @@ dependencies {
|
|||
implementation Deps.mozilla_feature_qr
|
||||
implementation Deps.mozilla_feature_search
|
||||
implementation Deps.mozilla_feature_session
|
||||
implementation Deps.mozilla_feature_syncedtabs
|
||||
implementation Deps.mozilla_feature_toolbar
|
||||
implementation Deps.mozilla_feature_tabs
|
||||
implementation Deps.mozilla_feature_findinpage
|
||||
|
|
|
@ -18,6 +18,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
|
|||
FromHome(R.id.homeFragment),
|
||||
FromSearch(R.id.searchFragment),
|
||||
FromSettings(R.id.settingsFragment),
|
||||
FromSyncedTabs(R.id.syncedTabsFragment),
|
||||
FromBookmarks(R.id.bookmarkFragment),
|
||||
FromHistory(R.id.historyFragment),
|
||||
FromExceptions(R.id.exceptionsFragment),
|
||||
|
|
|
@ -44,6 +44,11 @@ object FeatureFlags {
|
|||
*/
|
||||
val loginsEdit = Config.channel.isNightlyOrDebug
|
||||
|
||||
/**
|
||||
* Enable tab sync feature
|
||||
*/
|
||||
val syncedTabs = Config.channel.isNightlyOrDebug
|
||||
|
||||
/**
|
||||
* Enables new tab tray pref
|
||||
*/
|
||||
|
|
|
@ -32,7 +32,9 @@ import mozilla.components.browser.search.SearchEngine
|
|||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.state.WebExtensionState
|
||||
import mozilla.components.browser.tabstray.BrowserTabsTray
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.concept.tabstray.TabsTray
|
||||
import mozilla.components.feature.contextmenu.ext.DefaultSelectionActionDelegate
|
||||
import mozilla.components.service.fxa.sync.SyncReason
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
|
@ -51,37 +53,36 @@ import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
|||
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.exceptions.ExceptionsFragmentDirections
|
||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||
import org.mozilla.fenix.ext.checkAndUpdateScreenshotPermission
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
|
||||
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
|
||||
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentDirections
|
||||
import org.mozilla.fenix.perf.Performance
|
||||
import org.mozilla.fenix.perf.StartupTimeline
|
||||
import org.mozilla.fenix.search.SearchFragmentDirections
|
||||
import org.mozilla.fenix.settings.DefaultBrowserSettingsFragmentDirections
|
||||
import org.mozilla.fenix.settings.logins.SavedLoginsAuthFragmentDirections
|
||||
import org.mozilla.fenix.settings.SettingsFragmentDirections
|
||||
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
|
||||
import org.mozilla.fenix.settings.about.AboutFragmentDirections
|
||||
import org.mozilla.fenix.settings.logins.SavedLoginsAuthFragmentDirections
|
||||
import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections
|
||||
import org.mozilla.fenix.settings.search.EditCustomSearchEngineFragmentDirections
|
||||
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
|
||||
import org.mozilla.fenix.sync.SyncedTabsFragmentDirections
|
||||
import org.mozilla.fenix.tabtray.FenixTabsAdapter
|
||||
import org.mozilla.fenix.theme.DefaultThemeManager
|
||||
import org.mozilla.fenix.theme.ThemeManager
|
||||
import org.mozilla.fenix.utils.BrowsersCache
|
||||
import org.mozilla.fenix.utils.RunWhenReadyQueue
|
||||
import mozilla.components.concept.tabstray.TabsTray
|
||||
import mozilla.components.browser.tabstray.BrowserTabsTray
|
||||
import org.mozilla.fenix.tabtray.FenixTabsAdapter
|
||||
|
||||
/**
|
||||
* The main activity of the application. The application is primarily a single Activity (this one)
|
||||
|
@ -367,6 +368,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
SearchFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSettings ->
|
||||
SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromSyncedTabs ->
|
||||
SyncedTabsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromBookmarks ->
|
||||
BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||
BrowserDirection.FromHistory ->
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.annotation.VisibleForTesting
|
|||
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
import mozilla.components.browser.storage.sync.RemoteTabsStorage
|
||||
import mozilla.components.concept.sync.AccountObserver
|
||||
import mozilla.components.concept.sync.AuthType
|
||||
import mozilla.components.concept.sync.DeviceCapability
|
||||
|
@ -17,6 +18,7 @@ import mozilla.components.concept.sync.DeviceType
|
|||
import mozilla.components.concept.sync.OAuthAccount
|
||||
import mozilla.components.feature.accounts.push.FxaPushSupportFeature
|
||||
import mozilla.components.feature.accounts.push.SendTabFeature
|
||||
import mozilla.components.feature.syncedtabs.storage.SyncedTabsStorage
|
||||
import mozilla.components.lib.crash.CrashReporter
|
||||
import mozilla.components.service.fxa.DeviceConfig
|
||||
import mozilla.components.service.fxa.ServerConfig
|
||||
|
@ -35,6 +37,7 @@ import org.mozilla.fenix.components.metrics.Event
|
|||
import org.mozilla.fenix.components.metrics.MetricController
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.sync.SyncedTabsIntegration
|
||||
import org.mozilla.fenix.utils.Mockable
|
||||
import org.mozilla.fenix.utils.RunWhenReadyQueue
|
||||
|
||||
|
@ -49,7 +52,8 @@ class BackgroundServices(
|
|||
crashReporter: CrashReporter,
|
||||
historyStorage: Lazy<PlacesHistoryStorage>,
|
||||
bookmarkStorage: Lazy<PlacesBookmarksStorage>,
|
||||
passwordsStorage: Lazy<SyncableLoginsStorage>
|
||||
passwordsStorage: Lazy<SyncableLoginsStorage>,
|
||||
remoteTabsStorage: Lazy<RemoteTabsStorage>
|
||||
) {
|
||||
// Allows executing tasks which depend on the account manager, but do not need to eagerly initialize it.
|
||||
val accountManagerAvailableQueue = RunWhenReadyQueue()
|
||||
|
@ -83,16 +87,28 @@ class BackgroundServices(
|
|||
val syncConfig = if (FeatureFlags.asFeatureSyncDisabled) {
|
||||
null
|
||||
} else {
|
||||
|
||||
val supportedEngines = if (FeatureFlags.syncedTabs) {
|
||||
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords, SyncEngine.Tabs)
|
||||
} else {
|
||||
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords)
|
||||
}
|
||||
|
||||
SyncConfig(
|
||||
setOf(SyncEngine.History, SyncEngine.Bookmarks, SyncEngine.Passwords),
|
||||
supportedEngines,
|
||||
syncPeriodInMinutes = 240L) // four hours
|
||||
}
|
||||
|
||||
init {
|
||||
// Make the "history", "bookmark", and "passwords" stores accessible to workers spawned by the sync manager.
|
||||
/* Make the "history", "bookmark", "passwords", and "tabs" stores accessible to workers
|
||||
spawned by the sync manager. */
|
||||
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
|
||||
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
|
||||
GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage)
|
||||
|
||||
if (FeatureFlags.syncedTabs) {
|
||||
GlobalSyncableStoreProvider.configureStore(SyncEngine.Tabs to remoteTabsStorage)
|
||||
}
|
||||
}
|
||||
|
||||
private val telemetryAccountObserver = TelemetryAccountObserver(
|
||||
|
@ -104,6 +120,10 @@ class BackgroundServices(
|
|||
|
||||
val accountManager by lazy { makeAccountManager(context, serverConfig, deviceConfig, syncConfig) }
|
||||
|
||||
val syncedTabsStorage by lazy {
|
||||
SyncedTabsStorage(accountManager, context.components.core.store, remoteTabsStorage.value)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = PRIVATE)
|
||||
fun makeAccountManager(
|
||||
context: Context,
|
||||
|
@ -148,6 +168,8 @@ class BackgroundServices(
|
|||
notificationManager.showReceivedTabs(context, device, tabs)
|
||||
}
|
||||
|
||||
SyncedTabsIntegration(context, accountManager).launch()
|
||||
|
||||
accountAbnormalities.accountManagerInitializedAsync(
|
||||
accountManager,
|
||||
accountManager.initAsync()
|
||||
|
|
|
@ -37,7 +37,8 @@ class Components(private val context: Context) {
|
|||
analytics.crashReporter,
|
||||
core.lazyHistoryStorage,
|
||||
core.lazyBookmarksStorage,
|
||||
core.lazyPasswordsStorage
|
||||
core.lazyPasswordsStorage,
|
||||
core.lazyRemoteTabsStorage
|
||||
)
|
||||
}
|
||||
val services by lazy { Services(context, backgroundServices.accountManager) }
|
||||
|
|
|
@ -22,6 +22,7 @@ import mozilla.components.browser.state.state.BrowserState
|
|||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
import mozilla.components.browser.storage.sync.RemoteTabsStorage
|
||||
import mozilla.components.browser.thumbnails.ThumbnailsMiddleware
|
||||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
|
||||
import mozilla.components.concept.engine.DefaultSettings
|
||||
|
@ -212,6 +213,11 @@ class Core(private val context: Context) {
|
|||
val lazyBookmarksStorage = lazy { PlacesBookmarksStorage(context) }
|
||||
val lazyPasswordsStorage = lazy { SyncableLoginsStorage(context, passwordsEncryptionKey) }
|
||||
|
||||
/**
|
||||
* The storage component to sync and persist tabs in a Firefox Sync account.
|
||||
*/
|
||||
val lazyRemoteTabsStorage = lazy { RemoteTabsStorage() }
|
||||
|
||||
// For most other application code (non-startup), these wrappers are perfectly fine and more ergonomic.
|
||||
val historyStorage by lazy { lazyHistoryStorage.value }
|
||||
val bookmarksStorage by lazy { lazyBookmarksStorage.value }
|
||||
|
|
|
@ -419,7 +419,7 @@ sealed class Event {
|
|||
NEW_PRIVATE_TAB, SHARE, REPORT_SITE_ISSUE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX,
|
||||
SAVE_TO_COLLECTION, ADD_TO_TOP_SITES, ADD_TO_HOMESCREEN, QUIT, READER_MODE_ON,
|
||||
READER_MODE_OFF, OPEN_IN_APP, BOOKMARK, READER_MODE_APPEARANCE, ADDONS_MANAGER,
|
||||
BOOKMARKS, HISTORY
|
||||
BOOKMARKS, HISTORY, SYNC_TABS
|
||||
}
|
||||
|
||||
override val extras: Map<Events.browserMenuActionKeys, String>?
|
||||
|
|
|
@ -158,6 +158,10 @@ class DefaultBrowserToolbarController(
|
|||
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
|
||||
navController.nav(R.id.browserFragment, directions)
|
||||
}
|
||||
ToolbarMenu.Item.SyncedTabs -> {
|
||||
navController.nav(
|
||||
R.id.browserFragment, BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment())
|
||||
}
|
||||
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(
|
||||
item.isChecked,
|
||||
currentSession
|
||||
|
@ -372,6 +376,7 @@ class DefaultBrowserToolbarController(
|
|||
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
|
||||
ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES
|
||||
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
|
||||
ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS
|
||||
ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
|
||||
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
|
||||
is ToolbarMenu.Item.ReaderMode ->
|
||||
|
|
|
@ -25,9 +25,10 @@ import mozilla.components.browser.state.selector.findTab
|
|||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.storage.BookmarksStorage
|
||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||
import org.mozilla.fenix.Config
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.Config
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.ReleaseChannel
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.ext.asActivity
|
||||
|
@ -190,6 +191,7 @@ class DefaultToolbarMenu(
|
|||
val menuItems = listOfNotNull(
|
||||
historyItem,
|
||||
bookmarksItem,
|
||||
if (FeatureFlags.syncedTabs) syncedTabs else null,
|
||||
settings,
|
||||
if (shouldDeleteDataOnQuit) deleteDataOnQuit else null,
|
||||
BrowserMenuDivider(),
|
||||
|
@ -259,6 +261,14 @@ class DefaultToolbarMenu(
|
|||
onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen)
|
||||
}
|
||||
|
||||
private val syncedTabs = BrowserMenuImageText(
|
||||
label = context.getString(R.string.synced_tabs),
|
||||
imageResource = R.drawable.ic_tab_collection,
|
||||
iconTintColorResource = primaryTextColor()
|
||||
) {
|
||||
onItemTapped.invoke(ToolbarMenu.Item.SyncedTabs)
|
||||
}
|
||||
|
||||
private val installToHomescreen = BrowserMenuHighlightableItem(
|
||||
label = context.getString(R.string.browser_menu_install_on_homescreen),
|
||||
startImageResource = R.drawable.ic_add_to_homescreen,
|
||||
|
|
|
@ -23,6 +23,7 @@ interface ToolbarMenu {
|
|||
object AddToTopSites : Item()
|
||||
object InstallToHomeScreen : Item()
|
||||
object AddToHomeScreen : Item()
|
||||
object SyncedTabs : Item()
|
||||
object AddonsManager : Item()
|
||||
object Quit : Item()
|
||||
data class ReaderMode(val isChecked: Boolean) : Item()
|
||||
|
|
|
@ -675,6 +675,14 @@ class HomeFragment : Fragment() {
|
|||
HomeFragmentDirections.actionGlobalSettingsFragment()
|
||||
)
|
||||
}
|
||||
HomeMenu.Item.SyncedTabs -> {
|
||||
invokePendingDeleteJobs()
|
||||
hideOnboardingIfNeeded()
|
||||
nav(
|
||||
R.id.homeFragment,
|
||||
HomeFragmentDirections.actionGlobalSyncedTabsFragment()
|
||||
)
|
||||
}
|
||||
HomeMenu.Item.Bookmarks -> {
|
||||
invokePendingDeleteJobs()
|
||||
hideOnboardingIfNeeded()
|
||||
|
|
|
@ -21,6 +21,7 @@ import mozilla.components.concept.sync.AccountObserver
|
|||
import mozilla.components.concept.sync.AuthType
|
||||
import mozilla.components.concept.sync.OAuthAccount
|
||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
@ -39,6 +40,7 @@ class HomeMenu(
|
|||
object WhatsNew : Item()
|
||||
object Help : Item()
|
||||
object Settings : Item()
|
||||
object SyncedTabs : Item()
|
||||
object History : Item()
|
||||
object Bookmarks : Item()
|
||||
object Quit : Item()
|
||||
|
@ -118,6 +120,14 @@ class HomeMenu(
|
|||
onItemTapped.invoke(Item.Settings)
|
||||
}
|
||||
|
||||
val syncedTabsItem = BrowserMenuImageText(
|
||||
context.getString(R.string.library_synced_tabs),
|
||||
R.drawable.ic_tab_collection,
|
||||
primaryTextColor
|
||||
) {
|
||||
onItemTapped.invoke(Item.SyncedTabs)
|
||||
}
|
||||
|
||||
val helpItem = BrowserMenuImageText(
|
||||
context.getString(R.string.browser_menu_help),
|
||||
R.drawable.ic_help,
|
||||
|
@ -141,6 +151,7 @@ class HomeMenu(
|
|||
BrowserMenuDivider(),
|
||||
bookmarksItem,
|
||||
historyItem,
|
||||
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
|
||||
BrowserMenuDivider(),
|
||||
settingsItem,
|
||||
helpItem,
|
||||
|
@ -157,6 +168,7 @@ class HomeMenu(
|
|||
BrowserMenuDivider(),
|
||||
bookmarksItem,
|
||||
historyItem,
|
||||
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
|
||||
BrowserMenuDivider(),
|
||||
whatsNewItem
|
||||
).also { items ->
|
||||
|
|
|
@ -35,6 +35,7 @@ import mozilla.components.service.fxa.sync.SyncReason
|
|||
import mozilla.components.service.fxa.sync.SyncStatusObserver
|
||||
import mozilla.components.service.fxa.sync.getLastSynced
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
|
@ -206,6 +207,16 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
val tabsNameKey = getPreferenceKey(R.string.pref_key_sync_tabs)
|
||||
findPreference<CheckBoxPreference>(tabsNameKey)?.apply {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
SyncEnginesStorage(context).setStatus(SyncEngine.Tabs, newValue as Boolean)
|
||||
@Suppress("DeferredResultUnused")
|
||||
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
deviceConstellation?.registerDeviceObserver(
|
||||
deviceConstellationObserver,
|
||||
owner = this,
|
||||
|
@ -263,6 +274,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
|||
isEnabled = syncEnginesStatus.containsKey(SyncEngine.Passwords)
|
||||
isChecked = syncEnginesStatus.getOrElse(SyncEngine.Passwords) { true }
|
||||
}
|
||||
val tabsNameKey = getPreferenceKey(R.string.pref_key_sync_tabs)
|
||||
findPreference<CheckBoxPreference>(tabsNameKey)?.apply {
|
||||
isVisible = FeatureFlags.syncedTabs
|
||||
isEnabled = syncEnginesStatus.containsKey(SyncEngine.Tabs)
|
||||
isChecked = syncEnginesStatus.getOrElse(SyncEngine.Tabs) { FeatureFlags.syncedTabs }
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncNow() {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import mozilla.components.concept.sync.Device as SyncDevice
|
||||
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
||||
|
||||
class SyncedTabsAdapter(
|
||||
private val listener: (SyncTab) -> Unit
|
||||
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(
|
||||
DiffCallback
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SyncedTabsViewHolder {
|
||||
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
||||
return when (viewType) {
|
||||
DeviceViewHolder.LAYOUT_ID -> DeviceViewHolder(itemView)
|
||||
TabViewHolder.LAYOUT_ID -> TabViewHolder(itemView)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) {
|
||||
val item = when (holder) {
|
||||
is DeviceViewHolder -> getItem(position) as AdapterItem.Device
|
||||
is TabViewHolder -> getItem(position) as AdapterItem.Tab
|
||||
}
|
||||
holder.bind(item, listener)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (getItem(position)) {
|
||||
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
||||
}
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||
areContentsTheSame(oldItem, newItem)
|
||||
|
||||
@Suppress("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||
oldItem == newItem
|
||||
}
|
||||
|
||||
sealed class AdapterItem {
|
||||
data class Device(val device: SyncDevice) : AdapterItem()
|
||||
data class Tab(val tab: SyncTab) : AdapterItem()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_synced_tabs.*
|
||||
import mozilla.components.browser.storage.sync.Tab
|
||||
import mozilla.components.feature.syncedtabs.SyncedTabsFeature
|
||||
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
import org.mozilla.fenix.library.LibraryPageFragment
|
||||
|
||||
class SyncedTabsFragment : LibraryPageFragment<Tab>() {
|
||||
private val syncedTabsFeature = ViewBoundFeatureWrapper<SyncedTabsFeature>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_synced_tabs, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val backgroundServices = requireContext().components.backgroundServices
|
||||
|
||||
syncedTabsFeature.set(
|
||||
feature = SyncedTabsFeature(
|
||||
storage = backgroundServices.syncedTabsStorage,
|
||||
accountManager = backgroundServices.accountManager,
|
||||
view = synced_tabs_layout,
|
||||
lifecycleOwner = this.viewLifecycleOwner,
|
||||
onTabClicked = ::handleTabClicked
|
||||
),
|
||||
owner = this,
|
||||
view = view
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(getString(R.string.library_synced_tabs))
|
||||
}
|
||||
|
||||
private fun handleTabClicked(tab: Tab) {
|
||||
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = tab.active().url,
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromSyncedTabs
|
||||
)
|
||||
}
|
||||
|
||||
override val selectedItems: Set<Tab>
|
||||
get() = emptySet()
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import mozilla.components.concept.sync.AccountObserver
|
||||
import mozilla.components.concept.sync.AuthType
|
||||
import mozilla.components.concept.sync.OAuthAccount
|
||||
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
class SyncedTabsIntegration(
|
||||
private val context: Context,
|
||||
private val accountManager: FxaAccountManager
|
||||
) {
|
||||
fun launch() {
|
||||
val accountObserver = SyncedTabsAccountObserver(context)
|
||||
|
||||
accountManager.register(
|
||||
accountObserver,
|
||||
owner = ProcessLifecycleOwner.get(),
|
||||
autoPause = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyncedTabsAccountObserver(private val context: Context) : AccountObserver {
|
||||
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
|
||||
context.components.backgroundServices.syncedTabsStorage.start()
|
||||
}
|
||||
|
||||
override fun onLoggedOut() {
|
||||
context.components.backgroundServices.syncedTabsStorage.stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class SyncedTabsLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr), SyncedTabsView {
|
||||
|
||||
override var listener: SyncedTabsView.Listener? = null
|
||||
|
||||
private val adapter = SyncedTabsAdapter { listener?.onTabClicked(it) }
|
||||
|
||||
init {
|
||||
inflate(getContext(), R.layout.component_sync_tabs, this)
|
||||
|
||||
synced_tabs_list.layoutManager = LinearLayoutManager(context)
|
||||
synced_tabs_list.adapter = adapter
|
||||
|
||||
synced_tabs_pull_to_refresh.setOnRefreshListener { listener?.onRefresh() }
|
||||
}
|
||||
|
||||
override fun onError(error: SyncedTabsView.ErrorType) {
|
||||
val stringResId = when (error) {
|
||||
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
|
||||
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
|
||||
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
|
||||
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
|
||||
}
|
||||
|
||||
sync_tabs_status.text = context.getText(stringResId)
|
||||
|
||||
synced_tabs_list.visibility = View.GONE
|
||||
sync_tabs_status.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
|
||||
synced_tabs_list.visibility = View.VISIBLE
|
||||
sync_tabs_status.visibility = View.GONE
|
||||
|
||||
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
|
||||
|
||||
syncedTabs.forEach { (device, tabs) ->
|
||||
if (tabs.isEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val deviceTabs = tabs.map { SyncedTabsAdapter.AdapterItem.Tab(it) }
|
||||
|
||||
allDeviceTabs += listOf(SyncedTabsAdapter.AdapterItem.Device(device)) + deviceTabs
|
||||
}
|
||||
|
||||
adapter.submitList(allDeviceTabs)
|
||||
}
|
||||
|
||||
override fun startLoading() {
|
||||
synced_tabs_list.visibility = View.VISIBLE
|
||||
sync_tabs_status.visibility = View.GONE
|
||||
|
||||
synced_tabs_pull_to_refresh.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun stopLoading() {
|
||||
synced_tabs_pull_to_refresh.isRefreshing = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* 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.sync
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
|
||||
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
|
||||
import mozilla.components.browser.storage.sync.Tab
|
||||
import mozilla.components.concept.sync.DeviceType
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
|
||||
|
||||
sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
abstract fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit)
|
||||
|
||||
class TabViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||
|
||||
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||
bindTab(item as AdapterItem.Tab)
|
||||
|
||||
itemView.setOnClickListener {
|
||||
interactor(item.tab)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindTab(tab: AdapterItem.Tab) {
|
||||
val active = tab.tab.active()
|
||||
itemView.synced_tab_item_title.text = active.title
|
||||
itemView.synced_tab_item_url.text = active.url
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.sync_tabs_list_item
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||
|
||||
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||
bindHeader(item as AdapterItem.Device)
|
||||
}
|
||||
|
||||
private fun bindHeader(device: AdapterItem.Device) {
|
||||
|
||||
val deviceLogoDrawable = when (device.device.deviceType) {
|
||||
DeviceType.DESKTOP -> { R.drawable.mozac_ic_device_desktop }
|
||||
else -> { R.drawable.mozac_ic_device_mobile }
|
||||
}
|
||||
|
||||
itemView.synced_tabs_group_name.text = device.device.displayName
|
||||
itemView.synced_tabs_group_name.setCompoundDrawablesWithIntrinsicBounds(deviceLogoDrawable, 0, 0, 0)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.view_synced_tabs_group
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/history_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ProgressBar
|
||||
android:id="@+id/sync_tabs_progress_bar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:indeterminate="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:translationY="-3dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sync_tabs_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/sync_connect_device"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/synced_tabs_pull_to_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/synced_tabs_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/history_list_item"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,9 @@
|
|||
<?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/. -->
|
||||
<org.mozilla.fenix.sync.SyncedTabsLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/synced_tabs_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
|
@ -0,0 +1,63 @@
|
|||
<?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/. -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="60dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:minHeight="@dimen/library_item_height"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/synced_tab_item_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?primaryText"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/synced_tab_item_url"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Tab Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/synced_tab_item_url"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="12sp"
|
||||
tools:text="https://example.com/"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/synced_tab_item_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/synced_tab_item_separator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="?neutralFaded"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,31 @@
|
|||
<?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/. -->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/synced_tabs_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/synced_tabs_group_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="15dp"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_device_desktop"
|
||||
app:drawableTint="?primaryText"
|
||||
android:textSize="17sp"
|
||||
android:textAppearance="@style/Header14TextStyle"
|
||||
android:textColor="?primaryText"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
tools:text="Header" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -36,6 +36,7 @@
|
|||
<action android:id="@+id/action_global_deleteBrowsingDataFragment" app:destination="@id/deleteBrowsingDataFragment" />
|
||||
<action android:id="@+id/action_global_webExtensionActionPopupFragment" app:destination="@id/webExtensionActionPopupFragment" />
|
||||
<action android:id="@+id/action_global_settingsFragment" app:destination="@id/settingsFragment" />
|
||||
<action android:id="@+id/action_global_syncedTabsFragment" app:destination="@+id/syncedTabsFragment" />
|
||||
<action android:id="@+id/action_global_privateBrowsingFragment" app:destination="@id/privateBrowsingFragment"/>
|
||||
<action android:id="@+id/action_global_bookmarkFragment" app:destination="@id/bookmarkFragment"/>
|
||||
<action android:id="@+id/action_global_historyFragment" app:destination="@id/historyFragment"/>
|
||||
|
@ -149,6 +150,9 @@
|
|||
android:name="shouldAnimate"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<action
|
||||
android:id="@+id/action_browserFragment_to_syncedTabsFragment"
|
||||
app:destination="@id/syncedTabsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_browserFragment_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment" />
|
||||
|
@ -295,6 +299,13 @@
|
|||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/syncedTabsFragment"
|
||||
android:name="org.mozilla.fenix.sync.SyncedTabsFragment"
|
||||
android:label="@string/synced_tabs"
|
||||
tools:layout="@layout/fragment_synced_tabs">
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/loginDetailFragment"
|
||||
android:name="org.mozilla.fenix.settings.logins.LoginDetailFragment"
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
<dimen name="phone_feature_label_recommended_text_size">14sp</dimen>
|
||||
<dimen name="site_permissions_exceptions_item_text_size">18sp</dimen>
|
||||
<dimen name="site_permissions_exceptions_item_height">56dp</dimen>
|
||||
<dimen name="synced_tab_item_min_height">56dp</dimen>
|
||||
<dimen name="component_collection_creation_list_margin">16dp</dimen>
|
||||
<dimen name="exceptions_description_margin">12dp</dimen>
|
||||
<dimen name="preference_seek_bar_padding">24dp</dimen>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<string name="pref_key_sync_history" translatable="false">pref_key_sync_history</string>
|
||||
<string name="pref_key_sync_bookmarks" translatable="false">pref_key_sync_bookmarks</string>
|
||||
<string name="pref_key_sync_logins" translatable="false">pref_key_sync_logins</string>
|
||||
<string name="pref_key_sync_tabs" translatable="false">pref_key_sync_tabs</string>
|
||||
<string name="pref_key_sign_out" translatable="false">pref_key_sign_out</string>
|
||||
<string name="pref_key_cached_account" translatable="false">pref_key_cached_account</string>
|
||||
<string name="pref_key_sync_pair" translatable="false">pref_key_sync_pair</string>
|
||||
|
|
|
@ -95,6 +95,8 @@
|
|||
<string name="browser_menu_add_to_homescreen">Add to Home screen</string>
|
||||
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
|
||||
<string name="browser_menu_install_on_homescreen">Install</string>
|
||||
<!-- Menu option on the toolbar that takes you to synced tabs page-->
|
||||
<string name="synced_tabs">Synced Tabs</string>
|
||||
<!-- Browser menu button that opens the find in page menu -->
|
||||
<string name="browser_menu_find_in_page">Find in page</string>
|
||||
<!-- Browser menu button that creates a private tab -->
|
||||
|
@ -286,6 +288,8 @@
|
|||
<string name="preferences_sync_bookmarks">Bookmarks</string>
|
||||
<!-- Preference for syncing logins -->
|
||||
<string name="preferences_sync_logins">Logins</string>
|
||||
<!-- Preference for syncing tabs -->
|
||||
<string name="preferences_sync_tabs">Tabs</string>
|
||||
<!-- Preference for signing out -->
|
||||
<string name="preferences_sign_out">Sign out</string>
|
||||
<!-- Preference displays and allows changing current FxA device name -->
|
||||
|
@ -416,6 +420,8 @@
|
|||
<string name="library_desktop_bookmarks_unfiled">Other Bookmarks</string>
|
||||
<!-- Option in Library to open History page -->
|
||||
<string name="library_history">History</string>
|
||||
<!-- Option in Library to open Synced Tabs page -->
|
||||
<string name="library_synced_tabs">Synced Tabs</string>
|
||||
<!-- Option in Library to open Reading List -->
|
||||
<string name="library_reading_list">Reading List</string>
|
||||
<!-- Menu Item Label for Search in Library -->
|
||||
|
@ -1378,4 +1384,15 @@
|
|||
<string name="voice_search_content_description">Voice search</string>
|
||||
<!-- Voice search prompt description displayed after the user presses the voice search button -->
|
||||
<string name="voice_search_explainer">Speak now</string>
|
||||
|
||||
<!-- Synced Tabs -->
|
||||
<!-- Text displayed when user is not logged into a Firefox Account -->
|
||||
<string name="synced_tabs_connect_to_sync_account">Connect with a Firefox Account.</string>
|
||||
<!-- Text displayed to ask user to connect another device as no devices found with account -->
|
||||
<string name="synced_tabs_connect_another_device">Connect another device.</string>
|
||||
<!-- Text displayed asking user to re-authenticate -->
|
||||
<string name="synced_tabs_reauth">Please re-authenticate.</string>
|
||||
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
|
||||
<string name="synced_tabs_enable_tab_syncing">Please enable tab syncing.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -42,5 +42,13 @@
|
|||
android:key="@string/pref_key_sync_logins"
|
||||
android:layout="@layout/checkbox_left_preference"
|
||||
android:title="@string/preferences_sync_logins" />
|
||||
|
||||
<androidx.preference.CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
app:isPreferenceVisible="false"
|
||||
android:key="@string/pref_key_sync_tabs"
|
||||
android:layout="@layout/checkbox_left_preference"
|
||||
android:title="@string/preferences_sync_tabs"/>
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.mozilla.fenix.components.metrics.MetricController
|
|||
class BackgroundServicesTest {
|
||||
class TestableBackgroundServices(
|
||||
val context: Context
|
||||
) : BackgroundServices(context, mockk(), mockk(), mockk(), mockk(), mockk()) {
|
||||
) : BackgroundServices(context, mockk(), mockk(), mockk(), mockk(), mockk(), mockk()) {
|
||||
override fun makeAccountManager(
|
||||
context: Context,
|
||||
serverConfig: ServerConfig,
|
||||
|
|
|
@ -97,6 +97,7 @@ object Deps {
|
|||
const val mozilla_feature_qr = "org.mozilla.components:feature-qr:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_search = "org.mozilla.components:feature-search:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_session = "org.mozilla.components:feature-session:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_syncedtabs = "org.mozilla.components:feature-syncedtabs:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_tabs = "org.mozilla.components:feature-tabs:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_downloads = "org.mozilla.components:feature-downloads:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_storage = "org.mozilla.components:feature-storage:${Versions.mozilla_android_components}"
|
||||
|
|
Loading…
Reference in New Issue