Use StateListDrawable & resources w/ TabCollection
parent
451093fd9e
commit
6da6ddb095
|
@ -8,23 +8,18 @@ import android.graphics.PorterDuff.Mode.SRC_IN
|
|||
import android.view.LayoutInflater
|
||||
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.synthetic.main.collections_list_item.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.description
|
||||
import org.mozilla.fenix.ext.getIconColor
|
||||
import org.mozilla.fenix.home.sessioncontrol.Tab
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import org.mozilla.fenix.utils.AdapterWithJob
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SaveCollectionListAdapter(
|
||||
val actionEmitter: Observer<CollectionCreationAction>
|
||||
) : AdapterWithJob<CollectionViewHolder>() {
|
||||
) : RecyclerView.Adapter<CollectionViewHolder>() {
|
||||
|
||||
private var tabCollections = listOf<TabCollection>()
|
||||
private var selectedTabs: Set<Tab> = setOf()
|
||||
|
@ -33,13 +28,13 @@ class SaveCollectionListAdapter(
|
|||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(CollectionViewHolder.LAYOUT_ID, parent, false)
|
||||
|
||||
return CollectionViewHolder(view, actionEmitter, adapterJob)
|
||||
return CollectionViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CollectionViewHolder, position: Int) {
|
||||
val collection = tabCollections[position]
|
||||
holder.bind(collection)
|
||||
holder.view.setOnClickListener {
|
||||
holder.itemView.setOnClickListener {
|
||||
collection.apply {
|
||||
val action = CollectionCreationAction.SelectCollection(this, selectedTabs.toList())
|
||||
actionEmitter.onNext(action)
|
||||
|
@ -56,46 +51,19 @@ class SaveCollectionListAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
class CollectionViewHolder(
|
||||
val view: View,
|
||||
actionEmitter: Observer<CollectionCreationAction>,
|
||||
val job: Job
|
||||
) :
|
||||
RecyclerView.ViewHolder(view), CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.IO + job
|
||||
|
||||
private var collection: TabCollection? = null
|
||||
class CollectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun bind(collection: TabCollection) {
|
||||
this.collection = collection
|
||||
view.collection_item.text = collection.title
|
||||
view.collection_description.text = collection.description(view.context)
|
||||
itemView.collection_item.text = collection.title
|
||||
itemView.collection_description.text = collection.description(itemView.context)
|
||||
|
||||
view.collection_icon.setColorFilter(
|
||||
ContextCompat.getColor(
|
||||
view.context,
|
||||
getIconColor(collection.id)
|
||||
),
|
||||
itemView.collection_icon.setColorFilter(
|
||||
collection.getIconColor(itemView.context),
|
||||
SRC_IN
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getIconColor(id: Long): Int {
|
||||
return when ((id % 5).toInt()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.collections_list_item
|
||||
const val maxTitleLength = 20
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/* 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.ext
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.home.sessioncontrol.TabCollection
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Selects one of the predefined collection icon colors based on the id.
|
||||
*/
|
||||
@ColorInt
|
||||
fun TabCollection.getIconColor(context: Context): Int {
|
||||
val iconColors = context.resources.obtainTypedArray(R.array.collection_icon_colors)
|
||||
val index = abs(id % iconColors.length()).toInt()
|
||||
val color = iconColors.getColor(index, ContextCompat.getColor(context, R.color.white_color))
|
||||
iconColors.recycle()
|
||||
return color
|
||||
}
|
|
@ -104,7 +104,7 @@ class SessionControlAdapter(
|
|||
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(view, actionEmitter)
|
||||
NoContentMessageViewHolder.LAYOUT_ID -> NoContentMessageViewHolder(view)
|
||||
CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view)
|
||||
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter, adapterJob)
|
||||
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter)
|
||||
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter, adapterJob)
|
||||
OnboardingHeaderViewHolder.LAYOUT_ID -> OnboardingHeaderViewHolder(view)
|
||||
OnboardingSectionHeaderViewHolder.LAYOUT_ID -> OnboardingSectionHeaderViewHolder(view)
|
||||
|
|
|
@ -8,37 +8,29 @@ import android.content.Context
|
|||
import android.graphics.PorterDuff.Mode.SRC_IN
|
||||
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.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ThemeManager
|
||||
import org.mozilla.fenix.components.description
|
||||
import org.mozilla.fenix.ext.getIconColor
|
||||
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 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
|
||||
RecyclerView.ViewHolder(view), LayoutContainer {
|
||||
|
||||
private lateinit var collection: TabCollection
|
||||
private var expanded = false
|
||||
|
@ -70,6 +62,7 @@ class CollectionViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
view.clipToOutline = true
|
||||
view.setOnClickListener {
|
||||
handleExpansion(expanded)
|
||||
}
|
||||
|
@ -84,27 +77,20 @@ class CollectionViewHolder(
|
|||
private fun updateCollectionUI() {
|
||||
view.collection_title.text = collection.title
|
||||
view.collection_description.text = collection.description(view.context)
|
||||
val layoutParams = view.layoutParams as ViewGroup.MarginLayoutParams
|
||||
|
||||
view.isActivated = expanded
|
||||
if (expanded) {
|
||||
(view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0
|
||||
layoutParams.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.chevron.setBackgroundResource(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)
|
||||
layoutParams.bottomMargin = COLLAPSED_MARGIN
|
||||
view.collection_description.visibility = View.VISIBLE
|
||||
|
||||
view.chevron.setBackgroundResource(R.drawable.ic_chevron_down)
|
||||
}
|
||||
|
||||
view.collection_icon.setColorFilter(
|
||||
ContextCompat.getColor(
|
||||
view.context,
|
||||
getIconColor(collection.id)
|
||||
),
|
||||
collection.getIconColor(view.context),
|
||||
SRC_IN
|
||||
)
|
||||
}
|
||||
|
@ -117,19 +103,6 @@ class CollectionViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getIconColor(id: Long): Int {
|
||||
val sessionColorIndex = (id % 5).toInt()
|
||||
return 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
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val buttonIncreaseDps = 16
|
||||
const val EXPANDED_PADDING = 60
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
|
||||
<item android:drawable="@drawable/rounded_top_corners" android:state_activated="true" />
|
||||
<item android:drawable="@drawable/rounded_all_corners" />
|
||||
</selector>
|
|
@ -0,0 +1,8 @@
|
|||
<?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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
|
||||
<item android:drawable="@drawable/ic_chevron_up" android:state_activated="true" />
|
||||
<item android:drawable="@drawable/ic_chevron_down" />
|
||||
</selector>
|
|
@ -2,8 +2,7 @@
|
|||
<!-- 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>
|
||||
</shape>
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
<!-- 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>
|
||||
</shape>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@drawable/rounded_all_corners"
|
||||
android:background="@drawable/collection_home_list_row_background"
|
||||
android:clickable="true"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="5dp"
|
||||
|
@ -59,7 +59,7 @@
|
|||
android:layout_height="6dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/ic_chevron_down"
|
||||
android:background="@drawable/ic_chevron"
|
||||
android:contentDescription="@string/tab_menu"
|
||||
app:layout_constraintEnd_toStartOf="@+id/collection_share_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/collection_title"
|
||||
|
|
|
@ -102,6 +102,13 @@
|
|||
<color name="collection_icon_color_pink">@color/collection_icon_color_pink_light_theme</color>
|
||||
<color name="collection_icon_color_green">@color/collection_icon_color_green_light_theme</color>
|
||||
<color name="collection_icon_color_yellow">@color/collection_icon_color_yellow_light_theme</color>
|
||||
<array name="collection_icon_colors">
|
||||
<item>@color/collection_icon_color_violet</item>
|
||||
<item>@color/collection_icon_color_blue</item>
|
||||
<item>@color/collection_icon_color_pink</item>
|
||||
<item>@color/collection_icon_color_green</item>
|
||||
<item>@color/collection_icon_color_yellow</item>
|
||||
</array>
|
||||
|
||||
<!-- Library buttons -->
|
||||
<color name="library_sessions_icon_background">#B9F0FD</color>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* 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.ext
|
||||
|
||||
import androidx.core.content.ContextCompat
|
||||
import junit.framework.Assert.assertEquals
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import mozilla.components.support.test.mock
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.TestApplication
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = TestApplication::class)
|
||||
class TabCollectionTest {
|
||||
|
||||
@Test
|
||||
fun getIconColor() {
|
||||
val color = mockTabCollection(100L).getIconColor(testContext)
|
||||
// Color does not change
|
||||
for (i in 0..99) {
|
||||
assertEquals(color, mockTabCollection(100L).getIconColor(testContext))
|
||||
}
|
||||
|
||||
// Returns a color for negative IDs
|
||||
val defaultColor = ContextCompat.getColor(testContext, R.color.white_color)
|
||||
assertNotEquals(defaultColor, mockTabCollection(-123L).getIconColor(testContext))
|
||||
}
|
||||
|
||||
private fun mockTabCollection(id: Long): TabCollection {
|
||||
val collection: TabCollection = mock()
|
||||
`when`(collection.id).thenReturn(id)
|
||||
return collection
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue