For #11498 - add Sync tabs error view (including sign-in CTA)
parent
a11277079a
commit
bfc955cd40
|
@ -6,10 +6,13 @@ package org.mozilla.fenix.sync
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.navigation.NavController
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
||||||
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.ErrorViewHolder
|
||||||
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.SignInViewHolder
|
||||||
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
||||||
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
||||||
import mozilla.components.concept.sync.Device as SyncDevice
|
import mozilla.components.concept.sync.Device as SyncDevice
|
||||||
|
@ -24,6 +27,8 @@ class SyncedTabsAdapter(
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
DeviceViewHolder.LAYOUT_ID -> DeviceViewHolder(itemView)
|
DeviceViewHolder.LAYOUT_ID -> DeviceViewHolder(itemView)
|
||||||
TabViewHolder.LAYOUT_ID -> TabViewHolder(itemView)
|
TabViewHolder.LAYOUT_ID -> TabViewHolder(itemView)
|
||||||
|
ErrorViewHolder.LAYOUT_ID -> ErrorViewHolder(itemView)
|
||||||
|
SignInViewHolder.LAYOUT_ID -> SignInViewHolder(itemView)
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +40,8 @@ class SyncedTabsAdapter(
|
||||||
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
||||||
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||||
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Error -> ErrorViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.SignIn -> SignInViewHolder.LAYOUT_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateData(syncedTabs: List<SyncedDeviceTabs>) {
|
fun updateData(syncedTabs: List<SyncedDeviceTabs>) {
|
||||||
|
@ -55,7 +62,7 @@ class SyncedTabsAdapter(
|
||||||
when (oldItem) {
|
when (oldItem) {
|
||||||
is AdapterItem.Device ->
|
is AdapterItem.Device ->
|
||||||
newItem is AdapterItem.Device && oldItem.device.id == newItem.device.id
|
newItem is AdapterItem.Device && oldItem.device.id == newItem.device.id
|
||||||
is AdapterItem.Tab ->
|
is AdapterItem.Tab, AdapterItem.Error, AdapterItem.SignIn ->
|
||||||
oldItem == newItem
|
oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,5 +74,7 @@ class SyncedTabsAdapter(
|
||||||
sealed class AdapterItem {
|
sealed class AdapterItem {
|
||||||
data class Device(val device: SyncDevice) : AdapterItem()
|
data class Device(val device: SyncDevice) : AdapterItem()
|
||||||
data class Tab(val tab: SyncTab) : AdapterItem()
|
data class Tab(val tab: SyncTab) : AdapterItem()
|
||||||
|
data class SignIn(val navController: NavController) : AdapterItem()
|
||||||
|
data class Error(val errorResId: Int) : AdapterItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ package org.mozilla.fenix.sync
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.fragment.app.findFragment
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
|
@ -5,11 +5,17 @@
|
||||||
package org.mozilla.fenix.sync
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.no_content_message_with_action.view.*
|
||||||
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
|
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
|
||||||
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
|
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
|
||||||
import mozilla.components.browser.storage.sync.Tab
|
import mozilla.components.browser.storage.sync.Tab
|
||||||
import mozilla.components.concept.sync.DeviceType
|
import mozilla.components.concept.sync.DeviceType
|
||||||
|
import mozilla.components.support.ktx.android.util.dpToPx
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
|
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
|
||||||
|
|
||||||
|
@ -38,6 +44,44 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignInViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||||
|
|
||||||
|
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||||
|
val signInItem = item as AdapterItem.SignIn
|
||||||
|
setErrorMargins()
|
||||||
|
|
||||||
|
itemView.no_content_header.visibility = GONE
|
||||||
|
itemView.no_content_description.text =
|
||||||
|
itemView.context.getString(R.string.synced_tabs_sign_in_message)
|
||||||
|
itemView.no_content_button.text =
|
||||||
|
itemView.context.getString(R.string.synced_tabs_sign_in_button)
|
||||||
|
itemView.no_content_button.icon =
|
||||||
|
ContextCompat.getDrawable(itemView.context, R.drawable.ic_sign_in)
|
||||||
|
itemView.no_content_button.setOnClickListener {
|
||||||
|
signInItem.navController.navigate(NavGraphDirections.actionGlobalTurnOnSync())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.no_content_message_with_action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||||
|
|
||||||
|
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||||
|
val errorItem = item as AdapterItem.Error
|
||||||
|
setErrorMargins()
|
||||||
|
|
||||||
|
itemView.no_content_header.visibility = GONE
|
||||||
|
itemView.no_content_description.text = itemView.context.getString(errorItem.errorResId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.no_content_message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||||
|
|
||||||
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||||
|
@ -45,7 +89,6 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindHeader(device: AdapterItem.Device) {
|
private fun bindHeader(device: AdapterItem.Device) {
|
||||||
|
|
||||||
val deviceLogoDrawable = when (device.device.deviceType) {
|
val deviceLogoDrawable = when (device.device.deviceType) {
|
||||||
DeviceType.DESKTOP -> R.drawable.mozac_ic_device_desktop
|
DeviceType.DESKTOP -> R.drawable.mozac_ic_device_desktop
|
||||||
else -> R.drawable.mozac_ic_device_mobile
|
else -> R.drawable.mozac_ic_device_mobile
|
||||||
|
@ -59,4 +102,19 @@ sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(item
|
||||||
const val LAYOUT_ID = R.layout.view_synced_tabs_group
|
const val LAYOUT_ID = R.layout.view_synced_tabs_group
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun setErrorMargins() {
|
||||||
|
val lp = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
val displayMetrics = itemView.context.resources.displayMetrics
|
||||||
|
val margin = ERROR_MARGIN.dpToPx(displayMetrics)
|
||||||
|
lp.setMargins(margin, margin, margin, 0)
|
||||||
|
itemView.layoutParams = lp
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ERROR_MARGIN = 20
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,20 +20,6 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/synced_tabs_pull_to_refresh"
|
android:id="@+id/synced_tabs_pull_to_refresh"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1432,8 +1432,6 @@
|
||||||
<string name="saved_login_duplicate">A login with that username already exists</string>
|
<string name="saved_login_duplicate">A login with that username already exists</string>
|
||||||
|
|
||||||
<!-- Synced Tabs -->
|
<!-- 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 -->
|
<!-- 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>
|
<string name="synced_tabs_connect_another_device">Connect another device.</string>
|
||||||
<!-- Text displayed asking user to re-authenticate -->
|
<!-- Text displayed asking user to re-authenticate -->
|
||||||
|
|
Loading…
Reference in New Issue