diff --git a/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt b/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt index 313c7dbf7..7f9321a50 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/SaveCollectionListAdapter.kt @@ -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 -) : AdapterWithJob() { +) : RecyclerView.Adapter() { private var tabCollections = listOf() private var selectedTabs: Set = 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, - 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 } } diff --git a/app/src/main/java/org/mozilla/fenix/ext/TabCollection.kt b/app/src/main/java/org/mozilla/fenix/ext/TabCollection.kt new file mode 100644 index 000000000..b871cb6c9 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ext/TabCollection.kt @@ -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 +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index eb1884187..3af79fa2d 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -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) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt index 76183890f..e1fd3c840 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt @@ -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, - 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 diff --git a/app/src/main/res/drawable/collection_home_list_row_background.xml b/app/src/main/res/drawable/collection_home_list_row_background.xml new file mode 100644 index 000000000..ded6ed654 --- /dev/null +++ b/app/src/main/res/drawable/collection_home_list_row_background.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_chevron.xml b/app/src/main/res/drawable/ic_chevron.xml new file mode 100644 index 000000000..8ca3aae73 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/rounded_all_corners.xml b/app/src/main/res/drawable/rounded_all_corners.xml index 534bdbc5b..dd7f603ea 100644 --- a/app/src/main/res/drawable/rounded_all_corners.xml +++ b/app/src/main/res/drawable/rounded_all_corners.xml @@ -2,8 +2,7 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/drawable/rounded_top_corners.xml b/app/src/main/res/drawable/rounded_top_corners.xml index f83a5b4b9..224aa97dc 100644 --- a/app/src/main/res/drawable/rounded_top_corners.xml +++ b/app/src/main/res/drawable/rounded_top_corners.xml @@ -2,8 +2,7 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/collection_home_list_row.xml b/app/src/main/res/layout/collection_home_list_row.xml index 91b4c036a..0f6f38a38 100644 --- a/app/src/main/res/layout/collection_home_list_row.xml +++ b/app/src/main/res/layout/collection_home_list_row.xml @@ -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" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4893aa787..e43001c95 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -102,6 +102,13 @@ @color/collection_icon_color_pink_light_theme @color/collection_icon_color_green_light_theme @color/collection_icon_color_yellow_light_theme + + @color/collection_icon_color_violet + @color/collection_icon_color_blue + @color/collection_icon_color_pink + @color/collection_icon_color_green + @color/collection_icon_color_yellow + #B9F0FD diff --git a/app/src/test/java/org/mozilla/fenix/ext/TabCollectionTest.kt b/app/src/test/java/org/mozilla/fenix/ext/TabCollectionTest.kt new file mode 100644 index 000000000..1ef549575 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/ext/TabCollectionTest.kt @@ -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 + } +}