diff --git a/.gitignore b/.gitignore
index cc3ff799..7ee12cf5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
build/
pubspec.lock
CLAUDE.md
+AGENTS.md
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 bf1df2be..4e4fd560 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
@@ -235,7 +235,9 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
private fun openInBrowser(sessionId: String) {
val activity = requireActivity()
val store = components.core.store
- val url = store.state.findCustomTab(sessionId)?.content?.url ?: return
+ val customTab = store.state.findCustomTab(sessionId) ?: return
+ val url = customTab.content.url
+ val isPrivateCustomTab = customTab.content.private
sessionFeature?.get()?.release()
@@ -243,6 +245,7 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
val mainIntent = Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)).apply {
setClassName(activity, "eu.weblibre.gecko.MainActivity")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(PRIVATE_BROWSING_MODE, isPrivateCustomTab)
}
activity.startActivity(mainIntent)
@@ -354,6 +357,7 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
companion object {
private const val CUSTOM_TAB_SESSION_ID_KEY = "custom_tab_session_id"
private const val WEB_APP_MANIFEST_URL_KEY = "web_app_manifest_url"
+ private const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
fun create(
customTabSessionId: String,
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/AuthIntentReceiverActivity.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/AuthIntentReceiverActivity.kt
index 0085bc72..f03491ec 100644
--- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/AuthIntentReceiverActivity.kt
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/AuthIntentReceiverActivity.kt
@@ -11,6 +11,7 @@ import eu.weblibre.flutter_mozilla_components.GlobalComponents
class AuthIntentReceiverActivity : Activity() {
companion object {
private const val TAG = "AuthIntentReceiver"
+ private const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -32,7 +33,7 @@ class AuthIntentReceiverActivity : Activity() {
val processed = CustomTabIntentProcessor(
components.useCases.customTabsUseCases.add,
resources,
- isPrivate = false,
+ isPrivate = sourceIntent.getBooleanExtra(PRIVATE_BROWSING_MODE, false),
).process(sourceIntent)
if (processed) {
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/IntentReceiverActivity.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/IntentReceiverActivity.kt
index c4331f46..bbad82fe 100644
--- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/IntentReceiverActivity.kt
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/IntentReceiverActivity.kt
@@ -42,6 +42,7 @@ import java.io.File
class IntentReceiverActivity : Activity() {
companion object {
private const val TAG = "IntentReceiverActivity"
+ private const val PRIVATE_BROWSING_MODE = "private_browsing_mode"
}
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
@@ -81,6 +82,9 @@ class IntentReceiverActivity : Activity() {
}
private fun routeIntent(intent: Intent) {
+ val privateBrowsingMode = intent.getBooleanExtra(PRIVATE_BROWSING_MODE, false)
+ intent.putExtra(PRIVATE_BROWSING_MODE, privateBrowsingMode)
+
// Check if this is our custom PWA intent with profile metadata
val profileUuid = intent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID)
val contextId = intent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
@@ -107,7 +111,7 @@ class IntentReceiverActivity : Activity() {
"CustomTab" to CustomTabIntentProcessor(
components.useCases.customTabsUseCases.add,
resources,
- isPrivate = false,
+ isPrivate = privateBrowsingMode,
),
"PWA" to WebAppIntentProcessor(
components.core.store,
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 731317f9..fdd1a4e1 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
@@ -16,6 +16,7 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.content.ContextCompat
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors
import com.mikepenz.iconics.IconicsDrawable
@@ -46,6 +47,7 @@ class CustomTabToolbar @JvmOverloads constructor(
private val toolbarCard: MaterialCardView
private val closeButton: ImageButton
+ private val privateMaskIcon: ImageView
private val securityIcon: ImageView
private val titleText: TextView
private val urlText: TextView
@@ -58,6 +60,7 @@ class CustomTabToolbar @JvmOverloads constructor(
private var urlScope: CoroutineScope? = null
private var titleScope: CoroutineScope? = null
private var securityScope: CoroutineScope? = null
+ private var isPrivateSession: Boolean = false
var onCloseListener: (() -> Unit)? = null
var onShareListener: (() -> Unit)? = null
@@ -69,6 +72,7 @@ class CustomTabToolbar @JvmOverloads constructor(
toolbarCard = findViewById(R.id.toolbarCard)
closeButton = findViewById(R.id.closeButton)
+ privateMaskIcon = findViewById(R.id.privateMaskIcon)
securityIcon = findViewById(R.id.securityIcon)
titleText = findViewById(R.id.titleText)
urlText = findViewById(R.id.urlText)
@@ -89,9 +93,17 @@ class CustomTabToolbar @JvmOverloads constructor(
this.sessionId = sessionId
this.store = store
- toolbarColor?.let { applyCustomColors(it) }
+ val tab = store.state.findCustomTab(sessionId)
+ isPrivateSession = tab?.content?.private == true
- store.state.findCustomTab(sessionId)?.let { tab ->
+ if (toolbarColor != null) {
+ applyCustomColors(toolbarColor)
+ } else {
+ applyMaterial3Colors()
+ applyIcons()
+ }
+
+ tab?.let {
updateTitle(tab)
updateUrl(tab)
updateSecurityIcon(tab)
@@ -118,14 +130,16 @@ class CustomTabToolbar @JvmOverloads constructor(
private fun applyMaterial3Colors() {
val surfaceColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorSurface)
val onSurfaceColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurface)
+ val onSurfaceVariantColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurfaceVariant)
toolbarCard.setCardBackgroundColor(surfaceColor)
titleText.setTextColor(onSurfaceColor)
- urlText.setTextColor(onSurfaceColor)
+ urlText.setTextColor(onSurfaceVariantColor)
}
private fun applyIcons() {
val iconColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurfaceVariant)
+ updatePrivateMaskIcon()
closeButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_close, 20, iconColor))
shareButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 18, iconColor))
openInBrowserButton.setImageDrawable(
@@ -145,6 +159,7 @@ class CustomTabToolbar @JvmOverloads constructor(
titleText.setTextColor(textColor)
urlText.setTextColor(textColor)
+ updatePrivateMaskIcon()
closeButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon.cmd_close, 20, textColor))
shareButton.setImageDrawable(mdiIcon(CommunityMaterial.Icon3.cmd_share_variant, 18, textColor))
openInBrowserButton.setImageDrawable(
@@ -242,17 +257,29 @@ class CustomTabToolbar @JvmOverloads constructor(
}
private fun updateSecurityIcon(tab: CustomTabSessionState) {
+ val neutralIconColor = MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnSurfaceVariant)
+ val errorIconColor = MaterialColors.getColor(this, android.R.attr.colorError)
+
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))
+ securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock_open_outline, 16, neutralIconColor))
} 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))
+ securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock, 16, neutralIconColor))
} else {
- val color = MaterialColors.getColor(this, android.R.attr.colorError)
- securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock_open_outline, 16, color))
+ securityIcon.setImageDrawable(mdiIcon(CommunityMaterial.Icon2.cmd_lock_open_outline, 16, errorIconColor))
+ }
+ }
+
+ private fun updatePrivateMaskIcon() {
+ privateMaskIcon.visibility = if (isPrivateSession) View.VISIBLE else View.GONE
+ if (isPrivateSession) {
+ val privateMaskColor = ContextCompat.getColor(context, R.color.private_tab_mask_accent)
+ privateMaskIcon.setImageDrawable(
+ mdiIcon(CommunityMaterial.Icon.cmd_domino_mask, 16, privateMaskColor)
+ )
+ } else {
+ privateMaskIcon.setImageDrawable(null)
}
}
diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbarFeature.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbarFeature.kt
index 323e9651..be35ae72 100644
--- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbarFeature.kt
+++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/widget/CustomTabToolbarFeature.kt
@@ -27,7 +27,6 @@ class CustomTabToolbarFeature(
override fun start() {
val tab = store.state.findCustomTab(sessionId) ?: return
-
val toolbarColor = tab.config.colorSchemes?.defaultColorSchemeParams?.toolbarColor
toolbar.bind(sessionId, store, toolbarColor)
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 506e8cdf..69a35620 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
@@ -45,6 +45,15 @@
android:paddingHorizontal="8dp"
android:paddingVertical="6dp">
+
+
#ffffffff
-
\ No newline at end of file
+
+ #FF8000D7
+