From 8c42edb37bb71fe3939edb3f285dca2d3243a369 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 11 Feb 2026 09:41:01 +0100 Subject: [PATCH] improve custom tabs toolbar --- .../ExternalAppBrowserFragment.kt | 18 ++-- .../widget/CustomTabToolbar.kt | 89 +++++++++++++++++-- .../main/res/drawable/custom_tab_menu_bg.xml | 7 +- .../src/main/res/layout/custom_tab_menu.xml | 68 +++++++------- .../main/res/layout/custom_tab_toolbar.xml | 50 +++++++---- 5 files changed, 165 insertions(+), 67 deletions(-) diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ExternalAppBrowserFragment.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ExternalAppBrowserFragment.kt index fea66766..d6e99066 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ExternalAppBrowserFragment.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ExternalAppBrowserFragment.kt @@ -148,13 +148,13 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler android.view.ViewGroup.LayoutParams.WRAP_CONTENT, true ).apply { - elevation = 8f + elevation = 10f isOutsideTouchable = true } activePopup = popup - val iconColor = MaterialColors.getColor(anchorView, com.google.android.material.R.attr.colorOnSurface) - val disabledColor = MaterialColors.getColor(anchorView, com.google.android.material.R.attr.colorOnSurfaceVariant) + val iconColor = MaterialColors.getColor(anchorView, com.google.android.material.R.attr.colorOnSurfaceVariant) + val disabledColor = MaterialColors.getColor(anchorView, com.google.android.material.R.attr.colorOutline) // Navigation row icons val canGoBack = customTab.content.canGoBack @@ -163,7 +163,7 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler val backBtn = menuView.findViewById(R.id.menuBack) val forwardBtn = menuView.findViewById(R.id.menuForward) - backBtn.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_arrow_left, 20, if (canGoBack) iconColor else disabledColor)) + backBtn.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_arrow_left, 22, if (canGoBack) iconColor else disabledColor)) backBtn.isEnabled = canGoBack backBtn.alpha = if (canGoBack) 1.0f else 0.38f backBtn.setOnClickListener { @@ -171,7 +171,7 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler popup.dismiss() } - forwardBtn.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_arrow_right, 20, if (canGoForward) iconColor else disabledColor)) + forwardBtn.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_arrow_right, 22, if (canGoForward) iconColor else disabledColor)) forwardBtn.isEnabled = canGoForward forwardBtn.alpha = if (canGoForward) 1.0f else 0.38f forwardBtn.setOnClickListener { @@ -181,13 +181,13 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler // Menu item icons menuView.findViewById(R.id.menuRefreshIcon) - .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_refresh, 20, iconColor)) + .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_refresh, 24, iconColor)) menuView.findViewById(R.id.menuShareIcon) - .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 20, iconColor)) + .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 24, iconColor)) menuView.findViewById(R.id.menuDesktopIcon) - .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_monitor, 20, iconColor)) + .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_monitor, 24, iconColor)) menuView.findViewById(R.id.menuOpenInBrowserIcon) - .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 20, iconColor)) + .setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 24, iconColor)) // Refresh menuView.findViewById(R.id.menuRefresh).setOnClickListener { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbar.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbar.kt index a150d60b..731317f9 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbar.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbar.kt @@ -7,12 +7,15 @@ package eu.weblibre.flutter_mozilla_components.widget import android.content.Context +import android.graphics.drawable.Drawable +import android.net.Uri import android.util.AttributeSet import android.view.View import android.widget.FrameLayout import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources import com.google.android.material.card.MaterialCardView import com.google.android.material.color.MaterialColors import com.mikepenz.iconics.IconicsDrawable @@ -44,6 +47,7 @@ class CustomTabToolbar @JvmOverloads constructor( private val toolbarCard: MaterialCardView private val closeButton: ImageButton private val securityIcon: ImageView + private val titleText: TextView private val urlText: TextView private val shareButton: ImageButton private val openInBrowserButton: ImageButton @@ -52,6 +56,7 @@ class CustomTabToolbar @JvmOverloads constructor( private var sessionId: String? = null private var store: BrowserStore? = null private var urlScope: CoroutineScope? = null + private var titleScope: CoroutineScope? = null private var securityScope: CoroutineScope? = null var onCloseListener: (() -> Unit)? = null @@ -65,6 +70,7 @@ class CustomTabToolbar @JvmOverloads constructor( toolbarCard = findViewById(R.id.toolbarCard) closeButton = findViewById(R.id.closeButton) securityIcon = findViewById(R.id.securityIcon) + titleText = findViewById(R.id.titleText) urlText = findViewById(R.id.urlText) shareButton = findViewById(R.id.shareButton) openInBrowserButton = findViewById(R.id.openInBrowserButton) @@ -86,17 +92,21 @@ class CustomTabToolbar @JvmOverloads constructor( toolbarColor?.let { applyCustomColors(it) } store.state.findCustomTab(sessionId)?.let { tab -> + updateTitle(tab) updateUrl(tab) updateSecurityIcon(tab) } observeUrlChanges() + observeTitleChanges() observeSecurityChanges() } fun unbind() { urlScope?.cancel() urlScope = null + titleScope?.cancel() + titleScope = null securityScope?.cancel() securityScope = null } @@ -109,6 +119,7 @@ class CustomTabToolbar @JvmOverloads constructor( val surfaceColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface) val onSurfaceColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface) toolbarCard.setCardBackgroundColor(surfaceColor) + titleText.setTextColor(onSurfaceColor) urlText.setTextColor(onSurfaceColor) } @@ -117,7 +128,10 @@ class CustomTabToolbar @JvmOverloads constructor( closeButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_close, 20, iconColor)) shareButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 18, iconColor)) - openInBrowserButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 18, iconColor)) + openInBrowserButton.setImageDrawable( + weblibreLogoIcon() + ?: mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 18, iconColor) + ) menuButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_dots_vertical, 18, iconColor)) } @@ -128,14 +142,31 @@ class CustomTabToolbar @JvmOverloads constructor( } else { android.graphics.Color.BLACK } + titleText.setTextColor(textColor) urlText.setTextColor(textColor) closeButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_close, 20, textColor)) shareButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 18, textColor)) - openInBrowserButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 18, textColor)) + openInBrowserButton.setImageDrawable( + weblibreLogoIcon() + ?: mdiIcon(CommunityMaterial.Icon3.cmd_open_in_new, 18, textColor) + ) menuButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_dots_vertical, 18, textColor)) } + private fun weblibreLogoIcon(): Drawable? = runCatching { + val resId = context.resources.getIdentifier( + "ic_launcher_foreground", + "drawable", + context.packageName, + ) + if (resId == 0) { + null + } else { + AppCompatResources.getDrawable(context, resId) + } + }.getOrNull()?.mutate() + private fun isDarkColor(color: Int): Boolean { val darkness = 1 - (0.299 * android.graphics.Color.red(color) + 0.587 * android.graphics.Color.green(color) + @@ -151,7 +182,22 @@ class CustomTabToolbar @JvmOverloads constructor( flow .mapNotNull { state -> state.findCustomTab(sessionId) } .ifAnyChanged { tab -> arrayOf(tab.content.url) } - .collect { tab -> updateUrl(tab) } + .collect { tab -> + updateUrl(tab) + updateTitle(tab) + } + } + } + + private fun observeTitleChanges() { + val sessionId = this.sessionId ?: return + val store = this.store ?: return + + titleScope = store.flowScoped { flow -> + flow + .mapNotNull { state -> state.findCustomTab(sessionId) } + .ifAnyChanged { tab -> arrayOf(tab.content.title) } + .collect { tab -> updateTitle(tab) } } } @@ -162,22 +208,51 @@ class CustomTabToolbar @JvmOverloads constructor( securityScope = store.flowScoped { flow -> flow .mapNotNull { state -> state.findCustomTab(sessionId) } - .ifAnyChanged { tab -> arrayOf(tab.content.securityInfo.isSecure) } + .ifAnyChanged { + tab -> arrayOf( + tab.content.securityInfo.isSecure, + tab.content.securityInfo.host, + tab.content.loading + ) + } .collect { tab -> updateSecurityIcon(tab) } } } + private fun updateTitle(tab: CustomTabSessionState) { + val title = tab.content.title + titleText.text = if (title.isBlank()) { + displayHost(tab.content.url) + } else { + title + } + } + private fun updateUrl(tab: CustomTabSessionState) { - urlText.text = URLStringUtils.toDisplayUrl(tab.content.url) + urlText.text = displayHost(tab.content.url) + } + + private fun displayHost(url: String): String { + val host = Uri.parse(url).host + return if (!host.isNullOrBlank()) { + host + } else { + URLStringUtils.toDisplayUrl(url).toString() + } } private fun updateSecurityIcon(tab: CustomTabSessionState) { - if (tab.content.securityInfo.isSecure) { + val securityInfoKnown = tab.content.securityInfo.host.isNotBlank() + + if (tab.content.loading || !securityInfoKnown) { + val color = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurfaceVariant) + securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock_open_outline, 16, color)) + } else if (tab.content.securityInfo.isSecure) { val color = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurfaceVariant) securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock, 16, color)) } else { val color = MaterialColors.getColor(this, android.R.attr.colorError) - securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_web, 16, color)) + securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock_open_outline, 16, color)) } } diff --git a/packages/flutter_mozilla_components/android/src/main/res/drawable/custom_tab_menu_bg.xml b/packages/flutter_mozilla_components/android/src/main/res/drawable/custom_tab_menu_bg.xml index c4104c63..d9d7ab52 100644 --- a/packages/flutter_mozilla_components/android/src/main/res/drawable/custom_tab_menu_bg.xml +++ b/packages/flutter_mozilla_components/android/src/main/res/drawable/custom_tab_menu_bg.xml @@ -5,6 +5,9 @@ - - + + + diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_menu.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_menu.xml index 1452ff4c..a2e29f38 100644 --- a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_menu.xml +++ b/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_menu.xml @@ -8,29 +8,29 @@ android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/custom_tab_menu_bg" - android:paddingVertical="4dp" - android:minWidth="192dp"> + android:paddingVertical="8dp" + android:minWidth="220dp"> + android:paddingHorizontal="14dp"> @@ -40,33 +40,33 @@ + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="14dp" /> @@ -74,25 +74,25 @@ + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="14dp" /> @@ -100,26 +100,26 @@ + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="14dp" /> + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="14dp" /> diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_toolbar.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_toolbar.xml index 6491a349..506e8cdf 100644 --- a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_toolbar.xml +++ b/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_toolbar.xml @@ -13,8 +13,8 @@ @@ -35,31 +35,50 @@ android:scaleType="center" android:contentDescription="@string/mozac_feature_customtabs_exit_button" /> - + + android:paddingHorizontal="8dp" + android:paddingVertical="6dp"> - + + + + + + @@ -76,7 +95,8 @@ android:layout_width="40dp" android:layout_height="40dp" android:background="?attr/selectableItemBackgroundBorderless" - android:scaleType="center" + android:padding="8dp" + android:scaleType="centerInside" android:contentDescription="@string/custom_tab_open_in_browser" />