Commit 0835821a authored by Sawyer Blatz's avatar Sawyer Blatz Committed by Sebastian Kaspari
Browse files

Closes #2615: Adds titleView to DisplayToolbar

parent caaf992d
......@@ -192,6 +192,15 @@ class BrowserToolbar @JvmOverloads constructor(
displayToolbar.progressBarGravity = value
}
/**
* Sets the colour of the text for title displayed in the toolbar.
*/
var titleColor: Int
get() = displayToolbar.urlView.currentTextColor
set(value) {
displayToolbar.titleView.setTextColor(value)
}
/**
* Sets the colour of the text for the URL/search term displayed in the toolbar.
*/
......@@ -202,6 +211,15 @@ class BrowserToolbar @JvmOverloads constructor(
editToolbar.urlView.setTextColor(value)
}
/**
* Sets the size of the text for the title displayed in the toolbar.
*/
var titleTextSize: Float
get() = displayToolbar.titleView.textSize
set(value) {
displayToolbar.titleView.textSize = value
}
/**
* Sets the size of the text for the URL/search term displayed in the toolbar.
*/
......@@ -296,6 +314,13 @@ class BrowserToolbar @JvmOverloads constructor(
private var searchTerms: String = ""
private var urlCommitListener: ((String) -> Boolean)? = null
override var title: String = ""
set(value) {
displayToolbar.updateTitle(value)
field = value
}
override var url: CharSequence = ""
set(value) {
// We update the display toolbar immediately. We do not do that for the edit toolbar to not
......
......@@ -10,6 +10,7 @@ import android.support.v4.content.ContextCompat
import android.support.v7.widget.AppCompatImageButton
import android.support.v7.widget.AppCompatImageView
import android.support.v7.widget.AppCompatTextView
import android.text.TextUtils
import android.util.TypedValue
import android.view.Gravity
import android.view.View
......@@ -66,7 +67,7 @@ import mozilla.components.ui.icons.R.drawable.mozac_ic_lock
*
*/
@SuppressLint("ViewConstructor") // This view is only instantiated in code
@Suppress("LargeClass")
@Suppress("LargeClass", "TooManyFunctions")
internal class DisplayToolbar(
context: Context,
val toolbar: BrowserToolbar
......@@ -89,6 +90,16 @@ internal class DisplayToolbar(
setOnClickListener(null)
}
internal val titleView = AppCompatTextView(context).apply {
id = R.id.mozac_browser_toolbar_title_view
gravity = Gravity.CENTER_VERTICAL
textSize = URL_TEXT_SIZE
visibility = View.GONE
ellipsize = TextUtils.TruncateAt.END
setSingleLine(true)
}
internal val urlView = AppCompatTextView(context).apply {
id = R.id.mozac_browser_toolbar_url_view
gravity = Gravity.CENTER_VERTICAL
......@@ -195,11 +206,20 @@ internal class DisplayToolbar(
init {
addView(siteSecurityIconView)
addView(titleView)
addView(urlView)
addView(menuView)
addView(progressView)
}
/**
* Updates the title to be displayed.
*/
fun updateTitle(title: String) {
titleView.text = title
titleView.visibility = if (title.isEmpty()) View.GONE else View.VISIBLE
}
/**
* Updates the URL to be displayed.
*/
......@@ -306,7 +326,8 @@ internal class DisplayToolbar(
val height = MeasureSpec.getSize(heightMeasureSpec)
val fixedHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
val halfFixedHeightSpec = MeasureSpec
.makeMeasureSpec(height / MEASURED_HEIGHT_DENOMINATOR, MeasureSpec.EXACTLY)
setMeasuredDimension(width, height)
// The icon and menu fill the whole height and have a square shape
......@@ -325,7 +346,15 @@ internal class DisplayToolbar(
val urlWidth = (width - iconSize - browserActionsWidth - pageActionsWidth -
menuWidth - navigationActionsWidth - 2 * urlBoxMargin)
val urlWidthSpec = MeasureSpec.makeMeasureSpec(urlWidth, MeasureSpec.EXACTLY)
urlView.measure(urlWidthSpec, fixedHeightSpec)
if (titleView.isVisible()) {
// With a title view, the url and title split the rest of the space vertically
titleView.measure(urlWidthSpec, halfFixedHeightSpec)
urlView.measure(urlWidthSpec, halfFixedHeightSpec)
} else {
// With no title view, the url view takes up the rest of the space
urlView.measure(urlWidthSpec, fixedHeightSpec)
}
val progressHeightSpec = MeasureSpec.makeMeasureSpec(resources.pxToDp(PROGRESS_BAR_HEIGHT_DP),
MeasureSpec.EXACTLY)
......@@ -427,10 +456,32 @@ internal class DisplayToolbar(
// | navigation | icon | url [ page ] | browser | menu |
// | actions | | [ actions ] | actions | |
// +-------------+------+-----------------------+----------+------+
val iconWidth = if (siteSecurityIconView.isVisible()) siteSecurityIconView.measuredWidth else 0
val urlLeft = navigationActionsWidth + iconWidth + urlBoxMargin
urlView.layout(urlLeft, 0, urlLeft + urlView.measuredWidth, measuredHeight)
// If the titleView is visible, it will appear above the URL:
// +-------------+------+-----------------------+----------+------+
// | navigation | icon | title [ page ] | browser | menu |
// | actions | | url [ actions ] | actions | |
// +-------------+------+-----------------------+----------+------+
if (titleView.isVisible()) {
val totalTextHeights = urlView.measuredHeight + titleView.measuredHeight
val totalAvailablePadding = height - totalTextHeights
val padding = totalAvailablePadding / MEASURED_HEIGHT_DENOMINATOR
titleView.layout(
urlLeft,
padding,
urlLeft + titleView.measuredWidth,
padding + titleView.measuredHeight)
urlView.layout(
urlLeft,
padding + titleView.measuredHeight,
urlLeft + urlView.measuredWidth,
padding + titleView.measuredHeight + urlView.measuredHeight)
} else {
urlView.layout(urlLeft, 0, urlLeft + urlView.measuredWidth, measuredHeight)
}
// The progress bar by default is going to be drawn at the bottom of the toolbar, top if defined:
progressView.layout(
......@@ -449,6 +500,7 @@ internal class DisplayToolbar(
}
companion object {
internal const val MEASURED_HEIGHT_DENOMINATOR = 2
internal const val URL_FADING_EDGE_SIZE_DP = 24
const val BOTTOM_PROGRESS_BAR = 0
......
......@@ -6,6 +6,7 @@
-->
<resources>
<item name="mozac_browser_toolbar_title_view" type="id"/>
<item name="mozac_browser_toolbar_url_view" type="id"/>
<item name="mozac_browser_toolbar_edit_url_view" type="id"/>
<item name="mozac_browser_toolbar_clear_view" type="id"/>
......
......@@ -742,6 +742,45 @@ class BrowserToolbarTest {
assertEquals(12f, toolbar.editToolbar.urlView.textSize)
}
@Test
fun `titleTextSize changes display titleView`() {
val toolbar = BrowserToolbar(context)
assertTrue(toolbar.displayToolbar.titleView.textSize != 12f)
toolbar.titleTextSize = 12f
assertEquals(12f, toolbar.displayToolbar.titleView.textSize)
}
@Test
fun `titleTextColor changes display titleView`() {
val toolbar = BrowserToolbar(context)
toolbar.titleColor = R.color.photonBlue40
assertEquals(R.color.photonBlue40, toolbar.displayToolbar.titleView.currentTextColor)
}
@Test
fun `titleView visibility is based on being set`() {
val toolbar = BrowserToolbar(context)
assertEquals(toolbar.displayToolbar.titleView.visibility, View.GONE)
toolbar.title = "Mozilla"
assertEquals(toolbar.displayToolbar.titleView.visibility, View.VISIBLE)
toolbar.title = ""
assertEquals(toolbar.displayToolbar.titleView.visibility, View.GONE)
}
@Test
fun `titleView text is set properly`() {
val toolbar = BrowserToolbar(context)
toolbar.title = "Mozilla"
assertEquals(toolbar.displayToolbar.titleView.text, "Mozilla")
}
@Test
fun `typeface changes edit and display urlView`() {
val toolbar = BrowserToolbar(context)
......
......@@ -605,6 +605,102 @@ class DisplayToolbarTest {
assertEquals(624, viewRect.width())
}
@Test
fun `titleView does not display when there is no title text`() {
val toolbar = mock(BrowserToolbar::class.java)
val displayToolbar = DisplayToolbar(context, toolbar)
val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST)
val heightSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST)
displayToolbar.measure(widthSpec, heightSpec)
displayToolbar.layout(0, 0, 1024, 200)
val urlView = displayToolbar.urlView
val titleView = displayToolbar.titleView
val urlViewRect = Rect(urlView.left, urlView.top, urlView.right, urlView.bottom)
val titleViewRect = Rect(titleView.left, titleView.top, titleView.right, titleView.bottom)
assertTrue(urlViewRect.width() > 0)
assertTrue(urlViewRect.height() > 0)
assertTrue(titleViewRect.width() == 0)
assertTrue(titleViewRect.height() == 0)
assertEquals(titleView.visibility, View.GONE)
}
@Test
fun `titleView is properly laid out when there is title text`() {
val toolbar = mock(BrowserToolbar::class.java)
val displayToolbar = DisplayToolbar(context, toolbar)
displayToolbar.updateTitle("Mozilla")
assertEquals(displayToolbar.titleView.visibility, View.VISIBLE)
val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST)
val heightSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST)
displayToolbar.measure(widthSpec, heightSpec)
displayToolbar.layout(0, 0, 1024, 200)
val urlView = displayToolbar.urlView
val titleView = displayToolbar.titleView
val urlViewRect = Rect(urlView.left, urlView.top, urlView.right, urlView.bottom)
val titleViewRect = Rect(titleView.left, titleView.top, titleView.right, titleView.bottom)
assertTrue(urlViewRect.width() > 0)
assertTrue(urlViewRect.height() > 0)
assertTrue(titleViewRect.width() > 0)
assertTrue(titleViewRect.height() > 0)
val totalTextHeights = urlViewRect.height() + titleViewRect.height()
val totalAvailablePadding = 200 - totalTextHeights
val padding = totalAvailablePadding / DisplayToolbar.MEASURED_HEIGHT_DENOMINATOR
assertTrue(titleViewRect.left == urlViewRect.left)
assertTrue(titleViewRect.top == padding)
assertTrue(titleViewRect.right == urlViewRect.right)
assertTrue(titleViewRect.bottom == padding + titleViewRect.height())
}
@Test
fun `urlView is properly laid out when a title is shown`() {
val toolbar = mock(BrowserToolbar::class.java)
val displayToolbar = DisplayToolbar(context, toolbar)
displayToolbar.updateTitle("Mozilla")
val widthSpec = View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST)
val heightSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.AT_MOST)
displayToolbar.measure(widthSpec, heightSpec)
displayToolbar.layout(0, 0, 1024, 200)
val urlView = displayToolbar.urlView
val titleView = displayToolbar.titleView
val titleViewRect = Rect(titleView.left, titleView.top, titleView.right, titleView.bottom)
val urlViewRect = Rect(urlView.left, urlView.top, urlView.right, urlView.bottom)
val totalTextHeights = urlViewRect.height() + titleViewRect.height()
val totalAvailablePadding = 200 - totalTextHeights
val padding = totalAvailablePadding / DisplayToolbar.MEASURED_HEIGHT_DENOMINATOR
assertTrue(urlViewRect.width() > 0)
assertTrue(urlViewRect.height() > 0)
assertTrue(titleViewRect.width() > 0)
assertTrue(titleViewRect.height() > 0)
assertTrue(urlViewRect.left == titleViewRect.left)
assertTrue(urlViewRect.top == padding + titleViewRect.height())
assertTrue(urlViewRect.right == titleView.right)
assertTrue(urlViewRect.bottom == padding + titleViewRect.height() + urlViewRect.height())
}
@Test
fun `toolbar only switches to editing mode if onUrlClicked returns true`() {
val toolbar = mock(BrowserToolbar::class.java)
......
......@@ -21,6 +21,11 @@ import java.lang.ref.WeakReference
*/
@Suppress("TooManyFunctions")
interface Toolbar {
/**
* Sets/Gets the title to be displayed on the toolbar.
*/
var title: String
/**
* Sets/Gets the URL to be displayed on the toolbar.
*/
......
......@@ -43,7 +43,10 @@ class CustomTabsToolbarFeature(
if (initialized) {
return
}
initialized = sessionManager.runWithSession(sessionId) { initialize(it) }
initialized = sessionManager.runWithSession(sessionId) {
it.register(sessionObserver)
initialize(it)
}
}
@VisibleForTesting
......@@ -65,6 +68,10 @@ class CustomTabsToolbarFeature(
if (config.showShareMenuItem) addShareButton(session)
// Add menu items
if (config.menuItems.isNotEmpty()) addMenuItems(config.menuItems)
// Explicitly set the title regardless of the customTabConfig settings
toolbar.titleTextSize = TITLE_TEXT_SIZE
return true
}
return false
......@@ -75,6 +82,7 @@ class CustomTabsToolbarFeature(
toolbarColor?.let { color ->
toolbar.setBackgroundColor(color)
toolbar.textColor = readableColor
toolbar.titleColor = readableColor
toolbar.siteSecurityColor = Pair(readableColor, readableColor)
toolbar.menuViewColor = readableColor
}
......@@ -134,7 +142,20 @@ class CustomTabsToolbarFeature(
}
}
override fun stop() {}
private val sessionObserver = object : Session.Observer {
override fun onTitleChanged(session: Session, title: String) {
// Only shrink the urlTextSize if a title is displayed
toolbar.textSize = URL_TEXT_SIZE
toolbar.title = title
}
}
override fun stop() {
sessionManager.runWithSession(sessionId) {
it.unregister(sessionObserver)
true
}
}
/**
* When the back button is pressed if not initialized returns false,
......@@ -154,6 +175,8 @@ class CustomTabsToolbarFeature(
}
companion object {
const val TITLE_TEXT_SIZE = 16f
const val URL_TEXT_SIZE = 12f
@Suppress("MagicNumber")
internal fun getReadableTextColor(backgroundColor: Int): Int {
val greyValue = greyscaleFromRGB(backgroundColor)
......
......@@ -63,10 +63,25 @@ class ToolbarFeature(
* @property registrableDomainColor Text color that should be used for the registrable domain of the URL (see
* [PublicSuffixList.getPublicSuffixPlusOne] for an explanation of "registrable domain".
* @property urlColor Optional text color used for the URL.
* @property renderStyle Sealed class that controls the style of the url to be displayed
*/
data class UrlRenderConfiguration(
internal val publicSuffixList: PublicSuffixList,
@ColorInt internal val registrableDomainColor: Int,
@ColorInt internal val urlColor: Int? = null
@ColorInt internal val urlColor: Int? = null,
internal val renderStyle: RenderStyle = RenderStyle.ColoredUrl
)
/**
* Controls how the url should be styled
*
* RegistrableDomain: displays only the url, uncolored
* ColoredUrl: displays the registrableDomain with color and url with another color
* UncoloredUrl: displays the full url, uncolored
*/
sealed class RenderStyle {
object RegistrableDomain : RenderStyle()
object ColoredUrl : RenderStyle()
object UncoloredUrl : RenderStyle()
}
}
......@@ -59,8 +59,23 @@ internal class URLRenderer(
internal suspend fun updateUrl(url: String) {
if (url.isEmpty() || configuration == null) {
setUncoloredUrl(url)
return
}
when (configuration.renderStyle) {
ToolbarFeature.RenderStyle.UncoloredUrl -> setUncoloredUrl(url)
ToolbarFeature.RenderStyle.ColoredUrl -> setColoredUrl(url, configuration)
ToolbarFeature.RenderStyle.RegistrableDomain -> setRegistrableDomainUrl(url)
}
}
private suspend fun setRegistrableDomainUrl(url: String) {
val host = url.toUri().host
if (!host.isNullOrEmpty()) {
toolbar.url = getRegistrableDomain(host) ?: url
} else {
setColoredUrl(url, configuration)
toolbar.url = url
}
}
......@@ -76,6 +91,10 @@ internal class URLRenderer(
private fun setUncoloredUrl(url: String) {
toolbar.url = url
}
private suspend fun getRegistrableDomain(host: String): CharSequence? {
return configuration?.publicSuffixList?.getPublicSuffixPlusOne(host)?.await()
}
}
private suspend fun SpannableStringBuilder.colorRegistrableDomain(
......
......@@ -30,6 +30,7 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class ToolbarAutocompleteFeatureTest {
class TestToolbar : Toolbar {
override var title: String = ""
override var url: CharSequence = ""
override var siteSecure: Toolbar.SiteSecurity = Toolbar.SiteSecurity.INSECURE
override var private: Boolean = false
......
......@@ -23,6 +23,7 @@ class ToolbarInteractorTest {
override var url: CharSequence = ""
override var siteSecure: Toolbar.SiteSecurity = Toolbar.SiteSecurity.INSECURE
override var private: Boolean = false
override var title: String = ""
override fun setSearchTerms(searchTerms: String) {
fail()
......
......@@ -12,6 +12,11 @@ permalink: /changelog/
* [Gecko](https://github.com/mozilla-mobile/android-components/blob/master/buildSrc/src/main/java/Gecko.kt)
* [Configuration](https://github.com/mozilla-mobile/android-components/blob/master/buildSrc/src/main/java/Config.kt)
* **browser-toolbar**
* Added `titleView` to `DisplayToolbar` which displays the title of the page. Various options are able to modified such as
`titleTextSize`, `titleColor`, and `displayTitle`. In custom tabs, the URL will now only display the hostname.
* Changed `UrlRenderConfiguration` to include a `RenderStyle` parameter where you can specify how the URL renders
* **support-ktx**
* Added extension property `Uri.isHttpOrHttps`.
......
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