diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsAdapter.kt deleted file mode 100644 index ca10648eb..000000000 --- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsAdapter.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* 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.navigation.NavController -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import mozilla.components.browser.storage.sync.SyncedDeviceTabs -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder -import org.mozilla.fenix.sync.SyncedTabsViewHolder.ErrorViewHolder -import org.mozilla.fenix.sync.SyncedTabsViewHolder.NoTabsViewHolder -import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder -import org.mozilla.fenix.sync.SyncedTabsViewHolder.TitleViewHolder -import org.mozilla.fenix.sync.ext.toAdapterList -import mozilla.components.browser.storage.sync.Tab as SyncTab -import mozilla.components.concept.sync.Device as SyncDevice - -class SyncedTabsAdapter( - private val newListener: SyncedTabsView.Listener -) : ListAdapter(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) - ErrorViewHolder.LAYOUT_ID -> ErrorViewHolder(itemView) - TitleViewHolder.LAYOUT_ID -> TitleViewHolder(itemView) - NoTabsViewHolder.LAYOUT_ID -> NoTabsViewHolder(itemView) - else -> throw IllegalStateException() - } - } - - override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) { - holder.bind(getItem(position), newListener) - } - - override fun getItemViewType(position: Int) = when (getItem(position)) { - is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID - is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID - is AdapterItem.Error -> ErrorViewHolder.LAYOUT_ID - is AdapterItem.Title -> TitleViewHolder.LAYOUT_ID - is AdapterItem.NoTabs -> NoTabsViewHolder.LAYOUT_ID - } - - fun updateData(syncedTabs: List) { - val allDeviceTabs = syncedTabs.toAdapterList() - submitList(allDeviceTabs) - } - - private object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - when (oldItem) { - is AdapterItem.Device -> - newItem is AdapterItem.Device && oldItem.device.id == newItem.device.id - is AdapterItem.NoTabs -> - newItem is AdapterItem.NoTabs && oldItem.device.id == newItem.device.id - is AdapterItem.Tab, - is AdapterItem.Error, - is AdapterItem.Title -> - oldItem == newItem - } - - @Suppress("DiffUtilEquals") - override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - oldItem == newItem - } - - /** - * The various types of adapter items that can be found in a [SyncedTabsAdapter]. - */ - sealed class AdapterItem { - - /** - * A title header of the Synced Tabs UI that has a refresh button in it. This may be seen - * only in some views depending on where the Synced Tabs UI is displayed. - */ - object Title : AdapterItem() - - /** - * A device header for displaying a synced device. - */ - data class Device(val device: SyncDevice) : AdapterItem() - - /** - * A tab that was synced. - */ - data class Tab(val tab: SyncTab) : AdapterItem() - - /** - * A placeholder for a device that has no tabs synced. - */ - data class NoTabs(val device: SyncDevice) : AdapterItem() - - /** - * A message displayed if an error was encountered. - */ - data class Error( - val descriptionResId: Int, - val navController: NavController? = null - ) : AdapterItem() - } -} diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsTitleDecoration.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsTitleDecoration.kt deleted file mode 100644 index 13722a915..000000000 --- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsTitleDecoration.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* 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.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.ItemDecoration -import mozilla.components.support.ktx.android.util.dpToPx -import org.mozilla.fenix.R -import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder - -/** - * Adds an [ItemDecoration] to the device name of each Synced Tab group. - */ -class SyncedTabsTitleDecoration( - context: Context, - private val style: Style = Style( - height = 1.dpToPx(context.resources.displayMetrics), - color = run { - val a = context.obtainStyledAttributes(intArrayOf(R.attr.toolbarDivider)) - val color = a.getDrawable(0)!! - a.recycle() - color - } - ) -) : ItemDecoration() { - - /** - * A class for holding various customizations. - */ - data class Style(val height: Int, val color: Drawable) - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - val viewHolder = parent.getChildViewHolder(view) - val position = viewHolder.bindingAdapterPosition - val viewType = viewHolder.itemViewType - - // Only add offsets on the device title that is not the first. - if (viewType == DeviceViewHolder.LAYOUT_ID && position != 0) { - outRect.set(0, style.height, 0, 0) - return - } - - outRect.setEmpty() - } - - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - for (i in 0 until parent.childCount) { - val view = parent.getChildAt(i) - val viewHolder = parent.getChildViewHolder(view) - val position = viewHolder.bindingAdapterPosition - val viewType = viewHolder.itemViewType - - // Only draw on the device title that is not the first. - if (viewType == DeviceViewHolder.LAYOUT_ID && position != 0) { - style.color.setBounds( - view.left, - view.top - style.height, - view.right, - view.top - ) - style.color.draw(c) - } - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt deleted file mode 100644 index bf57fb1d4..000000000 --- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsViewHolder.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* 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 android.view.View.GONE -import android.view.View.VISIBLE -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import androidx.annotation.VisibleForTesting -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.browser.toolbar.MAX_URI_LENGTH -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import org.mozilla.fenix.NavGraphDirections -import org.mozilla.fenix.R -import org.mozilla.fenix.databinding.SyncTabsErrorRowBinding -import org.mozilla.fenix.databinding.SyncTabsListItemBinding -import org.mozilla.fenix.databinding.ViewSyncedTabsGroupBinding -import org.mozilla.fenix.databinding.ViewSyncedTabsTitleBinding -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.toShortUrl -import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem - -/** - * The various view-holders that can be found in a [SyncedTabsAdapter]. For more - * descriptive information on the different types, see the docs for [AdapterItem]. - */ -sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - abstract fun bind(item: T, interactor: SyncedTabsView.Listener) - - class TabViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { - override fun bind(item: T, interactor: SyncedTabsView.Listener) { - bindTab(item as AdapterItem.Tab) - - itemView.setOnClickListener { - interactor.onTabClicked(item.tab) - } - } - - private fun bindTab(tab: AdapterItem.Tab) { - val active = tab.tab.active() - val binding = SyncTabsListItemBinding.bind(itemView) - binding.syncedTabItemTitle.text = active.title - binding.syncedTabItemUrl.text = active.url - .toShortUrl(itemView.context.components.publicSuffixList) - .take(MAX_URI_LENGTH) - } - - companion object { - const val LAYOUT_ID = R.layout.sync_tabs_list_item - } - } - - class ErrorViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { - override fun bind(item: T, interactor: SyncedTabsView.Listener) { - val errorItem = item as AdapterItem.Error - val binding = SyncTabsErrorRowBinding.bind(itemView) - - binding.syncTabsErrorDescription.text = - itemView.context.getString(errorItem.descriptionResId) - binding.syncTabsErrorCtaButton.visibility = GONE - - errorItem.navController?.let { navController -> - binding.syncTabsErrorCtaButton.visibility = VISIBLE - binding.syncTabsErrorCtaButton.setOnClickListener { - navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) - } - } - } - - companion object { - const val LAYOUT_ID = R.layout.sync_tabs_error_row - } - } - - class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { - - @VisibleForTesting - internal val binding = ViewSyncedTabsGroupBinding.bind(itemView) - - override fun bind(item: T, interactor: SyncedTabsView.Listener) { - bindHeader(item as AdapterItem.Device) - } - - private fun bindHeader(device: AdapterItem.Device) { - binding.syncedTabsGroupName.text = device.device.displayName - } - - companion object { - const val LAYOUT_ID = R.layout.view_synced_tabs_group - } - } - - class NoTabsViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { - override fun bind(item: T, interactor: SyncedTabsView.Listener) = Unit - - companion object { - const val LAYOUT_ID = R.layout.view_synced_tabs_no_item - } - } - - class TitleViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) { - override fun bind(item: T, interactor: SyncedTabsView.Listener) { - val binding = ViewSyncedTabsTitleBinding.bind(itemView) - binding.refreshIcon.setOnClickListener { v -> - val rotation = AnimationUtils.loadAnimation( - itemView.context, - R.anim.full_rotation - ).apply { - repeatCount = Animation.ABSOLUTE - } - - v.startAnimation(rotation) - - interactor.onRefresh() - } - } - - companion object { - const val LAYOUT_ID = R.layout.view_synced_tabs_title - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/sync/ext/ErrorType.kt b/app/src/main/java/org/mozilla/fenix/sync/ext/ErrorType.kt deleted file mode 100644 index ab4817b59..000000000 --- a/app/src/main/java/org/mozilla/fenix/sync/ext/ErrorType.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* 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.ext - -import androidx.annotation.StringRes -import androidx.navigation.NavController -import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType -import org.mozilla.fenix.R -import org.mozilla.fenix.sync.SyncedTabsAdapter - -/** - * Converts the error type to the appropriate matching string resource for displaying to the user. - */ -fun ErrorType.toStringRes() = when (this) { - ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device - ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing - ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_sign_in_message - ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth - ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs -} - -/** - * Converts an error type to an [SyncedTabsAdapter.AdapterItem.Error]. - */ -fun ErrorType.toAdapterItem( - @StringRes stringResId: Int, - navController: NavController? = null -) = when (this) { - ErrorType.MULTIPLE_DEVICES_UNAVAILABLE, - ErrorType.SYNC_ENGINE_UNAVAILABLE, - ErrorType.SYNC_NEEDS_REAUTHENTICATION, - ErrorType.NO_TABS_AVAILABLE -> - SyncedTabsAdapter.AdapterItem - .Error(descriptionResId = stringResId) - ErrorType.SYNC_UNAVAILABLE -> - SyncedTabsAdapter.AdapterItem - .Error(descriptionResId = stringResId, navController = navController) -} diff --git a/app/src/main/java/org/mozilla/fenix/sync/ext/SyncedTabsAdapter.kt b/app/src/main/java/org/mozilla/fenix/sync/ext/SyncedTabsAdapter.kt deleted file mode 100644 index 18c2c1be9..000000000 --- a/app/src/main/java/org/mozilla/fenix/sync/ext/SyncedTabsAdapter.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* 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.ext - -import mozilla.components.browser.storage.sync.SyncedDeviceTabs -import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem - -/** - * Converts a list of [SyncedDeviceTabs] into a list of [AdapterItem]. - */ -fun List.toAdapterList() = asSequence().flatMap { (device, tabs) -> - - val deviceTabs = if (tabs.isEmpty()) { - sequenceOf(AdapterItem.NoTabs(device)) - } else { - tabs.asSequence().map { AdapterItem.Tab(it) } - } - - sequenceOf(AdapterItem.Device(device)) + deviceTabs -}.toList() diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt deleted file mode 100644 index 60e1e99db..000000000 --- a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/SyncedTabsTrayLayout.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* 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.tabstray.syncedtabs - -import android.content.Context -import android.util.AttributeSet -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.fragment.app.findFragment -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import mozilla.components.browser.storage.sync.SyncedDeviceTabs -import mozilla.components.feature.syncedtabs.SyncedTabsFeature -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import mozilla.components.support.base.observer.Observable -import mozilla.components.support.base.observer.ObserverRegistry -import org.mozilla.fenix.databinding.ComponentSyncTabsTrayLayoutBinding -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.sync.SyncedTabsAdapter -import org.mozilla.fenix.sync.SyncedTabsTitleDecoration -import org.mozilla.fenix.sync.ext.toAdapterItem -import org.mozilla.fenix.sync.ext.toStringRes -import org.mozilla.fenix.tabstray.TabsTrayAction -import org.mozilla.fenix.tabstray.TabsTrayFragment -import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.utils.view.LifecycleViewProvider - -class SyncedTabsTrayLayout @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr), - SyncedTabsView, - Observable by ObserverRegistry() { - - private val lifecycleProvider = LifecycleViewProvider(this) - private val coroutineScope = CoroutineScope(Dispatchers.Main) - private var _binding: ComponentSyncTabsTrayLayoutBinding? = null - private val binding get() = _binding!! - - private val syncedTabsFeature by lazy { - SyncedTabsFeature( - context = context, - storage = context.components.backgroundServices.syncedTabsStorage, - accountManager = context.components.backgroundServices.accountManager, - view = this, - lifecycleOwner = lifecycleProvider, - onTabClicked = { - // We can ignore this callback here because we're not connecting the adapter - // back to the feature. This works fine in other features, but passing the listener - // to other components in this case is annoying. - } - ) - } - - private val syncButtonBinding by lazy { - SyncButtonBinding(tabsTrayStore) { - listener?.onRefresh() - } - } - - lateinit var tabsTrayStore: TabsTrayStore - - override var listener: SyncedTabsView.Listener? = null - - override fun onFinishInflate() { - _binding = ComponentSyncTabsTrayLayoutBinding.bind(this) - binding.syncedTabsList.addItemDecoration(SyncedTabsTitleDecoration(context)) - - super.onFinishInflate() - } - - override fun displaySyncedTabs(syncedTabs: List) { - coroutineScope.launch { - (binding.syncedTabsList.adapter as SyncedTabsAdapter).updateData(syncedTabs) - } - } - - override fun onError(error: SyncedTabsView.ErrorType) { - coroutineScope.launch { - // We may still be displaying a "loading" spinner, hide it. - stopLoading() - - val navController: NavController? = try { - findFragment().findNavController() - } catch (exception: IllegalStateException) { - null - } - - val descriptionResId = error.toStringRes() - val errorItem = error.toAdapterItem(descriptionResId, navController) - - val errorList: List = listOf(errorItem) - (binding.syncedTabsList.adapter as SyncedTabsAdapter).submitList(errorList) - } - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - syncedTabsFeature.start() - syncButtonBinding.start() - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - _binding = null - - syncedTabsFeature.stop() - syncButtonBinding.stop() - - coroutineScope.cancel() - } - - override fun stopLoading() { - tabsTrayStore.dispatch(TabsTrayAction.SyncCompleted) - } - - /** - * Do nothing; the UI is handled with FloatingActionButtonBinding. - */ - override fun startLoading() = Unit -} diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt b/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt deleted file mode 100644 index 53f93ab6b..000000000 --- a/app/src/main/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegate.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* 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.tabstray.syncedtabs - -import mozilla.components.browser.storage.sync.Tab -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import org.mozilla.fenix.tabstray.NavigationInteractor - -/** - * A wrapper class that handles tab clicks from a Synced Tabs list. - */ -class TabClickDelegate( - private val interactor: NavigationInteractor -) : SyncedTabsView.Listener { - override fun onTabClicked(tab: Tab) { - interactor.onSyncedTabClicked(tab) - } - - override fun onRefresh() = Unit -} diff --git a/app/src/main/res/anim/full_rotation.xml b/app/src/main/res/anim/full_rotation.xml deleted file mode 100644 index eefb51740..000000000 --- a/app/src/main/res/anim/full_rotation.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/component_sync_tabs_tray_layout.xml b/app/src/main/res/layout/component_sync_tabs_tray_layout.xml deleted file mode 100644 index 2399f39f4..000000000 --- a/app/src/main/res/layout/component_sync_tabs_tray_layout.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/sync_tabs_error_row.xml b/app/src/main/res/layout/sync_tabs_error_row.xml deleted file mode 100644 index 4b4a78ce6..000000000 --- a/app/src/main/res/layout/sync_tabs_error_row.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/sync_tabs_list_item.xml b/app/src/main/res/layout/sync_tabs_list_item.xml deleted file mode 100644 index 4a43737c1..000000000 --- a/app/src/main/res/layout/sync_tabs_list_item.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/view_synced_tabs_group.xml b/app/src/main/res/layout/view_synced_tabs_group.xml deleted file mode 100644 index 0746db267..000000000 --- a/app/src/main/res/layout/view_synced_tabs_group.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/view_synced_tabs_no_item.xml b/app/src/main/res/layout/view_synced_tabs_no_item.xml deleted file mode 100644 index 521c832ab..000000000 --- a/app/src/main/res/layout/view_synced_tabs_no_item.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/view_synced_tabs_title.xml b/app/src/main/res/layout/view_synced_tabs_title.xml deleted file mode 100644 index 80cfee5aa..000000000 --- a/app/src/main/res/layout/view_synced_tabs_title.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - diff --git a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsAdapterTest.kt deleted file mode 100644 index 0b5ea9cbb..000000000 --- a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsAdapterTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* 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.widget.FrameLayout -import io.mockk.every -import io.mockk.mockk -import mozilla.components.browser.storage.sync.SyncedDeviceTabs -import mozilla.components.browser.storage.sync.Tab -import mozilla.components.browser.storage.sync.TabEntry -import mozilla.components.concept.sync.DeviceType -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import mozilla.components.lib.publicsuffixlist.PublicSuffixList -import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class SyncedTabsAdapterTest { - - private lateinit var listener: SyncedTabsView.Listener - private lateinit var adapter: SyncedTabsAdapter - - private val oneTabDevice = SyncedDeviceTabs( - device = mockk { - every { displayName } returns "Charcoal" - every { deviceType } returns DeviceType.DESKTOP - }, - tabs = listOf( - Tab( - history = listOf( - TabEntry( - title = "Mozilla", - url = "https://mozilla.org", - iconUrl = null - ) - ), - active = 0, - lastUsed = 0L - ) - ) - ) - - private val threeTabDevice = SyncedDeviceTabs( - device = mockk { - every { displayName } returns "Emerald" - every { deviceType } returns DeviceType.MOBILE - }, - tabs = listOf( - Tab( - history = listOf( - TabEntry( - title = "Mozilla", - url = "https://mozilla.org", - iconUrl = null - ) - ), - active = 0, - lastUsed = 0L - ), - Tab( - history = listOf( - TabEntry( - title = "Firefox", - url = "https://firefox.com", - iconUrl = null - ) - ), - active = 0, - lastUsed = 0L - ) - ) - ) - - @Before - fun setup() { - listener = mockk(relaxed = true) - adapter = SyncedTabsAdapter(listener) - } - - @Test - fun `updateData() adds items for each device and tab`() { - assertEquals(0, adapter.itemCount) - - adapter.updateData( - listOf( - oneTabDevice, - threeTabDevice - ) - ) - - assertEquals(5, adapter.itemCount) - assertEquals(SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID, adapter.getItemViewType(0)) - assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(1)) - assertEquals(SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID, adapter.getItemViewType(2)) - assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(3)) - assertEquals(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, adapter.getItemViewType(4)) - } - - @Test - fun `adapter can create and bind viewholders for SyncedDeviceTabs`() { - every { testContext.components.publicSuffixList } returns PublicSuffixList(testContext) - val parent = FrameLayout(testContext) - adapter.updateData(listOf(oneTabDevice)) - - val deviceHolder = adapter.createViewHolder(parent, SyncedTabsViewHolder.DeviceViewHolder.LAYOUT_ID) - val tabHolder = adapter.createViewHolder(parent, SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID) - - // Should not throw - adapter.bindViewHolder(deviceHolder, 0) - adapter.bindViewHolder(tabHolder, 1) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsTitleDecorationTest.kt b/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsTitleDecorationTest.kt deleted file mode 100644 index 46a129eb6..000000000 --- a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsTitleDecorationTest.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* 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.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import io.mockk.Called -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.sync.SyncedTabsTitleDecoration.Style -import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder -import org.mozilla.fenix.sync.SyncedTabsViewHolder.ErrorViewHolder - -class SyncedTabsTitleDecorationTest { - - private val recyclerView: RecyclerView = mockk(relaxed = true) - private val canvas: Canvas = mockk(relaxed = true) - private val viewHolder: RecyclerView.ViewHolder = mockk(relaxed = true) - private val state: RecyclerView.State = mockk(relaxed = true) - private val view: View = mockk(relaxed = true) - - // Mocking these classes so we don't have to use the (slow) Android test runner. - private val rect: Rect = mockk(relaxed = true) - private val colorDrawable: Drawable = mockk(relaxed = true) - - private val style = Style(5, colorDrawable) - - @Before - fun setup() { - every { recyclerView.getChildViewHolder(any()) }.returns(viewHolder) - every { recyclerView.childCount }.returns(1) - every { recyclerView.getChildAt(any()) }.returns(view) - every { view.left }.returns(5) - every { view.top }.returns(5) - every { view.right }.returns(5) - every { view.bottom }.returns(5) - } - - @Test - fun `WHEN device title and not first item THEN add offset to the layout rect`() { - val decoration = SyncedTabsTitleDecoration(mockk(), style) - - every { viewHolder.itemViewType }.answers { DeviceViewHolder.LAYOUT_ID } - every { viewHolder.bindingAdapterPosition }.answers { 1 } - - decoration.getItemOffsets(rect, mockk(), recyclerView, state) - - verify { rect.set(0, 5, 0, 0) } - } - - @Test - fun `WHEN not device title and first position THEN do not add offsets`() { - val decoration = SyncedTabsTitleDecoration(mockk(), style) - - every { viewHolder.itemViewType }.answers { ErrorViewHolder.LAYOUT_ID } - every { viewHolder.bindingAdapterPosition } - .answers { 1 } - .andThenAnswer { 0 } - - decoration.getItemOffsets(rect, mockk(), recyclerView, state) - - decoration.getItemOffsets(rect, mockk(), recyclerView, state) - - verify(exactly = 2) { rect.setEmpty() } - } - - @Test - fun `WHEN device title and not first THEN draw`() { - val decoration = SyncedTabsTitleDecoration(mockk(), style) - - every { viewHolder.itemViewType }.answers { DeviceViewHolder.LAYOUT_ID } - every { viewHolder.bindingAdapterPosition }.answers { 1 } - - decoration.onDraw(canvas, recyclerView, state) - - verify { colorDrawable.setBounds(5, 0, 5, 5) } - verify { colorDrawable.draw(canvas) } - } - - @Test - fun `WHEN not device title and not first THEN do not draw`() { - val decoration = SyncedTabsTitleDecoration(mockk(), style) - - every { viewHolder.itemViewType }.answers { ErrorViewHolder.LAYOUT_ID } - every { viewHolder.bindingAdapterPosition } - .answers { 1 } - .andThenAnswer { 0 } - - decoration.onDraw(canvas, recyclerView, state) - - verify { colorDrawable wasNot Called } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsViewHolderTest.kt deleted file mode 100644 index 540ed1b5e..000000000 --- a/app/src/test/java/org/mozilla/fenix/sync/SyncedTabsViewHolderTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* 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.View -import android.widget.TextView -import io.mockk.Called -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import mozilla.components.browser.storage.sync.Tab -import mozilla.components.browser.storage.sync.TabEntry -import mozilla.components.concept.sync.Device -import mozilla.components.concept.sync.DeviceType -import mozilla.components.feature.syncedtabs.view.SyncedTabsView -import mozilla.components.lib.publicsuffixlist.PublicSuffixList -import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.R -import org.mozilla.fenix.databinding.SyncTabsListItemBinding -import org.mozilla.fenix.databinding.ViewSyncedTabsGroupBinding -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class SyncedTabsViewHolderTest { - - private lateinit var tabViewHolder: SyncedTabsViewHolder.TabViewHolder - private lateinit var tabView: View - private lateinit var deviceViewHolder: SyncedTabsViewHolder.DeviceViewHolder - private lateinit var deviceView: View - private lateinit var deviceViewGroupName: TextView - private lateinit var titleView: View - private lateinit var titleViewHolder: SyncedTabsViewHolder.TitleViewHolder - private lateinit var noTabsView: View - private lateinit var noTabsViewHolder: SyncedTabsViewHolder.NoTabsViewHolder - - private lateinit var syncTabsListItemBinding: SyncTabsListItemBinding - - private val tab = Tab( - history = listOf( - mockk(), - TabEntry( - title = "Firefox", - url = "https://mozilla.org/mobile", - iconUrl = "https://mozilla.org/favicon.ico" - ), - mockk() - ), - active = 1, - lastUsed = 0L - ) - - @Before - fun setup() { - val inflater = LayoutInflater.from(testContext) - - tabView = inflater.inflate(SyncedTabsViewHolder.TabViewHolder.LAYOUT_ID, null) - tabViewHolder = SyncedTabsViewHolder.TabViewHolder(tabView) - - syncTabsListItemBinding = SyncTabsListItemBinding.bind(tabView) - val viewSyncedTabsGroupBinding = ViewSyncedTabsGroupBinding.inflate(inflater) - deviceView = mockk() - - deviceViewHolder = SyncedTabsViewHolder.DeviceViewHolder(spyk(viewSyncedTabsGroupBinding.root)) - - deviceViewGroupName = spyk(viewSyncedTabsGroupBinding.syncedTabsGroupName) - - titleView = inflater.inflate(SyncedTabsViewHolder.TitleViewHolder.LAYOUT_ID, null) - titleViewHolder = SyncedTabsViewHolder.TitleViewHolder(titleView) - - noTabsView = inflater.inflate(SyncedTabsViewHolder.NoTabsViewHolder.LAYOUT_ID, null) - noTabsViewHolder = SyncedTabsViewHolder.NoTabsViewHolder(noTabsView) - } - - @Test - fun `TabViewHolder binds active tab`() { - every { testContext.components.publicSuffixList } returns PublicSuffixList(testContext) - tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), mockk()) - - assertEquals("Firefox", syncTabsListItemBinding.syncedTabItemTitle.text) - assertEquals("mozilla.org", syncTabsListItemBinding.syncedTabItemUrl.text) - } - - @Test - fun `TabViewHolder calls interactor on click`() { - every { testContext.components.publicSuffixList } returns PublicSuffixList(testContext) - val interactor = mockk(relaxed = true) - tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), interactor) - - tabView.performClick() - verify { interactor.onTabClicked(tab) } - } - - @Test - fun `DeviceViewHolder binds desktop device`() { - val device = mockk { - every { displayName } returns "Charcoal" - every { deviceType } returns DeviceType.DESKTOP - } - deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk()) - - assertEquals("Charcoal", deviceViewHolder.binding.syncedTabsGroupName.text) - } - - @Test - fun `DeviceViewHolder binds mobile device`() { - val device = mockk { - every { displayName } returns "Emerald" - every { deviceType } returns DeviceType.MOBILE - } - deviceViewHolder.bind(SyncedTabsAdapter.AdapterItem.Device(device), mockk()) - - assertEquals("Emerald", deviceViewHolder.binding.syncedTabsGroupName.text) - } - - @Test - fun `TitleViewHolder calls interactor refresh`() { - val interactor = mockk(relaxed = true) - titleViewHolder.bind(SyncedTabsAdapter.AdapterItem.Title, interactor) - - titleView.findViewById(R.id.refresh_icon).performClick() - - verify { interactor.onRefresh() } - } - - @Test - fun `NoTabsViewHolder does nothing`() { - val device = mockk { - every { displayName } returns "Charcoal" - every { deviceType } returns DeviceType.DESKTOP - } - val interactor = mockk(relaxed = true) - noTabsViewHolder.bind(SyncedTabsAdapter.AdapterItem.NoTabs(device), interactor) - - titleView.performClick() - - verify { interactor wasNot Called } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/sync/ext/ErrorTypeKtTest.kt b/app/src/test/java/org/mozilla/fenix/sync/ext/ErrorTypeKtTest.kt deleted file mode 100644 index e705fcf4f..000000000 --- a/app/src/test/java/org/mozilla/fenix/sync/ext/ErrorTypeKtTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* 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.ext - -import org.junit.Test -import androidx.navigation.NavController -import io.mockk.mockk -import mozilla.components.feature.syncedtabs.view.SyncedTabsView.ErrorType -import org.mozilla.fenix.R -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertNull -import org.junit.Assert.assertEquals - -class ErrorTypeKtTest { - - @Test - fun `string resource for error`() { - assertEquals( - R.string.synced_tabs_connect_another_device, - ErrorType.MULTIPLE_DEVICES_UNAVAILABLE.toStringRes() - ) - assertEquals( - R.string.synced_tabs_enable_tab_syncing, - ErrorType.SYNC_ENGINE_UNAVAILABLE.toStringRes() - ) - assertEquals( - R.string.synced_tabs_sign_in_message, - ErrorType.SYNC_UNAVAILABLE.toStringRes() - ) - assertEquals( - R.string.synced_tabs_reauth, - ErrorType.SYNC_NEEDS_REAUTHENTICATION.toStringRes() - ) - assertEquals( - R.string.synced_tabs_no_tabs, - ErrorType.NO_TABS_AVAILABLE.toStringRes() - ) - } - - @Test - fun `get error item`() { - val navController = mockk() - - var errorItem = ErrorType.MULTIPLE_DEVICES_UNAVAILABLE.toAdapterItem( - R.string.synced_tabs_connect_another_device, navController - ) - assertNull(errorItem.navController) - assertEquals(R.string.synced_tabs_connect_another_device, errorItem.descriptionResId) - - errorItem = ErrorType.SYNC_ENGINE_UNAVAILABLE.toAdapterItem( - R.string.synced_tabs_enable_tab_syncing, navController - ) - assertNull(errorItem.navController) - assertEquals(R.string.synced_tabs_enable_tab_syncing, errorItem.descriptionResId) - - errorItem = ErrorType.SYNC_NEEDS_REAUTHENTICATION.toAdapterItem( - R.string.synced_tabs_reauth, navController - ) - assertNull(errorItem.navController) - assertEquals(R.string.synced_tabs_reauth, errorItem.descriptionResId) - - errorItem = ErrorType.NO_TABS_AVAILABLE.toAdapterItem( - R.string.synced_tabs_no_tabs, navController - ) - assertNull(errorItem.navController) - assertEquals(R.string.synced_tabs_no_tabs, errorItem.descriptionResId) - - errorItem = ErrorType.SYNC_UNAVAILABLE.toAdapterItem( - R.string.synced_tabs_sign_in_message, navController - ) - assertNotNull(errorItem.navController) - assertEquals(R.string.synced_tabs_sign_in_message, errorItem.descriptionResId) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelperTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelperTest.kt index 05f110ce3..8e8861fe8 100644 --- a/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelperTest.kt +++ b/app/src/test/java/org/mozilla/fenix/tabstray/browser/TabsTouchHelperTest.kt @@ -4,7 +4,7 @@ package org.mozilla.fenix.tabstray.browser -import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE import androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags @@ -28,8 +28,8 @@ class TabsTouchHelperTest { @Test fun `movement flags remain unchanged if onSwipeToDelete is true`() { val recyclerView = RecyclerView(testContext) - val layout = FrameLayout(testContext) - val viewHolder = SyncedTabsPageViewHolder(layout, mockk()) + val layout = ComposeView(testContext) + val viewHolder = SyncedTabsPageViewHolder(layout, mockk(), mockk()) val callback = TouchCallback(mockk(), { true }, { false }, featureName) assertEquals(0, callback.getDragDirs(recyclerView, viewHolder)) @@ -47,8 +47,8 @@ class TabsTouchHelperTest { @Test fun `movement flags remain unchanged if onSwipeToDelete is false`() { val recyclerView = RecyclerView(testContext) - val layout = FrameLayout(testContext) - val viewHolder = SyncedTabsPageViewHolder(layout, mockk()) + val layout = ComposeView(testContext) + val viewHolder = SyncedTabsPageViewHolder(layout, mockk(), mockk()) val callback = TouchCallback(mockk(), { false }, { false }, featureName) assertEquals(0, callback.getDragDirs(recyclerView, viewHolder)) diff --git a/app/src/test/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegateTest.kt b/app/src/test/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegateTest.kt deleted file mode 100644 index 58375c596..000000000 --- a/app/src/test/java/org/mozilla/fenix/tabstray/syncedtabs/TabClickDelegateTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* 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.tabstray.syncedtabs - -import io.mockk.Called -import io.mockk.mockk -import io.mockk.verify -import mozilla.components.browser.storage.sync.Tab -import org.junit.Test -import org.mozilla.fenix.tabstray.NavigationInteractor - -class TabClickDelegateTest { - - private val interactor = mockk(relaxed = true) - private val tab = mockk() - - @Test - fun `WHEN tab is clicked THEN invoke the interactor`() { - val delegate = TabClickDelegate(interactor) - - delegate.onTabClicked(tab) - - verify { interactor.onSyncedTabClicked(tab) } - } - - @Test - fun `WHEN refresh is invoked THEN do nothing`() { - val delegate = TabClickDelegate(interactor) - - delegate.onRefresh() - - verify { interactor wasNot Called } - } -}