For #10834 - Adding Sync Tabs Feature in Fenix
parent
0300f15df1
commit
4da22c605a
@ -0,0 +1,60 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import mozilla.components.concept.sync.Device as SyncDevice
|
||||||
|
import mozilla.components.browser.storage.sync.Tab as SyncTab
|
||||||
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.DeviceViewHolder
|
||||||
|
import org.mozilla.fenix.sync.SyncedTabsViewHolder.TabViewHolder
|
||||||
|
|
||||||
|
class SyncedTabsAdapter(
|
||||||
|
private val listener: (SyncTab) -> Unit
|
||||||
|
) : ListAdapter<SyncedTabsAdapter.AdapterItem, SyncedTabsViewHolder>(
|
||||||
|
DiffCallback
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SyncedTabsViewHolder {
|
||||||
|
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
DeviceViewHolder.LAYOUT_ID -> DeviceViewHolder(itemView)
|
||||||
|
TabViewHolder.LAYOUT_ID -> TabViewHolder(itemView)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SyncedTabsViewHolder, position: Int) {
|
||||||
|
val item = when (holder) {
|
||||||
|
is DeviceViewHolder -> getItem(position) as AdapterItem.Device
|
||||||
|
is TabViewHolder -> getItem(position) as AdapterItem.Tab
|
||||||
|
}
|
||||||
|
holder.bind(item, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return when (getItem(position)) {
|
||||||
|
is AdapterItem.Device -> DeviceViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Tab -> TabViewHolder.LAYOUT_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
areContentsTheSame(oldItem, newItem)
|
||||||
|
|
||||||
|
@Suppress("DiffUtilEquals")
|
||||||
|
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class AdapterItem {
|
||||||
|
data class Device(val device: SyncDevice) : AdapterItem()
|
||||||
|
data class Tab(val tab: SyncTab) : AdapterItem()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.fragment_synced_tabs.*
|
||||||
|
import mozilla.components.browser.storage.sync.Tab
|
||||||
|
import mozilla.components.feature.syncedtabs.SyncedTabsFeature
|
||||||
|
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.showToolbar
|
||||||
|
import org.mozilla.fenix.library.LibraryPageFragment
|
||||||
|
|
||||||
|
class SyncedTabsFragment : LibraryPageFragment<Tab>() {
|
||||||
|
private val syncedTabsFeature = ViewBoundFeatureWrapper<SyncedTabsFeature>()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_synced_tabs, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val backgroundServices = requireContext().components.backgroundServices
|
||||||
|
|
||||||
|
syncedTabsFeature.set(
|
||||||
|
feature = SyncedTabsFeature(
|
||||||
|
storage = backgroundServices.syncedTabsStorage,
|
||||||
|
accountManager = backgroundServices.accountManager,
|
||||||
|
view = synced_tabs_layout,
|
||||||
|
lifecycleOwner = this.viewLifecycleOwner,
|
||||||
|
onTabClicked = ::handleTabClicked
|
||||||
|
),
|
||||||
|
owner = this,
|
||||||
|
view = view
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
showToolbar(getString(R.string.library_synced_tabs))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTabClicked(tab: Tab) {
|
||||||
|
|
||||||
|
(activity as HomeActivity).openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = tab.active().url,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromSyncedTabs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val selectedItems: Set<Tab>
|
||||||
|
get() = emptySet()
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import mozilla.components.concept.sync.AccountObserver
|
||||||
|
import mozilla.components.concept.sync.AuthType
|
||||||
|
import mozilla.components.concept.sync.OAuthAccount
|
||||||
|
import mozilla.components.service.fxa.manager.FxaAccountManager
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
|
||||||
|
class SyncedTabsIntegration(
|
||||||
|
private val context: Context,
|
||||||
|
private val accountManager: FxaAccountManager
|
||||||
|
) {
|
||||||
|
fun launch() {
|
||||||
|
val accountObserver = SyncedTabsAccountObserver(context)
|
||||||
|
|
||||||
|
accountManager.register(
|
||||||
|
accountObserver,
|
||||||
|
owner = ProcessLifecycleOwner.get(),
|
||||||
|
autoPause = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SyncedTabsAccountObserver(private val context: Context) : AccountObserver {
|
||||||
|
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
|
||||||
|
context.components.backgroundServices.syncedTabsStorage.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoggedOut() {
|
||||||
|
context.components.backgroundServices.syncedTabsStorage.stop()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import kotlinx.android.synthetic.main.component_sync_tabs.view.*
|
||||||
|
import mozilla.components.browser.storage.sync.SyncedDeviceTabs
|
||||||
|
import mozilla.components.feature.syncedtabs.view.SyncedTabsView
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
class SyncedTabsLayout @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : FrameLayout(context, attrs, defStyleAttr), SyncedTabsView {
|
||||||
|
|
||||||
|
override var listener: SyncedTabsView.Listener? = null
|
||||||
|
|
||||||
|
private val adapter = SyncedTabsAdapter { listener?.onTabClicked(it) }
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(getContext(), R.layout.component_sync_tabs, this)
|
||||||
|
|
||||||
|
synced_tabs_list.layoutManager = LinearLayoutManager(context)
|
||||||
|
synced_tabs_list.adapter = adapter
|
||||||
|
|
||||||
|
synced_tabs_pull_to_refresh.setOnRefreshListener { listener?.onRefresh() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(error: SyncedTabsView.ErrorType) {
|
||||||
|
val stringResId = when (error) {
|
||||||
|
SyncedTabsView.ErrorType.MULTIPLE_DEVICES_UNAVAILABLE -> R.string.synced_tabs_connect_another_device
|
||||||
|
SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing
|
||||||
|
SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account
|
||||||
|
SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_tabs_status.text = context.getText(stringResId)
|
||||||
|
|
||||||
|
synced_tabs_list.visibility = View.GONE
|
||||||
|
sync_tabs_status.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun displaySyncedTabs(syncedTabs: List<SyncedDeviceTabs>) {
|
||||||
|
synced_tabs_list.visibility = View.VISIBLE
|
||||||
|
sync_tabs_status.visibility = View.GONE
|
||||||
|
|
||||||
|
val allDeviceTabs = emptyList<SyncedTabsAdapter.AdapterItem>().toMutableList()
|
||||||
|
|
||||||
|
syncedTabs.forEach { (device, tabs) ->
|
||||||
|
if (tabs.isEmpty()) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceTabs = tabs.map { SyncedTabsAdapter.AdapterItem.Tab(it) }
|
||||||
|
|
||||||
|
allDeviceTabs += listOf(SyncedTabsAdapter.AdapterItem.Device(device)) + deviceTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.submitList(allDeviceTabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startLoading() {
|
||||||
|
synced_tabs_list.visibility = View.VISIBLE
|
||||||
|
sync_tabs_status.visibility = View.GONE
|
||||||
|
|
||||||
|
synced_tabs_pull_to_refresh.isRefreshing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopLoading() {
|
||||||
|
synced_tabs_pull_to_refresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.sync
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.sync_tabs_list_item.view.*
|
||||||
|
import kotlinx.android.synthetic.main.view_synced_tabs_group.view.*
|
||||||
|
import mozilla.components.browser.storage.sync.Tab
|
||||||
|
import mozilla.components.concept.sync.DeviceType
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.sync.SyncedTabsAdapter.AdapterItem
|
||||||
|
|
||||||
|
sealed class SyncedTabsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
abstract fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit)
|
||||||
|
|
||||||
|
class TabViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||||
|
|
||||||
|
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||||
|
bindTab(item as AdapterItem.Tab)
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
interactor(item.tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindTab(tab: AdapterItem.Tab) {
|
||||||
|
val active = tab.tab.active()
|
||||||
|
itemView.synced_tab_item_title.text = active.title
|
||||||
|
itemView.synced_tab_item_url.text = active.url
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.sync_tabs_list_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeviceViewHolder(itemView: View) : SyncedTabsViewHolder(itemView) {
|
||||||
|
|
||||||
|
override fun <T : AdapterItem> bind(item: T, interactor: (Tab) -> Unit) {
|
||||||
|
bindHeader(item as AdapterItem.Device)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindHeader(device: AdapterItem.Device) {
|
||||||
|
|
||||||
|
val deviceLogoDrawable = when (device.device.deviceType) {
|
||||||
|
DeviceType.DESKTOP -> { R.drawable.mozac_ic_device_desktop }
|
||||||
|
else -> { R.drawable.mozac_ic_device_mobile }
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.synced_tabs_group_name.text = device.device.displayName
|
||||||
|
itemView.synced_tabs_group_name.setCompoundDrawablesWithIntrinsicBounds(deviceLogoDrawable, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.view_synced_tabs_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/history_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/sync_tabs_progress_bar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:translationY="-3dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sync_tabs_status"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/sync_connect_device"
|
||||||
|
android:textColor="?secondaryText"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/synced_tabs_pull_to_refresh"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/synced_tabs_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/history_list_item"/>
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<org.mozilla.fenix.sync.SyncedTabsLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/synced_tabs_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="60dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:minHeight="@dimen/library_item_height"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/synced_tab_item_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?primaryText"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/synced_tab_item_url"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="Tab Title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/synced_tab_item_url"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textColor="?secondaryText"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="https://example.com/"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/synced_tab_item_title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/synced_tab_item_separator"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:background="?neutralFaded"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/synced_tabs_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/synced_tabs_group_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="15dp"
|
||||||
|
app:drawableStartCompat="@drawable/mozac_ic_device_desktop"
|
||||||
|
app:drawableTint="?primaryText"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textAppearance="@style/Header14TextStyle"
|
||||||
|
android:textColor="?primaryText"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
tools:text="Header" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
Loading…
Reference in New Issue