* For #1574: Adds collections to home view * Adds colored icons and expansion * Adds state change * Adds more styling * Adds ItsNotBrokenSnacks * Adds chevron * Improves styling of swipe to delete and adds delete action * Fix nits * Try to add real savingmaster
parent
282ad31345
commit
7d577e5953
|
@ -60,7 +60,6 @@ import org.mozilla.fenix.R
|
|||
import org.mozilla.fenix.collections.CreateCollectionFragment
|
||||
import org.mozilla.fenix.collections.CreateCollectionViewModel
|
||||
import org.mozilla.fenix.collections.SaveCollectionStep
|
||||
import org.mozilla.fenix.collections.Tab
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.components.FindInPageIntegration
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
|
@ -75,7 +74,8 @@ import org.mozilla.fenix.customtabs.CustomTabsIntegration
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.share
|
||||
import org.mozilla.fenix.ext.urlToHost
|
||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.lib.Do
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
|
@ -606,7 +606,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope,
|
|||
|
||||
private fun showSaveToCollection() {
|
||||
getSessionById()?.let {
|
||||
val tabs = Tab(it.id, it.url, it.url.urlToHost(), it.title)
|
||||
val tabs = Tab(it.id, it.url, it.url.urlToTrimmedHost(), it.title)
|
||||
val viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ package org.mozilla.fenix.collections
|
|||
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.mvi.Action
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.Change
|
||||
|
@ -12,18 +14,6 @@ import org.mozilla.fenix.mvi.Reducer
|
|||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
data class Tab(
|
||||
val sessionId: String,
|
||||
val url: String,
|
||||
val hostname: String,
|
||||
val title: String
|
||||
)
|
||||
|
||||
data class Collection(
|
||||
val collectionId: String,
|
||||
val title: String
|
||||
)
|
||||
|
||||
sealed class SaveCollectionStep {
|
||||
object SelectTabs : SaveCollectionStep()
|
||||
object SelectCollection : SaveCollectionStep()
|
||||
|
@ -55,7 +45,7 @@ sealed class CollectionCreationAction : Action {
|
|||
data class SaveCollectionName(val tabs: List<Tab>, val name: String) :
|
||||
CollectionCreationAction()
|
||||
|
||||
data class SelectCollection(val collection: Collection) :
|
||||
data class SelectCollection(val collection: TabCollection) :
|
||||
CollectionCreationAction()
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.launch
|
|||
import mozilla.components.browser.icons.IconRequest
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class CollectionCreationTabListAdapter(
|
||||
|
|
|
@ -25,6 +25,7 @@ import mozilla.components.support.ktx.android.view.hideKeyboard
|
|||
import mozilla.components.support.ktx.android.view.showKeyboard
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.increaseTapArea
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
|
||||
class CollectionCreationUIView(
|
||||
|
|
|
@ -17,11 +17,15 @@ import kotlinx.android.synthetic.main.fragment_create_collection.view.*
|
|||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import org.mozilla.fenix.ext.getRootView
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
import java.util.Random
|
||||
|
||||
class CreateCollectionFragment : DialogFragment() {
|
||||
// Temporary callback. In the future we will just directly add the collection to the core session manager.
|
||||
var onCollectionSaved: ((TabCollection) -> Unit)? = null
|
||||
private lateinit var collectionCreationComponent: CollectionCreationComponent
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -91,6 +95,8 @@ class CreateCollectionFragment : DialogFragment() {
|
|||
is CollectionCreationAction.SaveCollectionName -> {
|
||||
showSavedSnackbar(it.tabs.size)
|
||||
dismiss()
|
||||
val newCollection = TabCollection(Random().nextInt(), it.name, it.tabs.toMutableList())
|
||||
onCollectionSaved?.invoke(newCollection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package org.mozilla.fenix.collections
|
|||
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
|
||||
class CreateCollectionViewModel : ViewModel() {
|
||||
var selectedTabs = setOf<Tab>()
|
||||
|
|
|
@ -14,13 +14,14 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SaveCollectionListAdapter(
|
||||
val actionEmitter: Observer<CollectionCreationAction>
|
||||
) : RecyclerView.Adapter<CollectionViewHolder>() {
|
||||
|
||||
private var collections: List<Collection> = listOf()
|
||||
private var collections: List<TabCollection> = listOf()
|
||||
private lateinit var job: Job
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CollectionViewHolder {
|
||||
|
@ -58,7 +59,7 @@ class CollectionViewHolder(
|
|||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
private var collection: Collection? = null
|
||||
private var collection: TabCollection? = null
|
||||
|
||||
private val listener = View.OnClickListener {
|
||||
collection?.apply {
|
||||
|
@ -71,7 +72,7 @@ class CollectionViewHolder(
|
|||
view.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
fun bind(collection: Collection) {
|
||||
fun bind(collection: TabCollection) {
|
||||
this.collection = collection
|
||||
view.collection_item.text = collection.title
|
||||
}
|
||||
|
|
|
@ -26,3 +26,20 @@ fun String?.urlToHost(): String {
|
|||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun String?.urlToTrimmedHost(): String {
|
||||
return try {
|
||||
val url = URL(this)
|
||||
val firstIndex = url.host.indexOfFirst { it == '.' } + 1
|
||||
val lastIndex = url.host.indexOfLast { it == '.' }
|
||||
|
||||
// Trim all but the title of the website from the hostname. 'www.mozilla.org' becomes 'mozilla'
|
||||
when {
|
||||
firstIndex - 1 == lastIndex -> url.host.substring(0, lastIndex)
|
||||
firstIndex < lastIndex -> url.host.substring(firstIndex, lastIndex)
|
||||
else -> url.host
|
||||
}
|
||||
} catch (e: MalformedURLException) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,18 +38,20 @@ import org.mozilla.fenix.R
|
|||
import org.mozilla.fenix.collections.CreateCollectionFragment
|
||||
import org.mozilla.fenix.collections.CreateCollectionViewModel
|
||||
import org.mozilla.fenix.collections.SaveCollectionStep
|
||||
import org.mozilla.fenix.collections.Tab
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.allowUndo
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.share
|
||||
import org.mozilla.fenix.ext.urlToHost
|
||||
import org.mozilla.fenix.ext.urlToTrimmedHost
|
||||
import org.mozilla.fenix.home.sessioncontrol.Mode
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlChange
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlComponent
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlState
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.CollectionAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.lib.Do
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
|
@ -73,6 +75,9 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + job
|
||||
|
||||
// TODO Remove this stub when we have the a-c version!
|
||||
var storedCollections = mutableListOf<TabCollection>()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -80,12 +85,12 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
): View? {
|
||||
job = Job()
|
||||
val view = inflater.inflate(R.layout.fragment_home, container, false)
|
||||
val mode =
|
||||
if ((activity as HomeActivity).browsingModeManager.isPrivate) Mode.Private else Mode.Normal
|
||||
val mode = if ((activity as HomeActivity).browsingModeManager.isPrivate) Mode.Private else Mode.Normal
|
||||
|
||||
sessionControlComponent = SessionControlComponent(
|
||||
view.homeLayout,
|
||||
bus,
|
||||
SessionControlState(listOf(), mode)
|
||||
SessionControlState(listOf(), listOf(), mode)
|
||||
)
|
||||
|
||||
view.homeLayout.applyConstraintSet {
|
||||
|
@ -178,6 +183,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
.subscribe {
|
||||
when (it) {
|
||||
is SessionControlAction.Tab -> handleTabAction(it.action)
|
||||
is SessionControlAction.Collection -> handleCollectionAction(it.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,6 +250,44 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleCollectionAction(action: CollectionAction) {
|
||||
when (action) {
|
||||
is CollectionAction.Expand -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.apply { expanded = true }
|
||||
}
|
||||
is CollectionAction.Collapse -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.apply { expanded = false }
|
||||
}
|
||||
is CollectionAction.Delete -> {
|
||||
storedCollections.find { it.id == action.collection.id }?.let { storedCollections.remove(it) }
|
||||
}
|
||||
is CollectionAction.AddTab -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1575")
|
||||
}
|
||||
is CollectionAction.Rename -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1575")
|
||||
}
|
||||
is CollectionAction.OpenTabs -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "2205")
|
||||
}
|
||||
is CollectionAction.ShareTabs -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1585")
|
||||
}
|
||||
is CollectionAction.RemoveTab -> {
|
||||
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "1578")
|
||||
}
|
||||
}
|
||||
|
||||
emitCollectionChange()
|
||||
}
|
||||
|
||||
private fun emitCollectionChange() {
|
||||
storedCollections.map { it.copy() }.let {
|
||||
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.CollectionsChange(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
sessionObserver?.let {
|
||||
|
@ -326,7 +370,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
org.mozilla.fenix.home.sessioncontrol.Tab(
|
||||
it.id,
|
||||
it.url,
|
||||
it.url.urlToHost(),
|
||||
it.url.urlToTrimmedHost(),
|
||||
it.title,
|
||||
selected,
|
||||
it.thumbnail
|
||||
|
@ -364,7 +408,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
org.mozilla.fenix.home.sessioncontrol.Tab(
|
||||
it.id,
|
||||
it.url,
|
||||
it.url.urlToHost(),
|
||||
it.url.urlToTrimmedHost(),
|
||||
it.title,
|
||||
selected,
|
||||
it.thumbnail
|
||||
|
@ -376,7 +420,7 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
|
||||
private fun showCollectionCreationFragment(selectedTabId: String?) {
|
||||
val tabs = requireComponents.core.sessionManager.sessions
|
||||
.map { Tab(it.id, it.url, it.url.urlToHost(), it.title) }
|
||||
.map { Tab(it.id, it.url, it.url.urlToTrimmedHost(), it.title) }
|
||||
|
||||
val viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(CreateCollectionViewModel::class.java)
|
||||
|
@ -387,11 +431,17 @@ class HomeFragment : Fragment(), CoroutineScope {
|
|||
viewModel?.selectedTabs = selectedSet
|
||||
viewModel?.saveCollectionStep = SaveCollectionStep.SelectTabs
|
||||
|
||||
CreateCollectionFragment()
|
||||
.show(
|
||||
CreateCollectionFragment().also {
|
||||
it.onCollectionSaved = {
|
||||
storedCollections.add(it)
|
||||
emitCollectionChange()
|
||||
}
|
||||
|
||||
it.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
CreateCollectionFragment.createCollectionTag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -15,6 +15,10 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.NoTabMessageViewHolder
|
|||
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabHeaderViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionMessageViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
sealed class AdapterItem {
|
||||
|
@ -24,6 +28,10 @@ sealed class AdapterItem {
|
|||
object PrivateBrowsingDescription : AdapterItem()
|
||||
object SaveTabGroup : AdapterItem()
|
||||
object DeleteTabs : AdapterItem()
|
||||
object CollectionHeader : AdapterItem()
|
||||
object NoCollectionMessage : AdapterItem()
|
||||
data class CollectionItem(val collection: TabCollection) : AdapterItem()
|
||||
data class TabInCollectionItem(val collection: TabCollection, val tab: Tab, val isLastTab: Boolean) : AdapterItem()
|
||||
|
||||
val viewType: Int
|
||||
get() = when (this) {
|
||||
|
@ -33,6 +41,10 @@ sealed class AdapterItem {
|
|||
SaveTabGroup -> SaveTabGroupViewHolder.LAYOUT_ID
|
||||
PrivateBrowsingDescription -> PrivateBrowsingDescriptionViewHolder.LAYOUT_ID
|
||||
DeleteTabs -> DeleteTabsViewHolder.LAYOUT_ID
|
||||
CollectionHeader -> CollectionHeaderViewHolder.LAYOUT_ID
|
||||
NoCollectionMessage -> NoCollectionMessageViewHolder.LAYOUT_ID
|
||||
is CollectionItem -> CollectionViewHolder.LAYOUT_ID
|
||||
is TabInCollectionItem -> TabInCollectionViewHolder.LAYOUT_ID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +74,12 @@ class SessionControlAdapter(
|
|||
actionEmitter
|
||||
)
|
||||
DeleteTabsViewHolder.LAYOUT_ID -> DeleteTabsViewHolder(view, actionEmitter)
|
||||
CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view)
|
||||
NoCollectionMessageViewHolder.LAYOUT_ID -> NoCollectionMessageViewHolder(
|
||||
view
|
||||
)
|
||||
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter, job)
|
||||
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter, job)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +103,13 @@ class SessionControlAdapter(
|
|||
is TabViewHolder -> holder.bindSession(
|
||||
(items[position] as AdapterItem.TabItem).tab
|
||||
)
|
||||
is CollectionViewHolder -> holder.bindSession(
|
||||
(items[position] as AdapterItem.CollectionItem).collection
|
||||
)
|
||||
is TabInCollectionViewHolder -> {
|
||||
val item = (items[position] as AdapterItem.TabInCollectionItem)
|
||||
holder.bindSession(item.collection, item.tab, item.isLastTab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.mozilla.fenix.mvi.ViewState
|
|||
class SessionControlComponent(
|
||||
private val container: ViewGroup,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: SessionControlState = SessionControlState(emptyList(), Mode.Normal)
|
||||
override var initialState: SessionControlState = SessionControlState(emptyList(), emptyList(), Mode.Normal)
|
||||
) :
|
||||
UIComponent<SessionControlState, SessionControlAction, SessionControlChange>(
|
||||
bus.getManagedEmitter(SessionControlAction::class.java),
|
||||
|
@ -26,6 +26,7 @@ class SessionControlComponent(
|
|||
|
||||
override val reducer: (SessionControlState, SessionControlChange) -> SessionControlState = { state, change ->
|
||||
when (change) {
|
||||
is SessionControlChange.CollectionsChange -> state.copy(collections = change.collections)
|
||||
is SessionControlChange.TabsChange -> state.copy(tabs = change.tabs)
|
||||
is SessionControlChange.ModeChange -> state.copy(mode = change.mode)
|
||||
}
|
||||
|
@ -45,10 +46,18 @@ data class Tab(
|
|||
val url: String,
|
||||
val hostname: String,
|
||||
val title: String,
|
||||
val selected: Boolean,
|
||||
val selected: Boolean? = null,
|
||||
val thumbnail: Bitmap? = null
|
||||
)
|
||||
|
||||
data class TabCollection(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val tabs: MutableList<Tab>,
|
||||
val iconColor: Int = 0,
|
||||
var expanded: Boolean = false
|
||||
)
|
||||
|
||||
sealed class Mode {
|
||||
object Normal : Mode()
|
||||
object Private : Mode()
|
||||
|
@ -56,6 +65,7 @@ sealed class Mode {
|
|||
|
||||
data class SessionControlState(
|
||||
val tabs: List<Tab>,
|
||||
val collections: List<TabCollection>,
|
||||
val mode: Mode
|
||||
) : ViewState
|
||||
|
||||
|
@ -70,15 +80,32 @@ sealed class TabAction : Action {
|
|||
object PrivateBrowsingLearnMore : TabAction()
|
||||
}
|
||||
|
||||
sealed class CollectionAction : Action {
|
||||
data class Expand(val collection: TabCollection) : CollectionAction()
|
||||
data class Collapse(val collection: TabCollection) : CollectionAction()
|
||||
data class Delete(val collection: TabCollection) : CollectionAction()
|
||||
data class AddTab(val collection: TabCollection) : CollectionAction()
|
||||
data class Rename(val collection: TabCollection) : CollectionAction()
|
||||
data class OpenTabs(val collection: TabCollection) : CollectionAction()
|
||||
data class ShareTabs(val collection: TabCollection) : CollectionAction()
|
||||
data class RemoveTab(val collection: TabCollection, val tab: Tab) : CollectionAction()
|
||||
}
|
||||
|
||||
sealed class SessionControlAction : Action {
|
||||
data class Tab(val action: TabAction) : SessionControlAction()
|
||||
data class Collection(val action: CollectionAction) : SessionControlAction()
|
||||
}
|
||||
|
||||
fun Observer<SessionControlAction>.onNext(tabAction: TabAction) {
|
||||
onNext(SessionControlAction.Tab(tabAction))
|
||||
}
|
||||
|
||||
fun Observer<SessionControlAction>.onNext(collectionAction: CollectionAction) {
|
||||
onNext(SessionControlAction.Collection(collectionAction))
|
||||
}
|
||||
|
||||
sealed class SessionControlChange : Change {
|
||||
data class TabsChange(val tabs: List<Tab>) : SessionControlChange()
|
||||
data class ModeChange(val mode: Mode) : SessionControlChange()
|
||||
data class CollectionsChange(val collections: List<TabCollection>) : SessionControlChange()
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import org.mozilla.fenix.BuildConfig
|
||||
|
||||
// Convert HomeState into a data structure HomeAdapter understands
|
||||
@SuppressWarnings("ComplexMethod")
|
||||
@SuppressWarnings("ComplexMethod", "NestedBlockDepth")
|
||||
private fun SessionControlState.toAdapterList(): List<AdapterItem> {
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
items.add(AdapterItem.TabHeader)
|
||||
|
||||
// Populate tabs
|
||||
if (tabs.isNotEmpty()) {
|
||||
tabs.reversed().map(AdapterItem::TabItem).forEach { items.add(it) }
|
||||
if (mode == Mode.Private) {
|
||||
|
@ -36,9 +37,39 @@ private fun SessionControlState.toAdapterList(): List<AdapterItem> {
|
|||
items.add(item)
|
||||
}
|
||||
|
||||
// Populate collections
|
||||
if (mode == Mode.Normal) {
|
||||
items.add(AdapterItem.CollectionHeader)
|
||||
if (collections.isNotEmpty()) {
|
||||
|
||||
// If the collection is expanded, we want to add all of its tabs beneath it in the adapter
|
||||
collections.reversed().map(AdapterItem::CollectionItem).forEach {
|
||||
if (it.collection.expanded) {
|
||||
items.add(it)
|
||||
addCollectionTabItems(it.collection, it.collection.tabs, items)
|
||||
} else {
|
||||
items.add(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items.add(AdapterItem.NoCollectionMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
private fun addCollectionTabItems(
|
||||
collection: TabCollection,
|
||||
tabs: MutableList<Tab>,
|
||||
itemList: MutableList<AdapterItem>
|
||||
) {
|
||||
for (tabIndex in 0 until tabs.size) {
|
||||
itemList.add(AdapterItem.TabInCollectionItem
|
||||
(collection, collection.tabs[tabIndex], tabIndex == collection.tabs.size - 1))
|
||||
}
|
||||
}
|
||||
|
||||
class SessionControlUIView(
|
||||
container: ViewGroup,
|
||||
actionEmitter: Observer<SessionControlAction>,
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
|
||||
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabViewHolder
|
||||
|
||||
class SwipeToDeleteCallback(
|
||||
|
@ -27,8 +28,11 @@ class SwipeToDeleteCallback(
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
if (viewHolder is TabViewHolder) {
|
||||
actionEmitter.onNext(TabAction.Close(viewHolder.tab?.sessionId!!))
|
||||
when (viewHolder) {
|
||||
is TabViewHolder -> actionEmitter.onNext(TabAction.Close(viewHolder.tab?.sessionId!!))
|
||||
is TabInCollectionViewHolder -> {
|
||||
actionEmitter.onNext(CollectionAction.RemoveTab(viewHolder.collection, viewHolder.tab))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,11 +47,18 @@ class SwipeToDeleteCallback(
|
|||
) {
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||
val icon = ContextCompat.getDrawable(recyclerView.context, R.drawable.ic_delete)
|
||||
val background = ContextCompat.getDrawable(
|
||||
recyclerView.context,
|
||||
R.drawable.session_background
|
||||
)
|
||||
|
||||
val backgroundDrawable = when {
|
||||
viewHolder is TabInCollectionViewHolder && viewHolder.isLastTab -> {
|
||||
R.drawable.tab_in_collection_last_swipe_background
|
||||
}
|
||||
viewHolder is TabInCollectionViewHolder -> {
|
||||
R.drawable.tab_in_collection_swipe_background
|
||||
}
|
||||
else -> R.drawable.session_background
|
||||
}
|
||||
|
||||
val background = ContextCompat.getDrawable(recyclerView.context, backgroundDrawable)
|
||||
background?.let {
|
||||
icon?.let {
|
||||
val itemView = viewHolder.itemView
|
||||
|
@ -95,7 +106,7 @@ class SwipeToDeleteCallback(
|
|||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return if (viewHolder is TabViewHolder) {
|
||||
return if (viewHolder is TabViewHolder || viewHolder is TabInCollectionViewHolder) {
|
||||
super.getSwipeDirs(recyclerView, viewHolder)
|
||||
} else 0
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* 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.home.sessioncontrol.viewholders
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class CollectionHeaderViewHolder(
|
||||
view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.collection_header
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
/* 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.home.sessioncontrol.viewholders
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.collection_home_list_row.*
|
||||
import kotlinx.android.synthetic.main.collection_home_list_row.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import mozilla.components.browser.menu.BrowserMenu
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import org.mozilla.fenix.DefaultThemeManager
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.increaseTapArea
|
||||
import org.mozilla.fenix.home.sessioncontrol.CollectionAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.home.sessioncontrol.onNext
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class CollectionViewHolder(
|
||||
val view: View,
|
||||
val actionEmitter: Observer<SessionControlAction>,
|
||||
val job: Job,
|
||||
override val containerView: View? = view
|
||||
) :
|
||||
RecyclerView.ViewHolder(view), LayoutContainer, CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
private lateinit var collection: TabCollection
|
||||
private var state = CollectionState.Collapsed
|
||||
private var collectionMenu: CollectionItemMenu
|
||||
|
||||
init {
|
||||
collectionMenu = CollectionItemMenu(view.context) {
|
||||
when (it) {
|
||||
is CollectionItemMenu.Item.DeleteCollection -> actionEmitter.onNext(CollectionAction.Delete(collection))
|
||||
is CollectionItemMenu.Item.AddTab -> actionEmitter.onNext(CollectionAction.AddTab(collection))
|
||||
is CollectionItemMenu.Item.RenameCollection -> actionEmitter.onNext(CollectionAction.Rename(collection))
|
||||
is CollectionItemMenu.Item.OpenTabs -> actionEmitter.onNext(CollectionAction.OpenTabs(collection))
|
||||
}
|
||||
}
|
||||
|
||||
collection_overflow_button.run {
|
||||
increaseTapArea(buttonIncreaseDps)
|
||||
setOnClickListener {
|
||||
collectionMenu.menuBuilder
|
||||
.build(view.context)
|
||||
.show(anchor = it, orientation = BrowserMenu.Orientation.DOWN)
|
||||
}
|
||||
}
|
||||
|
||||
collection_share_button.run {
|
||||
increaseTapArea(buttonIncreaseDps)
|
||||
setOnClickListener {
|
||||
actionEmitter.onNext(CollectionAction.ShareTabs(collection))
|
||||
}
|
||||
}
|
||||
|
||||
view.setOnClickListener {
|
||||
updateState()
|
||||
}
|
||||
|
||||
view.collection_icon.setColorFilter(ContextCompat.getColor(
|
||||
view.context,
|
||||
getNextIconColor()),
|
||||
android.graphics.PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
}
|
||||
|
||||
fun bindSession(collection: TabCollection) {
|
||||
this.collection = collection
|
||||
updateCollectionUI()
|
||||
}
|
||||
|
||||
private fun updateCollectionUI() {
|
||||
view.collection_title.text = collection.title
|
||||
|
||||
var hostNameList = listOf<String>()
|
||||
|
||||
collection.tabs.forEach {
|
||||
hostNameList += it.hostname.capitalize()
|
||||
}
|
||||
|
||||
var tabsDisplayed = 0
|
||||
val titleList = hostNameList.joinToString(", ") {
|
||||
if (it.length > maxTitleLength) {
|
||||
it.substring(0,
|
||||
maxTitleLength
|
||||
) + "..."
|
||||
} else {
|
||||
tabsDisplayed += 1
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
view.collection_description.text = titleList
|
||||
|
||||
if (collection.expanded) {
|
||||
(view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0
|
||||
collection_title.setPadding(0, 0, 0, EXPANDED_PADDING)
|
||||
view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_top_corners)
|
||||
view.collection_description.visibility = View.GONE
|
||||
view.expand_button.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.ic_chevron_up))
|
||||
} else {
|
||||
(view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = COLLAPSED_MARGIN
|
||||
view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_all_corners)
|
||||
view.collection_description.visibility = View.VISIBLE
|
||||
view.expand_button.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.ic_chevron_down))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
state = when (state) {
|
||||
CollectionState.Expanded -> {
|
||||
actionEmitter.onNext(CollectionAction.Collapse(collection))
|
||||
CollectionState.Collapsed
|
||||
}
|
||||
CollectionState.Collapsed -> {
|
||||
actionEmitter.onNext(CollectionAction.Expand(collection))
|
||||
CollectionState.Expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "MagicNumber")
|
||||
private fun getNextIconColor(): Int {
|
||||
with(view.context) {
|
||||
var sessionColorIndex = Settings.getInstance(this).preferences
|
||||
.getInt(getString(R.string.pref_key_collection_color), 0)
|
||||
|
||||
val iconResource = when (sessionColorIndex) {
|
||||
0 -> R.color.collection_icon_color_violet
|
||||
1 -> R.color.collection_icon_color_blue
|
||||
2 -> R.color.collection_icon_color_pink
|
||||
3 -> R.color.collection_icon_color_green
|
||||
4 -> R.color.collection_icon_color_yellow
|
||||
else -> R.color.white_color
|
||||
}
|
||||
|
||||
if (sessionColorIndex >= MAX_COLOR_INDEX) { sessionColorIndex = 0 } else { sessionColorIndex += 1 }
|
||||
|
||||
Settings.getInstance(this).preferences.edit()
|
||||
.putInt(getString(R.string.pref_key_collection_color), sessionColorIndex).apply()
|
||||
|
||||
return iconResource
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_COLOR_INDEX = 4
|
||||
const val EXPANDED_PADDING = 60
|
||||
const val COLLAPSED_MARGIN = 12
|
||||
const val LAYOUT_ID = R.layout.collection_home_list_row
|
||||
const val maxTitleLength = 20
|
||||
const val buttonIncreaseDps = 24
|
||||
}
|
||||
|
||||
enum class CollectionState {
|
||||
Expanded, Collapsed
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionItemMenu(
|
||||
private val context: Context,
|
||||
private val onItemTapped: (Item) -> Unit = {}
|
||||
) {
|
||||
sealed class Item {
|
||||
object DeleteCollection : Item()
|
||||
object AddTab : Item()
|
||||
object RenameCollection : Item()
|
||||
object OpenTabs : Item()
|
||||
}
|
||||
|
||||
val menuBuilder by lazy { BrowserMenuBuilder(menuItems) }
|
||||
|
||||
private val menuItems by lazy {
|
||||
listOf(
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_delete),
|
||||
textColorResource = DefaultThemeManager.resolveAttribute(R.attr.destructive, context)
|
||||
) {
|
||||
onItemTapped.invoke(Item.DeleteCollection)
|
||||
},
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.add_tab)
|
||||
) {
|
||||
onItemTapped.invoke(Item.AddTab)
|
||||
},
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_rename)
|
||||
) {
|
||||
onItemTapped.invoke(Item.RenameCollection)
|
||||
},
|
||||
SimpleBrowserMenuItem(
|
||||
context.getString(R.string.collection_open_tabs)
|
||||
) {
|
||||
onItemTapped.invoke(Item.OpenTabs)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/* 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.home.sessioncontrol.viewholders
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class NoCollectionMessageViewHolder(
|
||||
view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.no_collection_message
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/* 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.home.sessioncontrol.viewholders
|
||||
|
||||
import android.graphics.Outline
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.tab_in_collection.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.support.ktx.android.content.res.pxToDp
|
||||
import org.jetbrains.anko.backgroundColor
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getColorFromAttr
|
||||
import org.mozilla.fenix.home.sessioncontrol.CollectionAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlAction
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.home.sessioncontrol.onNext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class TabInCollectionViewHolder(
|
||||
val view: View,
|
||||
val actionEmitter: Observer<SessionControlAction>,
|
||||
val job: Job,
|
||||
override val containerView: View? = view
|
||||
) : RecyclerView.ViewHolder(view), LayoutContainer, CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
lateinit var collection: TabCollection
|
||||
private set
|
||||
lateinit var tab: Tab
|
||||
private set
|
||||
var isLastTab = false
|
||||
|
||||
init {
|
||||
collection_tab_icon.clipToOutline = true
|
||||
collection_tab_icon.outlineProvider = object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View?, outline: Outline?) {
|
||||
outline?.setRoundRect(
|
||||
0,
|
||||
0,
|
||||
view!!.width,
|
||||
view.height,
|
||||
view.context.resources.pxToDp(TabViewHolder.favIconBorderRadiusInPx).toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
collection_tab_close_button.setOnClickListener {
|
||||
actionEmitter.onNext(CollectionAction.RemoveTab(collection, tab))
|
||||
}
|
||||
}
|
||||
|
||||
fun bindSession(collection: TabCollection, tab: Tab, isLastTab: Boolean) {
|
||||
this.collection = collection
|
||||
this.tab = tab
|
||||
this.isLastTab = isLastTab
|
||||
updateTabUI()
|
||||
}
|
||||
|
||||
private fun updateTabUI() {
|
||||
collection_tab_hostname.text = tab.hostname
|
||||
collection_tab_title.text = tab.title
|
||||
launch(Dispatchers.IO) {
|
||||
val bitmap = collection_tab_icon.context.components.utils.icons
|
||||
.loadIcon(IconRequest(tab.url)).await().bitmap
|
||||
launch(Dispatchers.Main) {
|
||||
collection_tab_icon.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
// If I'm the last one...
|
||||
if (isLastTab) {
|
||||
view.background = ContextCompat.getDrawable(view.context, R.drawable.rounded_bottom_corners)
|
||||
divider_line.visibility = View.GONE
|
||||
} else {
|
||||
view.backgroundColor = R.attr.above.getColorFromAttr(view.context)
|
||||
divider_line.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.tab_in_collection
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.browser.menu.BrowserMenu
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import mozilla.components.support.ktx.android.content.res.pxToDp
|
||||
|
@ -85,18 +84,18 @@ class TabViewHolder(
|
|||
setOnClickListener {
|
||||
tabMenu.menuBuilder
|
||||
.build(view.context)
|
||||
.show(anchor = it, orientation = BrowserMenu.Orientation.DOWN)
|
||||
.show(anchor = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bindSession(tab: Tab) {
|
||||
this.tab = tab
|
||||
updateText(tab)
|
||||
updateSelected(tab.selected)
|
||||
updateTabUI(tab)
|
||||
updateSelected(tab.selected ?: false)
|
||||
}
|
||||
|
||||
fun updateText(tab: Tab) {
|
||||
private fun updateTabUI(tab: Tab) {
|
||||
hostname.text = tab.hostname
|
||||
tab_title.text = tab.title
|
||||
launch(Dispatchers.IO) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<vector android:height="6dp" android:viewportHeight="6"
|
||||
android:viewportWidth="10" android:width="10dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M5,5.6667C4.8232,5.6666 4.6537,5.5964 4.5287,5.4713L0.5287,1.4713C0.276,1.2097 0.2796,0.7939 0.5368,0.5368C0.7939,0.2796 1.2097,0.276 1.4713,0.5287L5,4.0573L8.5287,0.5287C8.7903,0.276 9.2061,0.2796 9.4632,0.5368C9.7204,0.7939 9.724,1.2097 9.4713,1.4713L5.4713,5.4713C5.3463,5.5964 5.1768,5.6666 5,5.6667Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<vector android:height="6dp" android:viewportHeight="6"
|
||||
android:viewportWidth="10" android:width="10dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?primaryText" android:fillType="nonZero"
|
||||
android:pathData="M5,0.3333C4.8232,0.3334 4.6537,0.4036 4.5287,0.5287L0.5287,4.5287C0.276,4.7903 0.2796,5.2061 0.5368,5.4632C0.7939,5.7204 1.2097,5.724 1.4713,5.4713L5,1.9427L8.5287,5.4713C8.7903,5.724 9.2061,5.7204 9.4632,5.4632C9.7204,5.2061 9.724,4.7903 9.4713,4.5287L5.4713,0.5287C5.3463,0.4036 5.1768,0.3334 5,0.3333Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
File diff suppressed because one or more lines are too long
|
@ -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/. -->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?above" />
|
||||
<corners android:radius="@dimen/tab_corner_radius"/>
|
||||
</shape>
|
|
@ -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/. -->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?above" />
|
||||
<corners android:bottomLeftRadius="@dimen/tab_corner_radius" android:bottomRightRadius="@dimen/tab_corner_radius" />
|
||||
</shape>
|
|
@ -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/. -->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?above" />
|
||||
<corners android:topLeftRadius="@dimen/tab_corner_radius" android:topRightRadius="@dimen/tab_corner_radius" />
|
||||
</shape>
|
|
@ -5,6 +5,6 @@
|
|||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<corners android:radius="@dimen/tab_corner_radius" />
|
||||
<solid android:color="@color/photonGrey30" />
|
||||
</shape>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?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/. -->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:bottomLeftRadius="@dimen/tab_corner_radius" android:bottomRightRadius="@dimen/tab_corner_radius" />
|
||||
<solid android:color="@color/photonGrey30" />
|
||||
</shape>
|
|
@ -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/. -->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/photonGrey30" />
|
||||
</shape>
|
|
@ -0,0 +1,33 @@
|
|||
<?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"
|
||||
android:id="@+id/collections_header"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/divider_line"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?neutralFaded"
|
||||
android:layout_marginStart="23dp"
|
||||
android:layout_marginEnd="23dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collections_header_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/collections_header"
|
||||
android:textAppearance="@style/HeaderTextStyle"
|
||||
android:layout_marginTop="15dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider_line" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,95 @@
|
|||
<?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:id="@+id/item_collection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:background="@drawable/rounded_all_corners"
|
||||
android:elevation="5dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collection_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="18dp"
|
||||
android:tint="@null"
|
||||
android:src="@drawable/ic_archive"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="17dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:textAppearance="@style/Header16TextStyle"
|
||||
app:layout_constraintStart_toEndOf="@id/collection_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/expand_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="26dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_chevron_down"
|
||||
app:layout_constraintStart_toEndOf="@id/collection_title"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:minLines="2"
|
||||
android:textAppearance="@style/SubtitleTextStyle"
|
||||
app:layout_constraintStart_toStartOf="@id/collection_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/collection_share_button"
|
||||
app:layout_constraintEnd_toStartOf="@id/collection_share_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/collection_share_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/tab_menu"
|
||||
android:src="@drawable/ic_hollow_share"
|
||||
android:layout_marginEnd="29dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/collection_overflow_button"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/collection_overflow_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/tab_menu"
|
||||
android:src="@drawable/ic_menu"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/collection_icon"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
|
@ -27,7 +27,7 @@
|
|||
android:focusable="false"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:text="@string/session_delete"
|
||||
android:text="@string/collection_delete"
|
||||
android:textColor="?contrastText"
|
||||
android:textSize="16sp" />
|
||||
</FrameLayout>
|
|
@ -0,0 +1,35 @@
|
|||
<?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"
|
||||
android:id="@+id/no_tabs_wrapper"
|
||||
android:background="@drawable/empty_session_control_background"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:padding="16dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_collection_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableEnd="@drawable/ic_archive"
|
||||
android:drawableTint="?primaryText"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/no_collections_header"
|
||||
android:textAppearance="@style/HeaderTextStyle"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_collection_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/no_collections_description"
|
||||
android:textColor="?primaryText"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal" />
|
||||
</LinearLayout>
|
|
@ -33,7 +33,7 @@
|
|||
android:contentDescription="@string/current_session_image"
|
||||
android:paddingBottom="20dp"
|
||||
android:src="@drawable/ic_session_thumbnail_placeholder_greyscale"
|
||||
android:tint="@color/session_placeholder_blue"
|
||||
android:tint="@color/collection_icon_color_blue"
|
||||
android:tintMode="multiply"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?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:id="@+id/tab_in_collection_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:background="?above"
|
||||
android:elevation="5dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collection_tab_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginStart="18dp"
|
||||
android:tint="@null"
|
||||
android:src="@drawable/ic_archive"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_tab_hostname"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:minLines="1"
|
||||
android:textAppearance="@style/Header12TextStyle"
|
||||
app:layout_constraintStart_toEndOf="@id/collection_tab_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/collection_tab_close_button"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_tab_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:minLines="2"
|
||||
android:textAppearance="@style/Body14TextStyle"
|
||||
app:layout_constraintStart_toStartOf="@id/collection_tab_hostname"
|
||||
app:layout_constraintTop_toBottomOf="@id/collection_tab_hostname"
|
||||
app:layout_constraintEnd_toEndOf="@id/collection_tab_hostname"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/collection_tab_close_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/close_tab"
|
||||
android:src="@drawable/ic_close"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:alpha="0.8"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider_line"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?neutralFaded"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
|
@ -7,7 +7,7 @@
|
|||
android:id="@+id/item_tab"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="true"
|
||||
|
@ -40,9 +40,7 @@
|
|||
android:layout_marginStart="8dp"
|
||||
android:ellipsize="none"
|
||||
android:singleLine="true"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="@style/Header12TextStyle"
|
||||
app:layout_constraintEnd_toStartOf="@id/close_tab_button"
|
||||
app:layout_constraintStart_toEndOf="@id/favicon_image"
|
||||
app:layout_constraintTop_toTopOf="@id/favicon_image" />
|
||||
|
@ -58,7 +56,7 @@
|
|||
android:maxLines="2"
|
||||
android:minLines="2"
|
||||
android:textColor="?primaryText"
|
||||
android:textSize="15sp"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintStart_toEndOf="@id/favicon_image"
|
||||
app:layout_constraintEnd_toEndOf="@id/hostname"
|
||||
app:layout_constraintTop_toBottomOf="@id/hostname"
|
||||
|
|
|
@ -74,12 +74,12 @@
|
|||
<!-- Bookmark buttons -->
|
||||
<color name="bookmark_favicon_background">#DFDFE3</color>
|
||||
|
||||
<!-- Session placeholder icons-->
|
||||
<color name="session_placeholder_blue">#00B3F4</color>
|
||||
<color name="session_placeholder_orange">#FF8A50</color>
|
||||
<color name="session_placeholder_green">#54FFBD</color>
|
||||
<color name="session_placeholder_purple">#AB71FF</color>
|
||||
<color name="session_placeholder_pink">#FF4AA2</color>
|
||||
<!-- Collection icons-->
|
||||
<color name="collection_icon_color_violet">#7542E5</color>
|
||||
<color name="collection_icon_color_blue">#0250BB</color>
|
||||
<color name="collection_icon_color_pink">#E31587</color>
|
||||
<color name="collection_icon_color_green">#2AC3A2</color>
|
||||
<color name="collection_icon_color_yellow">#E27F2E</color>
|
||||
|
||||
<!-- Library buttons -->
|
||||
<color name="library_sessions_icon_background">#B9F0FD</color>
|
||||
|
|
|
@ -65,4 +65,6 @@
|
|||
<string name="pref_key_tracking_protection_settings" translatable="false">pref_key_tracking_protection_settings</string>
|
||||
<string name="pref_key_tracking_protection" translatable="false">pref_key_tracking_protection</string>
|
||||
<string name="pref_key_tracking_protection_exceptions" translatable="false">pref_key_tracking_protection_exceptions</string>
|
||||
|
||||
<string name="pref_key_collection_color" translatable="false">pref_key_collection_color</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
- 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/. -->
|
||||
<resources>
|
||||
|
||||
|
||||
<!-- Home Fragment -->
|
||||
<!-- Content description (not visible, for screen readers etc.): "Three dot" menu button. -->
|
||||
<string name="content_description_menu">More options</string>
|
||||
|
@ -269,8 +267,12 @@
|
|||
<string name="current_session_image">Current session image</string>
|
||||
<!-- Button to save the current set of tabs into a collection -->
|
||||
<string name="save_to_collection">Save to collection</string>
|
||||
<!-- Text for the button to delete a session -->
|
||||
<string name="session_delete">Delete session</string>
|
||||
<!-- Text for the menu button to delete a collection -->
|
||||
<string name="collection_delete">Delete collection</string>
|
||||
<!-- Text for the menu button to rename a collection -->
|
||||
<string name="collection_rename">Rename collection</string>
|
||||
<!-- Text for the button to open tabs of the selected collection -->
|
||||
<string name="collection_open_tabs">Open tabs</string>
|
||||
<!-- Text for the button to delete a single session -->
|
||||
<string name="session_item_delete">Delete</string>
|
||||
<!-- Text to tell the user how many more tabs this session has.
|
||||
|
@ -383,11 +385,9 @@
|
|||
<!-- Message for copying the URL via long press on the toolbar -->
|
||||
<string name="url_copied">URL copied</string>
|
||||
|
||||
|
||||
<!-- Site Permissions -->
|
||||
<!-- Button label that take the user to the Android App setting -->
|
||||
<string name="phone_feature_go_to_settings">Go to Settings</string>
|
||||
|
||||
<!-- Content description (not visible, for screen readers etc.): Quick settings sheet
|
||||
to give users access to site specific information / settings. For example:
|
||||
Secure settings status and a button to modify site permissions -->
|
||||
|
@ -427,6 +427,13 @@
|
|||
<!-- Summary of tracking protection preference if tracking protection is set to off -->
|
||||
<string name="tracking_protection_off">Off</string>
|
||||
|
||||
<!-- Collections -->
|
||||
<!-- Collections header on home fragment -->
|
||||
<string name="collections_header">Collections</string>
|
||||
<!-- No Open Tabs Message Header -->
|
||||
<string name="no_collections_header">No collections</string>
|
||||
<!-- No Open Tabs Message Description -->
|
||||
<string name="no_collections_description">Your collections will be shown here.</string>
|
||||
|
||||
<!-- Title for the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_tabs">Select Tabs</string>
|
||||
|
@ -442,7 +449,6 @@
|
|||
|
||||
<!-- Button to select all tabs in the "select tabs" step of the collection creator -->
|
||||
<string name="create_collection_select_all">Select All</string>
|
||||
|
||||
<!-- Text to prompt users to select the tabs to save in the "select tabs" stepof the collection creator -->
|
||||
<string name="create_collection_save_to_collection_empty">Select tabs to save</string>
|
||||
|
||||
|
|
|
@ -166,12 +166,34 @@
|
|||
<item name="android:letterSpacing">0.03</item>
|
||||
</style>
|
||||
|
||||
<style name="Header16TextStyle" parent="TextAppearance.MaterialComponents.Body1">
|
||||
<item name="android:textColor">?primaryText</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Header14TextStyle" parent="TextAppearance.MaterialComponents.Body2">
|
||||
<item name="android:textColor">?primaryText</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Header12TextStyle" parent="TextAppearance.MaterialComponents.Body2">
|
||||
<item name="android:textColor">?secondaryText</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Body14TextStyle" parent="TextAppearance.MaterialComponents.Body2">
|
||||
<item name="android:textColor">?primaryText</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="SubtitleTextStyle" parent="TextAppearance.MaterialComponents.Body1">
|
||||
<item name="android:textColor">?secondaryText</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarTitleTextStyle" parent="HeaderTextStyle">
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue