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 +