Commit 6d4d5aa4 authored by Tiger Oakes's avatar Tiger Oakes Committed by Tiger Oakes
Browse files

Store browser-icons constants in res

parent d52a3bf5
......@@ -39,10 +39,10 @@ dependencies {
implementation project(':concept-fetch')
implementation project(':browser-session')
implementation Dependencies.kotlin_stdlib
implementation Dependencies.kotlin_coroutines
implementation Dependencies.androidx_annotation
implementation Dependencies.androidx_palette
implementation Dependencies.kotlin_stdlib
implementation Dependencies.kotlin_coroutines
implementation Dependencies.thirdparty_disklrucache
testImplementation project(':support-test')
......
......@@ -37,7 +37,6 @@ import mozilla.components.browser.session.utils.AllSessionsObserver
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.fetch.Client
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.util.dpToPx
import java.util.concurrent.Executors
private const val MAXIMUM_SCALE_FACTOR = 2.0f
......@@ -57,7 +56,7 @@ internal val sharedDiskCache = DiskCache()
class BrowserIcons(
private val context: Context,
private val httpClient: Client,
private val generator: IconGenerator = DefaultIconGenerator(context),
private val generator: IconGenerator = DefaultIconGenerator(),
private val preparers: List<IconPreprarer> = listOf(
TippyTopIconPreparer(context.assets),
MemoryIconPreparer(sharedMemoryCache),
......@@ -94,7 +93,7 @@ class BrowserIcons(
private fun loadIconInternal(initialRequest: IconRequest): Icon {
val desiredSize = DesiredSize(
targetSize = initialRequest.size.value.dpToPx(context.resources.displayMetrics),
targetSize = context.resources.getDimensionPixelSize(initialRequest.size.dimen),
maxSize = maximumSize,
maxScaleFactor = MAXIMUM_SCALE_FACTOR
)
......
......@@ -4,6 +4,7 @@
package mozilla.components.browser.icons
import androidx.annotation.DimenRes
import mozilla.components.concept.engine.manifest.Size as HtmlSize
/**
......@@ -23,12 +24,9 @@ data class IconRequest(
*
* We are trying to limit the supported sizes in order to optimize our caching strategy.
*/
@Suppress("MagicNumber")
enum class Size(
val value: Int
) {
DEFAULT(32),
LAUNCHER(48)
enum class Size(@DimenRes val dimen: Int) {
DEFAULT(R.dimen.mozac_browser_icons_size_default),
LAUNCHER(R.dimen.mozac_browser_icons_size_launcher)
}
/**
......
......@@ -5,47 +5,50 @@
package mozilla.components.browser.icons.generator
import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.net.Uri
import android.util.TypedValue
import androidx.annotation.ArrayRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.core.content.ContextCompat
import mozilla.components.browser.icons.Icon
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.icons.R
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import mozilla.components.support.ktx.android.util.dpToFloat
import mozilla.components.support.ktx.android.util.dpToPx
/**
* [IconGenerator] implementation that will generate an icon with a background color, rounded corners and a letter
* representing the URL.
*/
class DefaultIconGenerator(
context: Context,
cornerRadius: Int = DEFAULT_CORNER_RADIUS,
private val textColor: Int = Color.WHITE,
private val backgroundColors: IntArray = DEFAULT_COLORS
@DimenRes private val cornerRadiusDimen: Int = R.dimen.mozac_browser_icons_generator_default_corner_radius,
@ColorRes private val textColorRes: Int = R.color.mozac_browser_icons_generator_default_text_color,
@ArrayRes private val backgroundColorsRes: Int = R.array.mozac_browser_icons_photon_palette
) : IconGenerator {
private val cornerRadius: Float = cornerRadius.dpToFloat(context.resources.displayMetrics)
@Suppress("MagicNumber")
override fun generate(context: Context, request: IconRequest): Icon {
val size = request.size.value.dpToPx(context.resources.displayMetrics)
val size = context.resources.getDimension(request.size.dimen)
val sizePx = size.toInt()
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val bitmap = Bitmap.createBitmap(sizePx, sizePx, ARGB_8888)
val canvas = Canvas(bitmap)
val backgroundColor = pickColor(request.url)
val backgroundColor = pickColor(context.resources, request.url)
val paint = Paint()
paint.color = backgroundColor
canvas.drawRoundRect(
RectF(0f, 0f, size.toFloat(), size.toFloat()), cornerRadius, cornerRadius, paint)
paint.color = textColor
val sizeRect = RectF(0f, 0f, size, size)
val cornerRadius = context.resources.getDimension(cornerRadiusDimen)
canvas.drawRoundRect(sizeRect, cornerRadius, cornerRadius, paint)
val character = getRepresentativeCharacter(request.url)
......@@ -53,37 +56,47 @@ class DefaultIconGenerator(
// size of 112dp we'd use a text size of 14dp (112 / 8).
val textSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
size.toFloat() / 8.0f,
size / 8.0f,
context.resources.displayMetrics
)
paint.color = ContextCompat.getColor(context, textColorRes)
paint.textAlign = Paint.Align.CENTER
paint.textSize = textSize
paint.isAntiAlias = true
canvas.drawText(
character,
canvas.width / 2.0f,
(canvas.height / 2.0f) - ((paint.descent() + paint.ascent()) / 2.0f),
canvas.width / 2f,
(canvas.height / 2f) - ((paint.descent() + paint.ascent()) / 2f),
paint
)
return Icon(bitmap = bitmap, color = backgroundColor, source = Icon.Source.GENERATOR)
return Icon(
bitmap = bitmap,
color = backgroundColor,
source = Icon.Source.GENERATOR
)
}
/**
* Return a color for this [url]. Colors will be based on the host. URLs with the same host will
* return the same color.
*/
internal fun pickColor(url: String): Int {
if (url.isEmpty()) {
return backgroundColors[0]
@ColorInt
internal fun pickColor(resources: Resources, url: String): Int {
val backgroundColors = resources.obtainTypedArray(backgroundColorsRes)
val color = if (url.isEmpty()) {
backgroundColors.getColor(0, 0)
} else {
val snippet = getRepresentativeSnippet(url)
val index = Math.abs(snippet.hashCode() % backgroundColors.length())
backgroundColors.getColor(index, 0)
}
val snippet = getRepresentativeSnippet(url)
val index = Math.abs(snippet.hashCode() % backgroundColors.size)
return backgroundColors[index]
backgroundColors.recycle()
return color
}
/**
......@@ -123,13 +136,4 @@ class DefaultIconGenerator(
return "?"
}
companion object {
// Mozilla's Visual Design Colour Palette
// http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
private val DEFAULT_COLORS =
intArrayOf(-0x65b400, -0x54ff73, -0xb3ff64, -0xffd164, -0xff613e, -0xff62fe, -0xae5500, -0xc9c7a6)
private const val DEFAULT_CORNER_RADIUS = 2
}
}
<?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/. -->
<resources>
<color name="mozac_browser_icons_generator_default_text_color">@android:color/white</color>
<!-- Mozilla's Visual Design Colour Palette: http://firefoxux.github.io/StyleGuide/#/visualDesign/colours -->
<array name="mozac_browser_icons_photon_palette">
<item>#9A4C00</item>
<item>#AB008D</item>
<item>#4C009C</item>
<item>#002E9C</item>
<item>#009EC2</item>
<item>#009D02</item>
<item>#51AB00</item>
<item>#36385A</item>
</array>
</resources>
......@@ -3,5 +3,9 @@
- 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>
<dimen name="mozac_browser_icons_size_default">32dp</dimen>
<dimen name="mozac_browser_icons_size_launcher">48dp</dimen>
<dimen name="mozac_browser_icons_maximum_size">64dp</dimen>
<dimen name="mozac_browser_icons_generator_default_corner_radius">2dp</dimen>
</resources>
......@@ -22,7 +22,7 @@ class DefaultIconGeneratorTest {
@Test
fun getRepresentativeCharacter() = runBlocking {
val generator = DefaultIconGenerator(testContext)
val generator = DefaultIconGenerator()
assertEquals("M", generator.getRepresentativeCharacter("https://mozilla.org"))
assertEquals("W", generator.getRepresentativeCharacter("http://wikipedia.org"))
......@@ -72,28 +72,29 @@ class DefaultIconGeneratorTest {
@Test
fun pickColor() {
val generator = DefaultIconGenerator(testContext)
val generator = DefaultIconGenerator()
val res = testContext.resources
val color = generator.pickColor("http://m.facebook.com")
val color = generator.pickColor(res, "http://m.facebook.com")
// Color does not change
for (i in 0..99) {
assertEquals(color, generator.pickColor("http://m.facebook.com"))
assertEquals(color, generator.pickColor(res, "http://m.facebook.com"))
}
// Color is stable for "similar" hosts.
assertEquals(color, generator.pickColor("https://m.facebook.com"))
assertEquals(color, generator.pickColor("http://facebook.com"))
assertEquals(color, generator.pickColor("http://www.facebook.com"))
assertEquals(color, generator.pickColor("http://www.facebook.com/foo/bar/foobar?mobile=1"))
assertEquals(color, generator.pickColor(res, "https://m.facebook.com"))
assertEquals(color, generator.pickColor(res, "http://facebook.com"))
assertEquals(color, generator.pickColor(res, "http://www.facebook.com"))
assertEquals(color, generator.pickColor(res, "http://www.facebook.com/foo/bar/foobar?mobile=1"))
// Returns a color for an empty string
assertNotEquals(0, generator.pickColor(""))
assertNotEquals(0, generator.pickColor(res, ""))
}
@Test
fun generate() = runBlocking {
val generator = DefaultIconGenerator(testContext)
val generator = DefaultIconGenerator()
val icon = generator.generate(testContext, IconRequest(
url = "https://m.facebook.com"))
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment