diff --git a/splash-screen/README.md b/splash-screen/README.md index 0a4020e0a..f1adbb17a 100644 --- a/splash-screen/README.md +++ b/splash-screen/README.md @@ -71,19 +71,21 @@ To set the color of the spinner use `spinnerColor`, values are either `#RRGGBB` These config values are available: -| Prop | Type | Description | Default | Since | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- | -| **`launchShowDuration`** | number | How long to show the launch splash screen when autoHide is enabled (in ms) | 0 | 1.0.0 | -| **`launchAutoHide`** | boolean | Whether to auto hide the splash after launchShowDuration. | true | 1.0.0 | -| **`backgroundColor`** | string | Color of the background of the Splash Screen in hex format, #RRGGBB or #RRGGBBAA. | | 1.0.0 | -| **`androidSplashResourceName`** | string | Name of the resource to be used as Splash Screen. Only available on Android. | splash | 1.0.0 | -| **`androidScaleType`** | 'CENTER' \| 'CENTER_CROP' \| 'CENTER_INSIDE' \| 'FIT_CENTER' \| 'FIT_END' \| 'FIT_START' \| 'FIT_XY' \| 'MATRIX' | The [ImageView.ScaleType](https://developer.android.com/reference/android/widget/ImageView.ScaleType) used to scale the Splash Screen image. Only available on Android. | FIT_XY | 1.0.0 | -| **`showSpinner`** | boolean | Show a loading spinner on the Splash Screen. | | 1.0.0 | -| **`androidSpinnerStyle`** | 'horizontal' \| 'small' \| 'large' \| 'inverse' \| 'smallInverse' \| 'largeInverse' | Style of the Android spinner. | large | 1.0.0 | -| **`iosSpinnerStyle`** | 'small' \| 'large' | Style of the iOS spinner. Only available on iOS. | large | 1.0.0 | -| **`spinnerColor`** | string | Color of the spinner in hex format, #RRGGBB or #RRGGBBAA. | | 1.0.0 | -| **`splashFullScreen`** | boolean | Hide the status bar on the Splash Screen. Only available on Android. | | 1.0.0 | -| **`splashImmersive`** | boolean | Hide the status bar and the software navigation buttons on the Splash Screen. Only available on Android. | | 1.0.0 | +| Prop | Type | Description | Default | Since | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ----- | +| **`launchShowDuration`** | number | How long to show the launch splash screen when autoHide is enabled (in ms) | 0 | 1.0.0 | +| **`launchAutoHide`** | boolean | Whether to auto hide the splash after launchShowDuration. | true | 1.0.0 | +| **`backgroundColor`** | string | Color of the background of the Splash Screen in hex format, #RRGGBB or #RRGGBBAA. Doesn't work if `useDialog` is true. | | 1.0.0 | +| **`androidSplashResourceName`** | string | Name of the resource to be used as Splash Screen. Only available on Android. | splash | 1.0.0 | +| **`androidScaleType`** | 'CENTER' \| 'CENTER_CROP' \| 'CENTER_INSIDE' \| 'FIT_CENTER' \| 'FIT_END' \| 'FIT_START' \| 'FIT_XY' \| 'MATRIX' | The [ImageView.ScaleType](https://developer.android.com/reference/android/widget/ImageView.ScaleType) used to scale the Splash Screen image. Doesn't work if `useDialog` is true. Only available on Android. | FIT_XY | 1.0.0 | +| **`showSpinner`** | boolean | Show a loading spinner on the Splash Screen. Doesn't work if `useDialog` is true. | | 1.0.0 | +| **`androidSpinnerStyle`** | 'horizontal' \| 'small' \| 'large' \| 'inverse' \| 'smallInverse' \| 'largeInverse' | Style of the Android spinner. Doesn't work if `useDialog` is true. | large | 1.0.0 | +| **`iosSpinnerStyle`** | 'small' \| 'large' | Style of the iOS spinner. Doesn't work if `useDialog` is true. Only available on iOS. | large | 1.0.0 | +| **`spinnerColor`** | string | Color of the spinner in hex format, #RRGGBB or #RRGGBBAA. Doesn't work if `useDialog` is true. | | 1.0.0 | +| **`splashFullScreen`** | boolean | Hide the status bar on the Splash Screen. Only available on Android. | | 1.0.0 | +| **`splashImmersive`** | boolean | Hide the status bar and the software navigation buttons on the Splash Screen. Only available on Android. | | 1.0.0 | +| **`layoutName`** | string | If `useDialog` is set to true, configure the Dialog layout. If `useDialog` is not set or false, use a layout instead of the ImageView. Only available on Android. | | 1.1.0 | +| **`useDialog`** | boolean | Use a Dialog instead of an ImageView. If `layoutName` is not configured, it will use a layout that uses the splash image as background. Only available on Android. | | 1.1.0 | ### Examples @@ -103,7 +105,9 @@ In `capacitor.config.json`: "iosSpinnerStyle": "small", "spinnerColor": "#999999", "splashFullScreen": true, - "splashImmersive": true + "splashImmersive": true, + "layoutName": "launch_screen", + "useDialog": true } } } @@ -130,6 +134,8 @@ const config: CapacitorConfig = { spinnerColor: "#999999", splashFullScreen: true, splashImmersive: true, + layoutName: "launch_screen", + useDialog: true, }, }, }; diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java index a745e8651..6ef6fac08 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreen.java @@ -1,6 +1,8 @@ package com.capacitorjs.plugins.splashscreen; import android.animation.Animator; +import android.app.Activity; +import android.app.Dialog; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -9,11 +11,11 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Handler; -import android.view.Gravity; -import android.view.View; -import android.view.WindowManager; +import android.view.*; import android.view.animation.LinearInterpolator; +import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import androidx.appcompat.app.AppCompatActivity; import com.getcapacitor.Logger; @@ -23,7 +25,8 @@ */ public class SplashScreen { - private ImageView splashImage; + private Dialog dialog; + private View splashImage; private ProgressBar spinnerBar; private WindowManager windowManager; private boolean isVisible = false; @@ -49,8 +52,11 @@ public void showOnLaunch(final AppCompatActivity activity) { settings.setShowDuration(config.getLaunchShowDuration()); settings.setAutoHide(config.isLaunchAutoHide()); settings.setFadeInDuration(config.getLaunchFadeInDuration()); - - show(activity, settings, null, true); + if (config.isUsingDialog()) { + showDialog(activity, settings, null, true); + } else { + show(activity, settings, null, true); + } } /** @@ -61,7 +67,83 @@ public void showOnLaunch(final AppCompatActivity activity) { * @param splashListener A listener to handle the finish of the animation (if any) */ public void show(final AppCompatActivity activity, final SplashScreenSettings settings, final SplashListener splashListener) { - show(activity, settings, splashListener, false); + if (config.isUsingDialog()) { + showDialog(activity, settings, splashListener, false); + } else { + show(activity, settings, splashListener, false); + } + } + + private void showDialog( + final AppCompatActivity activity, + final SplashScreenSettings settings, + final SplashListener splashListener, + final boolean isLaunchSplash + ) { + if (activity == null || activity.isFinishing()) return; + + if (isVisible) { + splashListener.completed(); + return; + } + + activity.runOnUiThread( + () -> { + if (config.isImmersive()) { + dialog = new Dialog(activity, R.style.capacitor_immersive_style); + } else if (config.isFullScreen()) { + dialog = new Dialog(activity, R.style.capacitor_full_screen_style); + } else { + dialog = new Dialog(activity, R.style.capacitor_default_style); + } + int splashId = 0; + if (config.getLayoutName() != null) { + splashId = context.getResources().getIdentifier(config.getLayoutName(), "layout", context.getPackageName()); + if (splashId == 0) { + Logger.warn("Layout not found, using default"); + } + } + if (splashId != 0) { + dialog.setContentView(splashId); + } else { + Drawable splash = getSplashDrawable(); + LinearLayout parent = new LinearLayout(context); + parent.setLayoutParams( + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + ); + parent.setOrientation(LinearLayout.VERTICAL); + if (splash != null) { + parent.setBackground(splash); + } + dialog.setContentView(parent); + } + + dialog.setCancelable(false); + if (!dialog.isShowing()) { + dialog.show(); + } + isVisible = true; + + if (settings.isAutoHide()) { + new Handler() + .postDelayed( + () -> { + hideDialog(activity, isLaunchSplash); + + if (splashListener != null) { + splashListener.completed(); + } + }, + settings.getShowDuration() + ); + } else { + // If no autoHide, call complete + if (splashListener != null) { + splashListener.completed(); + } + } + } + ); } /** @@ -73,6 +155,15 @@ public void hide(SplashScreenSettings settings) { hide(settings.getFadeOutDuration(), false); } + /** + * Hide the Splash Screen when showing it as a dialog + * + * @param activity the activity showing the dialog + */ + public void hideDialog(final AppCompatActivity activity) { + hideDialog(activity, false); + } + public void onPause() { tearDown(true); } @@ -83,34 +174,53 @@ public void onDestroy() { private void buildViews() { if (splashImage == null) { - int splashId = context.getResources().getIdentifier(config.getResourceName(), "drawable", context.getPackageName()); - + int splashId = 0; Drawable splash; - try { - splash = context.getResources().getDrawable(splashId, context.getTheme()); - } catch (Resources.NotFoundException ex) { - Logger.warn("No splash screen found, not displaying"); - return; - } - if (splash instanceof Animatable) { - ((Animatable) splash).start(); + if (config.getLayoutName() != null) { + splashId = context.getResources().getIdentifier(config.getLayoutName(), "layout", context.getPackageName()); + if (splashId == 0) { + Logger.warn("Layout not found, defaulting to ImageView"); + } } - if (splash instanceof LayerDrawable) { - LayerDrawable layeredSplash = (LayerDrawable) splash; + if (splashId != 0) { + Activity activity = (Activity) context; + LayoutInflater inflator = activity.getLayoutInflater(); + ViewGroup root = new FrameLayout(context); + root.setLayoutParams( + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + ); + splashImage = inflator.inflate(splashId, root, false); + } else { + splash = getSplashDrawable(); + if (splash != null) { + if (splash instanceof Animatable) { + ((Animatable) splash).start(); + } + + if (splash instanceof LayerDrawable) { + LayerDrawable layeredSplash = (LayerDrawable) splash; - for (int i = 0; i < layeredSplash.getNumberOfLayers(); i++) { - Drawable layerDrawable = layeredSplash.getDrawable(i); + for (int i = 0; i < layeredSplash.getNumberOfLayers(); i++) { + Drawable layerDrawable = layeredSplash.getDrawable(i); - if (layerDrawable instanceof Animatable) { - ((Animatable) layerDrawable).start(); + if (layerDrawable instanceof Animatable) { + ((Animatable) layerDrawable).start(); + } + } } + + splashImage = new ImageView(context); + // Stops flickers dead in their tracks + // https://stackoverflow.com/a/21847579/32140 + ImageView imageView = (ImageView) splashImage; + imageView.setDrawingCacheEnabled(true); + imageView.setScaleType(config.getScaleType()); + imageView.setImageDrawable(splash); } } - splashImage = new ImageView(context); - splashImage.setFitsSystemWindows(true); if (config.isImmersive()) { @@ -126,16 +236,9 @@ private void buildViews() { splashImage.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN); } - // Stops flickers dead in their tracks - // https://stackoverflow.com/a/21847579/32140 - splashImage.setDrawingCacheEnabled(true); - if (config.getBackgroundColor() != null) { splashImage.setBackgroundColor(config.getBackgroundColor()); } - - splashImage.setScaleType(config.getScaleType()); - splashImage.setImageDrawable(splash); } if (spinnerBar == null) { @@ -162,6 +265,17 @@ private void buildViews() { } } + private Drawable getSplashDrawable() { + int splashId = context.getResources().getIdentifier(config.getResourceName(), "drawable", context.getPackageName()); + try { + Drawable drawable = context.getResources().getDrawable(splashId, context.getTheme()); + return drawable; + } catch (Resources.NotFoundException ex) { + Logger.warn("No splash screen found, not displaying"); + return null; + } + } + private void show( final AppCompatActivity activity, final SplashScreenSettings settings, @@ -333,6 +447,36 @@ public void onAnimationRepeat(Animator animator) {} ); } + private void hideDialog(final AppCompatActivity activity, boolean isLaunchSplash) { + // Warn the user if the splash was hidden automatically, which means they could be experiencing an app + // that feels slower than it actually is. + if (isLaunchSplash && isVisible) { + Logger.debug( + "SplashScreen was automatically hidden after the launch timeout. " + + "You should call `SplashScreen.hide()` as soon as your web app is loaded (or increase the timeout)." + + "Read more at https://capacitorjs.com/docs/apis/splash-screen#hiding-the-splash-screen" + ); + } + + if (isHiding) { + return; + } + + isHiding = true; + + activity.runOnUiThread( + () -> { + if (dialog != null && dialog.isShowing()) { + if (!activity.isFinishing() && !activity.isDestroyed()) { + dialog.dismiss(); + } + dialog = null; + isVisible = false; + } + } + ); + } + private void tearDown(boolean removeSpinner) { if (spinnerBar != null && spinnerBar.getParent() != null) { spinnerBar.setVisibility(View.INVISIBLE); diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java index 642d611b5..043bbde2e 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenConfig.java @@ -15,6 +15,8 @@ public class SplashScreenConfig { private boolean immersive = false; private boolean fullScreen = false; private ScaleType scaleType = ScaleType.FIT_XY; + private boolean usingDialog = false; + private String layoutName; public Integer getBackgroundColor() { return backgroundColor; @@ -99,4 +101,20 @@ public ScaleType getScaleType() { public void setScaleType(ScaleType scaleType) { this.scaleType = scaleType; } + + public boolean isUsingDialog() { + return usingDialog; + } + + public void setUsingDialog(boolean usingDialog) { + this.usingDialog = usingDialog; + } + + public String getLayoutName() { + return layoutName; + } + + public void setLayoutName(String layoutName) { + this.layoutName = layoutName; + } } diff --git a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java index 70c22ffdb..d4e06258e 100644 --- a/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java +++ b/splash-screen/android/src/main/java/com/capacitorjs/plugins/splashscreen/SplashScreenPlugin.java @@ -12,9 +12,10 @@ public class SplashScreenPlugin extends Plugin { private SplashScreen splashScreen; + private SplashScreenConfig config; public void load() { - SplashScreenConfig config = getSplashScreenConfig(); + config = getSplashScreenConfig(); splashScreen = new SplashScreen(getContext(), config); splashScreen.showOnLaunch(getActivity()); } @@ -40,7 +41,11 @@ public void error() { @PluginMethod public void hide(PluginCall call) { - splashScreen.hide(getSettings(call)); + if (config.isUsingDialog()) { + splashScreen.hideDialog(getActivity()); + } else { + splashScreen.hide(getSettings(call)); + } call.resolve(); } @@ -142,6 +147,13 @@ private SplashScreenConfig getSplashScreenConfig() { Boolean showSpinner = getConfig().getBoolean("showSpinner", config.isShowSpinner()); config.setShowSpinner(showSpinner); + Boolean useDialog = getConfig().getBoolean("useDialog", config.isUsingDialog()); + config.setUsingDialog(useDialog); + + if (getConfig().getString("layoutName") != null) { + config.setLayoutName(getConfig().getString("layoutName")); + } + return config; } } diff --git a/splash-screen/android/src/main/res/values/styles.xml b/splash-screen/android/src/main/res/values/styles.xml new file mode 100644 index 000000000..3534cf911 --- /dev/null +++ b/splash-screen/android/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/splash-screen/src/definitions.ts b/splash-screen/src/definitions.ts index bc91ef1e4..1f4256aad 100644 --- a/splash-screen/src/definitions.ts +++ b/splash-screen/src/definitions.ts @@ -26,6 +26,7 @@ declare module '@capacitor/cli' { /** * Color of the background of the Splash Screen in hex format, #RRGGBB or #RRGGBBAA. + * Doesn't work if `useDialog` is true. * * @since 1.0.0 * @example "#ffffffff" @@ -46,6 +47,7 @@ declare module '@capacitor/cli' { /** * The [ImageView.ScaleType](https://developer.android.com/reference/android/widget/ImageView.ScaleType) used to scale * the Splash Screen image. + * Doesn't work if `useDialog` is true. * * Only available on Android. * @@ -65,6 +67,7 @@ declare module '@capacitor/cli' { /** * Show a loading spinner on the Splash Screen. + * Doesn't work if `useDialog` is true. * * @since 1.0.0 * @example true @@ -73,6 +76,7 @@ declare module '@capacitor/cli' { /** * Style of the Android spinner. + * Doesn't work if `useDialog` is true. * * @since 1.0.0 * @default large @@ -88,6 +92,7 @@ declare module '@capacitor/cli' { /** * Style of the iOS spinner. + * Doesn't work if `useDialog` is true. * * Only available on iOS. * @@ -99,6 +104,7 @@ declare module '@capacitor/cli' { /** * Color of the spinner in hex format, #RRGGBB or #RRGGBBAA. + * Doesn't work if `useDialog` is true. * * @since 1.0.0 * @example "#999999" @@ -124,6 +130,29 @@ declare module '@capacitor/cli' { * @example true */ splashImmersive?: boolean; + + /** + * If `useDialog` is set to true, configure the Dialog layout. + * If `useDialog` is not set or false, use a layout instead of the ImageView. + * + * Only available on Android. + * + * @since 1.1.0 + * @example "launch_screen" + */ + layoutName?: string; + + /** + * Use a Dialog instead of an ImageView. + * If `layoutName` is not configured, it will use + * a layout that uses the splash image as background. + * + * Only available on Android. + * + * @since 1.1.0 + * @example true + */ + useDialog?: boolean; }; } }