[fenix] For https://github.com/mozilla-mobile/fenix/issues/21900 - Delete files from Synced Tabs XML implementation
parent
fbf1799ffa
commit
71f539bf4e
@ -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<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)
|
||||
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<SyncedDeviceTabs>) {
|
||||
val allDeviceTabs = syncedTabs.toAdapterList()
|
||||
submitList(allDeviceTabs)
|
||||
}
|
||||
|
||||
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||
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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <T : AdapterItem> bind(item: T, interactor: SyncedTabsView.Listener)
|
||||
|
||||
class TabViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||
override fun <T : AdapterItem> 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 <T : AdapterItem> 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 <T : AdapterItem> 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 <T : AdapterItem> 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 <T : AdapterItem> 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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<SyncedDeviceTabs>.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()
|
@ -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<SyncedTabsView.Listener> 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<SyncedDeviceTabs>) {
|
||||
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<TabsTrayFragment>().findNavController()
|
||||
} catch (exception: IllegalStateException) {
|
||||
null
|
||||
}
|
||||
|
||||
val descriptionResId = error.toStringRes()
|
||||
val errorItem = error.toAdapterItem(descriptionResId, navController)
|
||||
|
||||
val errorList: List<SyncedTabsAdapter.AdapterItem> = 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
|
||||
}
|
@ -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
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?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/. -->
|
||||
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="275"
|
||||
android:fromDegrees="0"
|
||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="360" />
|
@ -1,20 +0,0 @@
|
||||
<?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.tabstray.syncedtabs.SyncedTabsTrayLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/synced_tabs_tray_layout"
|
||||
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"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/tab_tray_list_bottom_padding"
|
||||
tools:listitem="@layout/sync_tabs_list_item"/>
|
||||
|
||||
</org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsTrayLayout>
|
@ -1,35 +0,0 @@
|
||||
<?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/. -->
|
||||
<LinearLayout 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:background="@drawable/empty_session_control_background"
|
||||
android:layout_margin="@dimen/synced_tabs_error_margin"
|
||||
android:padding="16dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sync_tabs_error_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/fx_mobile_text_color_primary"
|
||||
tools:text="@string/synced_tabs_no_tabs"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/sync_tabs_error_cta_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
style="@style/PositiveButton"
|
||||
app:icon="@drawable/ic_sign_in"
|
||||
android:visibility="gone"
|
||||
android:text="@string/synced_tabs_sign_in_button"
|
||||
android:layout_marginTop="8dp"/>
|
||||
</LinearLayout>
|
@ -1,46 +0,0 @@
|
||||
<?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:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/fx_mobile_text_color_primary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Tab Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/synced_tab_item_url"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
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" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,31 +0,0 @@
|
||||
<?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/synced_tabs_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/synced_tabs_group_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textAppearance="@style/Header14TextStyle"
|
||||
android:textColor="@color/fx_mobile_text_color_primary"
|
||||
android:textSize="12sp"
|
||||
android:letterSpacing="0.04"
|
||||
android:textDirection="locale"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Header" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,21 +0,0 @@
|
||||
<?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/. -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="7dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/synced_tabs_no_open_tabs"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</FrameLayout>
|
@ -1,33 +0,0 @@
|
||||
<?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/. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/Header16TextStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/synced_tabs" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/refresh_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:padding="12dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/resync_button_content_description"
|
||||
app:srcCompat="@drawable/mozac_ic_refresh"
|
||||
app:tint="?primaryText" />
|
||||
|
||||
</LinearLayout>
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
@ -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<SyncedTabsView.Listener>(relaxed = true)
|
||||
tabViewHolder.bind(SyncedTabsAdapter.AdapterItem.Tab(tab), interactor)
|
||||
|
||||
tabView.performClick()
|
||||
verify { interactor.onTabClicked(tab) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeviceViewHolder binds desktop device`() {
|
||||
val device = mockk<Device> {
|
||||
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<Device> {
|
||||
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<SyncedTabsView.Listener>(relaxed = true)
|
||||
titleViewHolder.bind(SyncedTabsAdapter.AdapterItem.Title, interactor)
|
||||
|
||||
titleView.findViewById<View>(R.id.refresh_icon).performClick()
|
||||
|
||||
verify { interactor.onRefresh() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NoTabsViewHolder does nothing`() {
|
||||
val device = mockk<Device> {
|
||||
every { displayName } returns "Charcoal"
|
||||
every { deviceType } returns DeviceType.DESKTOP
|
||||
}
|
||||
val interactor = mockk<SyncedTabsView.Listener>(relaxed = true)
|
||||
noTabsViewHolder.bind(SyncedTabsAdapter.AdapterItem.NoTabs(device), interactor)
|
||||
|
||||
titleView.performClick()
|
||||
|
||||
verify { interactor wasNot Called }
|
||||
}
|
||||
}
|
@ -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<NavController>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<NavigationInteractor>(relaxed = true)
|
||||
private val tab = mockk<Tab>()
|
||||
|
||||
@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 }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue