From 3e4a9cfc5f5dc2a3aa8464a6bed60d06e73e4e70 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Tue, 10 Feb 2026 18:01:00 +0100 Subject: [PATCH] improve splash screens --- .../PwaConstants.kt | 4 + .../PwaSplashCache.kt | 96 ++++++ .../activities/ExternalAppBrowserActivity.kt | 64 +--- .../activities/IntentReceiverActivity.kt | 36 +-- .../api/GeckoPwaApiImpl.kt | 18 +- .../ui/LoadingScreenManager.kt | 281 ++++-------------- .../src/main/res/drawable/pulse_ripple.xml | 10 - .../res/layout/custom_tab_loading_screen.xml | 54 ---- .../src/main/res/layout/loading_screen.xml | 56 ++++ .../main/res/layout/pwa_loading_screen.xml | 88 ------ .../android/src/main/res/values/styles.xml | 6 - 11 files changed, 243 insertions(+), 470 deletions(-) create mode 100644 packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaSplashCache.kt delete mode 100644 packages/flutter_mozilla_components/android/src/main/res/drawable/pulse_ripple.xml delete mode 100644 packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_loading_screen.xml create mode 100644 packages/flutter_mozilla_components/android/src/main/res/layout/loading_screen.xml delete mode 100644 packages/flutter_mozilla_components/android/src/main/res/layout/pwa_loading_screen.xml diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaConstants.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaConstants.kt index 4d0d37f7..89100bfa 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaConstants.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaConstants.kt @@ -11,9 +11,13 @@ object PwaConstants { const val EXTRA_PWA_PROFILE_UUID = "pwa_profile_uuid" const val EXTRA_PWA_CONTEXT_ID = "pwa_context_id" + // Shortcut ID for linking to cached manifest + icon on disk + const val EXTRA_PWA_SHORTCUT_ID = "pwa_shortcut_id" + // Profile and file paths const val CURRENT_PROFILE_FILE = "weblibre_profiles/current_profile" const val PROFILE_MAPPING_PREFS = "pwa_profile_mapping" + const val PWA_CACHE_DIR = "pwa_cache" // Component initialization timeouts const val COMPONENT_INIT_TIMEOUT_MS = 10000L diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaSplashCache.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaSplashCache.kt new file mode 100644 index 00000000..65706578 --- /dev/null +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/PwaSplashCache.kt @@ -0,0 +1,96 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +package eu.weblibre.flutter_mozilla_components + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Log +import mozilla.components.concept.engine.manifest.WebAppManifest +import mozilla.components.concept.engine.manifest.WebAppManifestParser +import java.io.File +import java.io.FileOutputStream + +/** + * Simple disk cache for PWA splash screen data (manifest + icon). + * Keyed by shortcut ID so home screen shortcuts can instantly display + * branded splash screens without waiting for components to initialize. + * + * Cache layout: + * /pwa_cache//manifest.json + * /pwa_cache//icon.png + */ +object PwaSplashCache { + + private const val TAG = "PwaSplashCache" + private val parser = WebAppManifestParser() + + private const val MANIFEST_FILE = "manifest.json" + private const val ICON_FILE = "icon.png" + + /** + * Saves manifest JSON and icon bitmap to disk for the given shortcut ID. + */ + fun save(context: Context, shortcutId: String, manifest: WebAppManifest, icon: Bitmap?) { + try { + val dir = cacheDir(context, shortcutId) + if (!dir.exists()) dir.mkdirs() + + val json = parser.serialize(manifest).toString() + File(dir, MANIFEST_FILE).writeText(json) + + icon?.let { bitmap -> + FileOutputStream(File(dir, ICON_FILE)).use { out -> + bitmap.compress(Bitmap.CompressFormat.PNG, 90, out) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to cache splash data for $shortcutId", e) + } + } + + /** + * Loads cached manifest for the given shortcut ID, or null if not cached. + */ + fun loadManifest(context: Context, shortcutId: String): WebAppManifest? { + return try { + val file = File(cacheDir(context, shortcutId), MANIFEST_FILE) + if (!file.exists()) return null + + val json = file.readText() + when (val result = parser.parse(json)) { + is WebAppManifestParser.Result.Success -> result.manifest + is WebAppManifestParser.Result.Failure -> { + Log.e(TAG, "Failed to parse cached manifest for $shortcutId", result.exception) + null + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to load cached manifest for $shortcutId", e) + null + } + } + + /** + * Loads cached icon bitmap for the given shortcut ID, or null if not cached. + */ + fun loadIcon(context: Context, shortcutId: String): Bitmap? { + return try { + val file = File(cacheDir(context, shortcutId), ICON_FILE) + if (!file.exists()) return null + BitmapFactory.decodeFile(file.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Failed to load cached icon for $shortcutId", e) + null + } + } + + private fun cacheDir(context: Context, shortcutId: String): File { + // Always use applicationContext.filesDir to avoid profile-scoped context paths + return File(context.applicationContext.filesDir, "${PwaConstants.PWA_CACHE_DIR}/$shortcutId") + } +} diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/ExternalAppBrowserActivity.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/ExternalAppBrowserActivity.kt index 2ff1d344..c7ace60a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/ExternalAppBrowserActivity.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/activities/ExternalAppBrowserActivity.kt @@ -12,7 +12,6 @@ import android.os.Bundle import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import eu.weblibre.flutter_mozilla_components.Components import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.PwaConstants @@ -25,7 +24,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import mozilla.components.browser.state.selector.findCustomTab import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.log.logger.Logger @@ -75,20 +73,18 @@ class ExternalAppBrowserActivity : AppCompatActivity() { private fun showLoading() { val container = findViewById(R.id.container) loadingScreenManager = LoadingScreenManager.forActivity(this, container) - - // Show branded placeholder immediately based on available data - // If we have a manifest URL, it's likely a PWA + val url = webAppManifestUrl ?: "" + val shortcutId = intent?.getStringExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID) + if (url.isNotEmpty()) { - // Try to show PWA placeholder - loadingScreenManager?.showLoadingForIntent( - Intent().apply { - data = android.net.Uri.parse(url) - putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, "placeholder") - } - ) + val loadingIntent = Intent().apply { + data = android.net.Uri.parse(url) + putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, "placeholder") + shortcutId?.let { putExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID, it) } + } + loadingScreenManager?.showLoadingForIntent(loadingIntent) } else { - // Show Custom Tab placeholder loadingScreenManager?.showLoadingForIntent(Intent()) } } @@ -98,12 +94,7 @@ class ExternalAppBrowserActivity : AppCompatActivity() { var elapsedMs = 0L while (isActive && elapsedMs < PwaConstants.COMPONENT_INIT_TIMEOUT_MS) { - val components = GlobalComponents.components - if (components != null) { - // Enhance the existing loading screen with actual data - enhanceLoadingScreen(components, sessionId) - // Brief delay to show the enhanced loading screen - delay(200) + if (GlobalComponents.components != null) { showFragment(sessionId) return@launch } @@ -120,39 +111,6 @@ class ExternalAppBrowserActivity : AppCompatActivity() { } } - /** - * Enhances the existing loading screen with actual data once components are ready. - */ - private fun enhanceLoadingScreen(components: Components, sessionId: String) { - val session = components.core.store.state.findCustomTab(sessionId) ?: return - val url = session.content.url - val manifestUrl = webAppManifestUrl - - loadingScreenManager?.let { manager -> - when (session.config.externalAppType) { - mozilla.components.browser.state.state.ExternalAppType.PROGRESSIVE_WEB_APP, - mozilla.components.browser.state.state.ExternalAppType.TRUSTED_WEB_ACTIVITY -> { - // Enhance PWA loading with manifest data - coroutineScope.launch(Dispatchers.IO) { - val manifest = manifestUrl?.let { manifestUrl -> - components.core.webAppManifestStorage.loadManifest(manifestUrl) - } - - withContext(Dispatchers.Main) { - manifest?.let { - manager.enhancePwaLoading(it, components.core.icons, coroutineScope) - } ?: manager.enhanceCustomTabLoading(url, components.core.icons, coroutineScope) - } - } - } - else -> { - // Enhance Custom Tab loading with favicon - manager.enhanceCustomTabLoading(url, components.core.icons, coroutineScope) - } - } - } - } - private fun showFragment(sessionId: String) { val components = GlobalComponents.components ?: run { logger.error("Components still null after waiting, finishing.") @@ -241,11 +199,13 @@ class ExternalAppBrowserActivity : AppCompatActivity() { context: Context, customTabSessionId: String, webAppManifestUrl: String? = null, + pwaShortcutId: String? = null, ): Intent { return Intent(context, ExternalAppBrowserActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId) webAppManifestUrl?.let { putExtra(EXTRA_WEB_APP_MANIFEST_URL, it) } + pwaShortcutId?.let { putExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID, it) } } } } 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 9d4c48ab..6e49ea76 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 @@ -246,6 +246,7 @@ class IntentReceiverActivity : Activity() { context = this@IntentReceiverActivity, customTabSessionId = sessionId, webAppManifestUrl = url, + pwaShortcutId = intent.getStringExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID), ) startActivity(externalIntent) finish() @@ -310,33 +311,6 @@ class IntentReceiverActivity : Activity() { loadingScreenManager?.showLoadingForIntent(intent) } - /** - * Enhances the existing loading screen with actual data once components are initialized. - * This updates the placeholder with real manifest/icon data. - */ - private fun enhanceLoadingScreen(intent: Intent) { - val components = GlobalComponents.components ?: return - val url = intent.dataString ?: return - - loadingScreenManager?.let { manager -> - when { - // PWA intent - enhance with manifest data - LoadingScreenManager.isPwaIntent(intent) -> { - coroutineScope.launch { - val manifest = components.core.webAppManifestStorage.loadManifest(url) - manifest?.let { - manager.enhancePwaLoading(it, components.core.icons, coroutineScope) - } - } - } - // Custom Tab - enhance with favicon - else -> { - manager.enhanceCustomTabLoading(url, components.core.icons, coroutineScope) - } - } - } - } - /** * Waits for GlobalComponents to be initialized with a timeout. * Once components are ready, shows branded loading screen before routing. @@ -349,13 +323,7 @@ class IntentReceiverActivity : Activity() { while (isActive && elapsedMs < PwaConstants.COMPONENT_INIT_TIMEOUT_MS) { if (GlobalComponents.components != null) { logger.debug("Components initialized after ${elapsedMs}ms") - pendingIntent?.let { intent -> - // Enhance the existing loading screen with actual data - enhanceLoadingScreen(intent) - // Small delay to show the enhanced loading screen (200ms) - delay(200) - routeIntent(intent) - } + pendingIntent?.let { routeIntent(it) } pendingIntent = null return@launch } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoPwaApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoPwaApiImpl.kt index af0bff4a..faaae27e 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoPwaApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoPwaApiImpl.kt @@ -17,6 +17,7 @@ import android.os.Build import androidx.core.content.getSystemService import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.PwaConstants +import eu.weblibre.flutter_mozilla_components.PwaSplashCache import eu.weblibre.flutter_mozilla_components.activities.IntentReceiverActivity import eu.weblibre.flutter_mozilla_components.pigeons.ExternalApplicationResource import eu.weblibre.flutter_mozilla_components.pigeons.GeckoPwaApi @@ -135,19 +136,26 @@ class GeckoPwaApiImpl( return@withContext false } + val (iconBitmap, isMaskable) = loadPwaIcon(manifest) + + val shortcutId = generateShortcutId(manifest.startUrl) + + // Cache manifest + icon to disk for instant branded splash at launch + PwaSplashCache.save(context, shortcutId, manifest, iconBitmap) + + val appName = manifest.shortName ?: manifest.name ?: "Web App" + val shortcutIntent = Intent(context, IntentReceiverActivity::class.java).apply { action = Intent.ACTION_VIEW data = Uri.parse(manifest.startUrl) putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, profileUuid) putExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID, contextId) + putExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID, shortcutId) } - val (iconBitmap, isMaskable) = loadPwaIcon(manifest) - - val shortcutId = generateShortcutId(manifest.startUrl) val shortcut = ShortcutInfo.Builder(context, shortcutId).apply { - setShortLabel(manifest.shortName ?: manifest.name ?: "Web App") - setLongLabel(manifest.name ?: manifest.shortName ?: "Web App") + setShortLabel(appName) + setLongLabel(manifest.name ?: appName) setIntent(shortcutIntent) if (iconBitmap != null) { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ui/LoadingScreenManager.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ui/LoadingScreenManager.kt index 634b7cba..9567223a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ui/LoadingScreenManager.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/ui/LoadingScreenManager.kt @@ -8,203 +8,90 @@ package eu.weblibre.flutter_mozilla_components.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter -import android.animation.ObjectAnimator import android.app.Activity +import android.content.Context import android.content.Intent -import android.graphics.Bitmap import android.graphics.Color +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View -import android.view.animation.AccelerateDecelerateInterpolator import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.core.graphics.ColorUtils import eu.weblibre.flutter_mozilla_components.PwaConstants +import eu.weblibre.flutter_mozilla_components.PwaSplashCache import eu.weblibre.flutter_mozilla_components.R -import mozilla.components.browser.icons.BrowserIcons -import mozilla.components.browser.icons.IconRequest -import mozilla.components.concept.engine.manifest.WebAppManifest -import mozilla.components.support.base.log.logger.Logger -import kotlinx.coroutines.* /** - * Manager for displaying branded loading screens during PWA and Custom Tab initialization. - * Handles loading of icons, theming, and animations. + * Displays a loading screen during PWA and Custom Tab initialization. + * + * - PWA with cached data: shows cached icon, app name, and theme color instantly from disk. + * - PWA without cache / Custom Tab: shows WebLibre logo with domain name. + * + * No component dependencies — everything is resolved from intent extras and disk cache. */ class LoadingScreenManager private constructor( private val activity: Activity, private val container: FrameLayout ) { - private val logger = Logger("LoadingScreenManager") private var currentLoadingView: View? = null - private var pulseAnimator: ObjectAnimator? = null + + // Material3 theme wrapper for activities that use non-Material themes (e.g. AppCompat.Translucent) + private val themedContext: Context + get() = ContextThemeWrapper(activity, com.google.android.material.R.style.Theme_Material3_DayNight_NoActionBar) companion object { - /** - * Creates a LoadingScreenManager for the given activity. - * The container should be the root view where loading screens will be added. - */ - fun forActivity(activity: Activity, container: FrameLayout): LoadingScreenManager { - return LoadingScreenManager(activity, container) - } + fun forActivity(activity: Activity, container: FrameLayout) = + LoadingScreenManager(activity, container) - /** - * Detects if an intent is for a PWA based on the extras. - */ - fun isPwaIntent(intent: Intent?): Boolean { - return intent?.hasExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) == true - } + fun isPwaIntent(intent: Intent?) = + intent?.hasExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) == true - /** - * Extracts URL from an intent. - */ - fun extractUrl(intent: Intent?): String? { - return intent?.dataString - } + fun hasCachedSplashData(intent: Intent?) = + intent?.hasExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID) == true } /** - * Shows the appropriate loading screen immediately based on intent analysis. - * This can be called before components are initialized. - * - * @param intent The intent to analyze for type detection + * Shows the loading screen immediately based on intent analysis. + * Can be called before components are initialized. */ fun showLoadingForIntent(intent: Intent) { - when { - isPwaIntent(intent) -> showPwaPlaceholder(intent) - else -> showCustomTabPlaceholder(intent) - } - } - - /** - * Shows a PWA placeholder loading screen immediately (before components are ready). - * Uses URL to extract domain as temporary app name. - */ - private fun showPwaPlaceholder(intent: Intent) { cleanup() - - val view = LayoutInflater.from(activity).inflate( - R.layout.pwa_loading_screen, - container, - false + + val view = LayoutInflater.from(themedContext).inflate( + R.layout.loading_screen, container, false ) - - // Extract URL and use domain as temporary name - val url = intent.dataString - val domain = url?.let { extractDomain(it) } ?: "Web App" - - // Set temporary app name (will be replaced with actual name once manifest loads) - val nameView = view.findViewById(R.id.pwa_name) - nameView.text = domain - - // Show WebLibre logo as placeholder (already set in XML layout) - val iconView = view.findViewById(R.id.pwa_icon) - iconView.alpha = 0.5f - - // Start pulsing animation - startPulseAnimation(view) - + + val iconView = view.findViewById(R.id.loading_icon) + val nameView = view.findViewById(R.id.loading_name) + val statusView = view.findViewById(R.id.loading_status) + + val shortcutId = intent.getStringExtra(PwaConstants.EXTRA_PWA_SHORTCUT_ID) + val manifest = shortcutId?.let { PwaSplashCache.loadManifest(activity, it) } + + if (manifest != null) { + // PWA with cached data — branded splash + nameView.text = manifest.shortName ?: manifest.name ?: "Web App" + + val cachedIcon = PwaSplashCache.loadIcon(activity, shortcutId) + if (cachedIcon != null) { + iconView.setImageBitmap(cachedIcon) + } + + manifest.themeColor?.let { themeColor -> + applyThemeColor(view, themeColor, nameView, statusView) + } + } else { + // Custom Tab or uncached PWA — WebLibre logo + domain + val url = intent.dataString + nameView.text = url?.let { extractDomain(it) } ?: "Web App" + } + container.addView(view) currentLoadingView = view } - /** - * Shows a Custom Tab placeholder loading screen immediately (before components are ready). - */ - private fun showCustomTabPlaceholder(intent: Intent) { - cleanup() - - val view = LayoutInflater.from(activity).inflate( - R.layout.custom_tab_loading_screen, - container, - false - ) - - // Extract and display domain - val url = intent.dataString ?: return - val domainView = view.findViewById(R.id.custom_tab_domain) - domainView.text = extractDomain(url) - - container.addView(view) - currentLoadingView = view - } - - /** - * Enhances the current loading screen with actual PWA data once components are ready. - * This updates the placeholder with real manifest data. - */ - fun enhancePwaLoading( - manifest: WebAppManifest, - browserIcons: BrowserIcons, - coroutineScope: CoroutineScope - ) { - currentLoadingView?.let { view -> - // Apply theme colors if available - manifest.themeColor?.let { colorInt -> - view.setBackgroundColor(colorInt) - - val isDark = ColorUtils.calculateLuminance(colorInt) < 0.5 - val primaryTextColor = if (isDark) Color.WHITE else Color.BLACK - val secondaryTextColor = ColorUtils.setAlphaComponent(primaryTextColor, 0xB3) - - view.findViewById(R.id.pwa_name)?.setTextColor(primaryTextColor) - view.findViewById(R.id.pwa_status)?.setTextColor(secondaryTextColor) - } - - // Update app name - val nameView = view.findViewById(R.id.pwa_name) - val appName = manifest.shortName ?: manifest.name - if (appName != null && nameView.text != appName) { - nameView.text = appName - } - - // Load the PWA icon asynchronously and update - coroutineScope.launch(Dispatchers.IO) { - loadPwaIcon(manifest, browserIcons)?.let { bitmap -> - withContext(Dispatchers.Main) { - val iconView = view.findViewById(R.id.pwa_icon) - iconView.alpha = 1.0f - iconView.setImageBitmap(bitmap) - } - } - } - } - } - - /** - * Enhances the Custom Tab loading screen with favicon once components are ready. - */ - fun enhanceCustomTabLoading( - url: String, - browserIcons: BrowserIcons, - coroutineScope: CoroutineScope - ) { - currentLoadingView?.let { view -> - coroutineScope.launch(Dispatchers.IO) { - try { - val iconRequest = IconRequest( - url = url, - size = IconRequest.Size.DEFAULT - ) - val iconResult = browserIcons.loadIcon(iconRequest).await() - iconResult?.bitmap?.let { bitmap -> - withContext(Dispatchers.Main) { - val iconView = view.findViewById(R.id.custom_tab_icon) - iconView.setImageBitmap(bitmap) - iconView.alpha = 1.0f - } - } - } catch (e: Exception) { - logger.debug("Failed to load favicon for $url") - } - } - } - } - - /** - * Hides the loading screen with an optional fade-out animation. - */ fun hideLoading(animate: Boolean = true) { currentLoadingView?.let { view -> if (animate) { @@ -223,74 +110,26 @@ class LoadingScreenManager private constructor( } } - /** - * Cleans up the loading view and animations. - */ fun cleanup() { - pulseAnimator?.cancel() - pulseAnimator = null - - currentLoadingView?.let { view -> - container.removeView(view) - } + currentLoadingView?.let { container.removeView(it) } currentLoadingView = null } - private fun startPulseAnimation(view: View) { - val pulseView = view.findViewById(R.id.pwa_icon_pulse) - ?: return - - pulseView.alpha = 0.0f - pulseAnimator = ObjectAnimator.ofFloat(pulseView, "alpha", 0.0f, 0.3f, 0.0f).apply { - duration = 1500 - repeatCount = ObjectAnimator.INFINITE - interpolator = AccelerateDecelerateInterpolator() - start() - } - } + private fun applyThemeColor(view: View, themeColor: Int, nameView: TextView, statusView: TextView) { + view.setBackgroundColor(themeColor) - private suspend fun loadPwaIcon( - manifest: WebAppManifest, - browserIcons: BrowserIcons - ): Bitmap? { - return try { - val iconResource = manifest.icons - .filter { it.purpose.contains(WebAppManifest.Icon.Purpose.MASKABLE) || - it.purpose.contains(WebAppManifest.Icon.Purpose.ANY) } - .maxByOrNull { (it.sizes?.maxOf { size -> size.width * size.height } ?: 0) } - ?: manifest.icons.firstOrNull() + val isDark = ColorUtils.calculateLuminance(themeColor) < 0.5 + val primaryTextColor = if (isDark) Color.WHITE else Color.BLACK + val secondaryTextColor = ColorUtils.setAlphaComponent(primaryTextColor, 0xB3) - iconResource?.let { icon -> - val iconRequest = IconRequest( - url = manifest.startUrl, - size = IconRequest.Size.LAUNCHER, - resources = listOf( - IconRequest.Resource( - url = icon.src, - type = IconRequest.Resource.Type.MANIFEST_ICON, - sizes = icon.sizes?.map { size -> - mozilla.components.concept.engine.manifest.Size(size.width, size.height) - } ?: emptyList(), - mimeType = icon.type, - maskable = icon.purpose.contains(WebAppManifest.Icon.Purpose.MASKABLE) - ) - ) - ) - - val result = browserIcons.loadIcon(iconRequest).await() - result?.bitmap - } - } catch (e: Exception) { - logger.error("Failed to load PWA icon", e) - null - } + nameView.setTextColor(primaryTextColor) + statusView.setTextColor(secondaryTextColor) } private fun extractDomain(url: String): String { return try { - val uri = android.net.Uri.parse(url) - uri.host ?: url - } catch (e: Exception) { + android.net.Uri.parse(url).host ?: url + } catch (_: Exception) { url } } diff --git a/packages/flutter_mozilla_components/android/src/main/res/drawable/pulse_ripple.xml b/packages/flutter_mozilla_components/android/src/main/res/drawable/pulse_ripple.xml deleted file mode 100644 index 24dc7baf..00000000 --- a/packages/flutter_mozilla_components/android/src/main/res/drawable/pulse_ripple.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_loading_screen.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_loading_screen.xml deleted file mode 100644 index 9a2933d3..00000000 --- a/packages/flutter_mozilla_components/android/src/main/res/layout/custom_tab_loading_screen.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/loading_screen.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/loading_screen.xml new file mode 100644 index 00000000..57afdb52 --- /dev/null +++ b/packages/flutter_mozilla_components/android/src/main/res/layout/loading_screen.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/packages/flutter_mozilla_components/android/src/main/res/layout/pwa_loading_screen.xml b/packages/flutter_mozilla_components/android/src/main/res/layout/pwa_loading_screen.xml deleted file mode 100644 index 2b9b56bd..00000000 --- a/packages/flutter_mozilla_components/android/src/main/res/layout/pwa_loading_screen.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml b/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml index 8c6a1337..ed88a36e 100644 --- a/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml +++ b/packages/flutter_mozilla_components/android/src/main/res/values/styles.xml @@ -7,10 +7,4 @@ true @android:style/Animation - - - \ No newline at end of file