feat!: android 12 splash screen (#1441)

* chore!: remove old splashscreen logic
* feat(splashscreen): add backwards compatibility
* chore: remove unused method
* chore: prefix splashscreen_background with cdv_
* feat: support android 12 splashscreen api configs
* feat: improve & refactor the logic for android splashscreen api 12
* feat: splashscreen copy image resources
* feat: splashscreen branding image & xml cleanup
* fix: splashscreen cleanup & branding conditions
* fix: splashscreen @color usage
* feat: update default Apache Cordova splash screen
* chore: add missing asf header
* fix: splashscreen image size
* chore: use Theme.SplashScreen.IconBackground as default parent to support windowSplashScreenIconBackgroundColor
* fix: center default test image by correct pivot
* fix: fs-extra copySync
* feat: re-add AutoHideSplashScreen and SplashScreenDelay preference support
* chore: move splashscreen into CordovaActivity
* feat: support splashscreen.hide & centralize to SplashScreenPlugin
* chore: cleanup SplashScreenPlugin
* feat: support fade, default auto hide on onPageFinished, support delays, refactor
* refactor: cleanup splash screen
* refactor: cleanup remove unused import
* chore: add show method as unsupported
* test: create a spy on updateProjectSplashScreen
* style: add ending new line
* chore: improve logging to warn when image path is missing
* chore: split windowSplashScreenAnimatedIcon and windowSplashScreenBrandingImage case and added branding warning
* chore: improve when to display warning
* fix: add splashscreen dependency to app as well
* chore: move the core-splashscreen dep lower

Co-authored-by: Niklas Merz <niklasmerz@linux.com>
This commit is contained in:
エリス 2022-06-30 10:49:10 +09:00 committed by GitHub
parent 2d2ad4cb81
commit 606e9c4826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 605 additions and 221 deletions

View File

@ -36,6 +36,9 @@ module.exports = {
// TODO: Extract this as a proper plugin.
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
// Core Splash Screen
modulemapper.clobbers('cordova/plugin/android/splashscreen', 'navigator.splashscreen');
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
// Inject a listener for the backbutton on the document.

View File

@ -0,0 +1,33 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var splashscreen = {
show: function () {
console.log('"navigator.splashscreen.show()" is unsupported on Android.');
},
hide: function () {
exec(null, null, 'CordovaSplashScreenPlugin', 'hide', []);
}
};
module.exports = splashscreen;

View File

@ -80,6 +80,7 @@ android {
dependencies {
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
implementation "androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}"
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
}
/**

View File

@ -8,6 +8,7 @@
"KOTLIN_VERSION": "1.5.21",
"ANDROIDX_APP_COMPAT_VERSION": "1.4.2",
"ANDROIDX_WEBKIT_VERSION": "1.4.0",
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0-rc01",
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.10",
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false

View File

@ -76,6 +76,14 @@ public class ConfigXmlParser {
)
);
pluginEntries.add(
new PluginEntry(
SplashScreenPlugin.PLUGIN_NAME,
"org.apache.cordova.SplashScreenPlugin",
true
)
);
parse(action.getResources().getXml(id));
}

View File

@ -42,6 +42,7 @@ import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
/**
* This class is the main Android activity that represents the Cordova
@ -98,11 +99,16 @@ public class CordovaActivity extends AppCompatActivity {
protected ArrayList<PluginEntry> pluginEntries;
protected CordovaInterfaceImpl cordovaInterface;
private SplashScreen splashScreen;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
// Handle the splash screen transition.
splashScreen = SplashScreen.installSplashScreen(this);
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
loadConfig();
@ -125,8 +131,6 @@ public class CordovaActivity extends AppCompatActivity {
// (as was the case in previous cordova versions)
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
immersiveMode = true;
// The splashscreen plugin needs the flags set before we're focused to prevent
// the nav and title bars from flashing in.
setImmersiveUiVisibility();
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
@ -153,6 +157,9 @@ public class CordovaActivity extends AppCompatActivity {
}
cordovaInterface.onCordovaInit(appView.getPluginManager());
// Setup the splash screen based on preference settings
cordovaInterface.pluginManager.postMessage("setupSplashScreen", splashScreen);
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
@ -526,5 +533,4 @@ public class CordovaActivity extends AppCompatActivity {
}
}
}

View File

@ -0,0 +1,168 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.core.splashscreen.SplashScreen;
import androidx.core.splashscreen.SplashScreenViewProvider;
import org.json.JSONArray;
import org.json.JSONException;
@SuppressLint("LongLogTag")
public class SplashScreenPlugin extends CordovaPlugin {
static final String PLUGIN_NAME = "CordovaSplashScreenPlugin";
// Default config preference values
private static final boolean DEFAULT_AUTO_HIDE = true;
private static final int DEFAULT_DELAY_TIME = -1;
private static final boolean DEFAULT_FADE = true;
private static final int DEFAULT_FADE_TIME = 500;
// Config preference values
/**
* @param boolean autoHide to auto splash screen (default=true)
*/
private boolean autoHide;
/**
* @param int delayTime in milliseconds (default=-1)
*/
private int delayTime;
/**
* @param int fade to fade out splash screen (default=true)
*/
private boolean isFadeEnabled;
/**
* @param int fadeDuration fade out duration in milliseconds (default=500)
*/
private int fadeDuration;
// Internal variables
/**
* @param boolean keepOnScreen flag to determine if the splash screen remains visible.
*/
private boolean keepOnScreen = true;
@Override
protected void pluginInitialize() {
// Auto Hide & Delay Settings
autoHide = preferences.getBoolean("AutoHideSplashScreen", DEFAULT_AUTO_HIDE);
delayTime = preferences.getInteger("SplashScreenDelay", DEFAULT_DELAY_TIME);
LOG.d(PLUGIN_NAME, "Auto Hide: " + autoHide);
if (delayTime != DEFAULT_DELAY_TIME) {
LOG.d(PLUGIN_NAME, "Delay: " + delayTime + "ms");
}
// Fade & Fade Duration
isFadeEnabled = preferences.getBoolean("FadeSplashScreen", DEFAULT_FADE);
fadeDuration = preferences.getInteger("FadeSplashScreenDuration", DEFAULT_FADE_TIME);
LOG.d(PLUGIN_NAME, "Fade: " + isFadeEnabled);
if (isFadeEnabled) {
LOG.d(PLUGIN_NAME, "Fade Duration: " + fadeDuration + "ms");
}
}
@Override
public boolean execute(
String action,
JSONArray args,
CallbackContext callbackContext
) throws JSONException {
if (action.equals("hide") && autoHide == false) {
/*
* The `.hide()` method can only be triggered if the `splashScreenAutoHide`
* is set to `false`.
*/
keepOnScreen = false;
} else {
return false;
}
callbackContext.success();
return true;
}
@Override
public Object onMessage(String id, Object data) {
switch (id) {
case "setupSplashScreen":
setupSplashScreen((SplashScreen) data);
break;
case "onPageFinished":
attemptCloseOnPageFinished();
break;
}
return null;
}
private void setupSplashScreen(SplashScreen splashScreen) {
// Setup Splash Screen Delay
splashScreen.setKeepOnScreenCondition(() -> keepOnScreen);
// auto hide splash screen when custom delay is defined.
if (autoHide && delayTime != DEFAULT_DELAY_TIME) {
Handler splashScreenDelayHandler = new Handler();
splashScreenDelayHandler.postDelayed(() -> keepOnScreen = false, delayTime);
}
// auto hide splash screen with default delay (-1) delay is controlled by the
// `onPageFinished` message.
// If auto hide is disabled (false), the hiding of the splash screen must be determined &
// triggered by the front-end code with the `navigator.splashscreen.hide()` method.
// Setup the fade
splashScreen.setOnExitAnimationListener(new SplashScreen.OnExitAnimationListener() {
@Override
public void onSplashScreenExit(@NonNull SplashScreenViewProvider splashScreenViewProvider) {
View splashScreenView = splashScreenViewProvider.getView();
splashScreenView
.animate()
.alpha(0.0f)
.setDuration(isFadeEnabled ? fadeDuration : 0)
.setStartDelay(isFadeEnabled ? 0 : fadeDuration)
.setInterpolator(new AccelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
splashScreenViewProvider.remove();
}
}).start();
}
});
}
private void attemptCloseOnPageFinished() {
if (autoHide && delayTime == DEFAULT_DELAY_TIME) {
keepOnScreen = false;
}
}
}

View File

@ -73,6 +73,8 @@ class Api {
configXml: path.join(appRes, 'xml', 'config.xml'),
defaultConfigXml: path.join(this.root, 'cordova', 'defaults.xml'),
strings: path.join(appRes, 'values', 'strings.xml'),
themes: path.join(appRes, 'values', 'themes.xml'),
colors: path.join(appRes, 'values', 'colors.xml'),
manifest: path.join(appMain, 'AndroidManifest.xml'),
build: path.join(this.root, 'build'),
javaSrc: path.join(appMain, 'java')

View File

@ -62,16 +62,14 @@ module.exports.prepare = function (cordovaProject, options) {
updateUserProjectGradlePropertiesConfig(this, args);
// Update own www dir with project's www assets and plugins' assets and js-files
return Promise.resolve(updateWww(cordovaProject, this.locations)).then(function () {
// update project according to config.xml changes.
return updateProjectAccordingTo(self._config, self.locations);
}).then(function () {
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
}).then(function () {
events.emit('verbose', 'Prepared android project successfully');
});
return Promise.resolve(updateWww(cordovaProject, this.locations))
.then(() => updateProjectAccordingTo(self._config, self.locations))
.then(function () {
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
}).then(function () {
events.emit('verbose', 'Prepared android project successfully');
});
};
/** @param {PlatformApi} project */
@ -171,7 +169,6 @@ module.exports.clean = function (options) {
return Promise.resolve().then(function () {
cleanWww(projectRoot, self.locations);
cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
cleanSplashes(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
cleanFileResources(projectRoot, projectConfig, path.relative(projectRoot, self.locations.root));
});
};
@ -267,19 +264,10 @@ function cleanWww (projectRoot, locations) {
* @param {Object} locations A map of locations for this platform
*/
function updateProjectAccordingTo (platformConfig, locations) {
// Update app name by editing res/values/strings.xml
const strings = xmlHelpers.parseElementtreeSync(locations.strings);
updateProjectStrings(platformConfig, locations);
updateProjectSplashScreen(platformConfig, locations);
const name = platformConfig.name();
strings.find('string[@name="app_name"]').text = name.replace(/'/g, '\\\'');
const shortName = platformConfig.shortName && platformConfig.shortName();
if (shortName && shortName !== name) {
strings.find('string[@name="launcher_name"]').text = shortName.replace(/'/g, '\\\'');
}
fs.writeFileSync(locations.strings, strings.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings);
// Update app name for gradle project
fs.writeFileSync(path.join(locations.root, 'cdv-gradle-name.gradle'),
@ -342,6 +330,262 @@ function updateProjectAccordingTo (platformConfig, locations) {
}
}
/**
* Updates project structure and AndroidManifest according to project's configuration.
*
* @param {ConfigParser} platformConfig A project's configuration that will
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectStrings (platformConfig, locations) {
// Update app name by editing res/values/strings.xml
const strings = xmlHelpers.parseElementtreeSync(locations.strings);
const name = platformConfig.name();
strings.find('string[@name="app_name"]').text = name.replace(/'/g, '\\\'');
const shortName = platformConfig.shortName && platformConfig.shortName();
if (shortName && shortName !== name) {
strings.find('string[@name="launcher_name"]').text = shortName.replace(/'/g, '\\\'');
}
fs.writeFileSync(locations.strings, strings.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out android application name "' + name + '" to ' + locations.strings);
}
/**
* @param {ConfigParser} platformConfig A project's configuration that will
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectSplashScreen (platformConfig, locations) {
// res/values/themes.xml
const themes = xmlHelpers.parseElementtreeSync(locations.themes);
const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]');
[
'windowSplashScreenAnimatedIcon',
'windowSplashScreenAnimationDuration',
'windowSplashScreenBackground',
'windowSplashScreenBrandingImage',
'windowSplashScreenIconBackgroundColor',
'postSplashScreenTheme'
].forEach(themeKey => {
const cdvConfigPrefKey = 'Android' + themeKey.charAt(0).toUpperCase() + themeKey.slice(1);
const cdvConfigPrefValue = platformConfig.getPreference(cdvConfigPrefKey, this.platform);
let themeTargetNode = splashScreenTheme.find(`item[@name="${themeKey}"]`);
switch (themeKey) {
case 'windowSplashScreenBackground':
// use the user defined value for "colors.xml"
updateProjectSplashScreenBackgroundColor(cdvConfigPrefValue, locations);
// force the themes value to `@color/cdv_splashscreen_background`
themeTargetNode.text = '@color/cdv_splashscreen_background';
break;
case 'windowSplashScreenAnimatedIcon':
// handle here the cases of "png" vs "xml" (drawable)
// If "png":
// - Clear out default or previous set "drawable/ic_cdv_splashscreen.xml" if exisiting.
// - Copy png in correct mipmap dir with name "ic_cdv_splashscreen.png"
// If "xml":
// - Clear out "{mipmap}/ic_cdv_splashscreen.png" if exisiting.
// - Copy xml into drawable dir with name "ic_cdv_splashscreen.xml"
// updateProjectSplashScreenIcon()
// value should change depending on case:
// If "png": "@mipmap/ic_cdv_splashscreen"
// If "xml": "@drawable/ic_cdv_splashscreen"
updateProjectSplashScreenImage(locations, themeKey, cdvConfigPrefKey, cdvConfigPrefValue);
break;
case 'windowSplashScreenBrandingImage':
// display warning only when set.
if (cdvConfigPrefValue) {
events.emit('warn', `"${themeKey}" is currently not supported by the splash screen compatibility library. https://issuetracker.google.com/issues/194301890`);
}
updateProjectSplashScreenImage(locations, themeKey, cdvConfigPrefKey, cdvConfigPrefValue);
// force the themes value to `@color/cdv_splashscreen_icon_background`
if (!cdvConfigPrefValue && themeTargetNode) {
splashScreenTheme.remove(themeTargetNode);
} else if (cdvConfigPrefValue) {
// if there is no current node, create a new node.
if (!themeTargetNode) {
themeTargetNode = themes.getroot().makeelement('item', { name: themeKey });
splashScreenTheme.append(themeTargetNode);
}
// set the user defined color.
themeTargetNode.text = '@drawable/ic_cdv_splashscreen_branding';
}
break;
case 'windowSplashScreenIconBackgroundColor':
// use the user defined value for "colors.xml"
updateProjectSplashScreenIconBackgroundColor(cdvConfigPrefValue, locations);
// force the themes value to `@color/cdv_splashscreen_icon_background`
if (!cdvConfigPrefValue && themeTargetNode) {
// currentItem.remove();
splashScreenTheme.remove(themeTargetNode);
} else if (cdvConfigPrefValue) {
// if there is no current color, create a new node.
if (!themeTargetNode) {
themeTargetNode = themes.getroot().makeelement('item', { name: themeKey });
splashScreenTheme.append(themeTargetNode);
}
// set the user defined color.
themeTargetNode.text = '@color/cdv_splashscreen_icon_background';
}
break;
case 'windowSplashScreenAnimationDuration':
themeTargetNode.text = cdvConfigPrefValue || '200';
break;
case 'postSplashScreenTheme':
themeTargetNode.text = cdvConfigPrefValue || '@style/Theme.AppCompat.NoActionBar';
break;
default:
events.emit('warn', `The theme property "${themeKey}" does not exist`);
}
});
fs.writeFileSync(locations.themes, themes.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out Android application themes to ' + locations.themes);
}
/**
* @param {String} splashBackgroundColor SplashScreen Background Color Hex Code
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectSplashScreenBackgroundColor (splashBackgroundColor, locations) {
if (!splashBackgroundColor) { splashBackgroundColor = '#FFFFFF'; }
// res/values/colors.xml
const colors = xmlHelpers.parseElementtreeSync(locations.colors);
colors.find('color[@name="cdv_splashscreen_background"]').text = splashBackgroundColor.replace(/'/g, '\\\'');
fs.writeFileSync(locations.colors, colors.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out Android application SplashScreen Color to ' + locations.colors);
}
/**
* @param {String} splashIconBackgroundColor SplashScreen Icon Background Color Hex Code
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectSplashScreenIconBackgroundColor (splashIconBackgroundColor, locations) {
// res/values/colors.xml
const colors = xmlHelpers.parseElementtreeSync(locations.colors);
// node name
const name = 'cdv_splashscreen_icon_background';
// get the current defined color
let currentColor = colors.find(`color[@name="${name}"]`);
if (!splashIconBackgroundColor && currentColor) {
colors.getroot().remove(currentColor);
} else if (splashIconBackgroundColor) {
// if there is no current color, create a new node.
if (!currentColor) {
currentColor = colors.getroot().makeelement('color', { name });
colors.getroot().append(currentColor);
}
// set the user defined color.
currentColor.text = splashIconBackgroundColor.replace(/'/g, '\\\'');
}
// write out the changes.
fs.writeFileSync(locations.colors, colors.write({ indent: 4 }), 'utf-8');
events.emit('verbose', 'Wrote out Android application SplashScreen Icon Color to ' + locations.colors);
}
function cleanupAndSetProjectSplashScreenImage (srcFile, destFilePath, possiblePreviousDestFilePath, cleanupOnly = false) {
if (fs.existsSync(possiblePreviousDestFilePath)) {
fs.removeSync(possiblePreviousDestFilePath);
}
if (cleanupOnly && fs.existsSync(destFilePath)) {
// Also remove dest file path for cleanup even if previous was not use.
fs.removeSync(destFilePath);
}
if (!cleanupOnly && srcFile && fs.existsSync(srcFile)) {
fs.copySync(srcFile, destFilePath);
}
}
function updateProjectSplashScreenImage (locations, themeKey, cdvConfigPrefKey, cdvConfigPrefValue = '') {
const SPLASH_SCREEN_IMAGE_BY_THEME_KEY = {
windowSplashScreenAnimatedIcon: 'ic_cdv_splashscreen',
windowSplashScreenBrandingImage: 'ic_cdv_splashscreen_branding'
};
const destFileName = SPLASH_SCREEN_IMAGE_BY_THEME_KEY[themeKey] || null;
if (!destFileName) throw new CordovaError(`${themeKey} is not valid for image detection.`);
// Default paths of where images are saved
const destPngDir = path.join(locations.res, 'drawable-nodpi');
const destXmlDir = path.join(locations.res, 'drawable');
// Dest File Name and Path
const destFileNameExt = destFileName + '.xml';
let destFilePath = path.join(destXmlDir, destFileNameExt);
let possiblePreviousDestFilePath = path.join(destPngDir, destFileName + '.png');
// Default Drawable Source File
const defaultSrcFilePath = themeKey !== 'windowSplashScreenBrandingImage'
? require.resolve('cordova-android/templates/project/res/drawable/' + destFileNameExt)
: null;
if (!cdvConfigPrefValue || !fs.existsSync(cdvConfigPrefValue)) {
let emitType = 'verbose';
let emmitMessage = `The "${cdvConfigPrefKey}" is undefined. Cordova's default will be used.`;
if (cdvConfigPrefValue && !fs.existsSync(cdvConfigPrefValue)) {
emitType = 'warn';
emmitMessage = `The "${cdvConfigPrefKey}" value does not exist. Cordova's default will be used.`;
}
events.emit(emitType, emmitMessage);
const cleanupOnly = themeKey === 'windowSplashScreenBrandingImage';
cleanupAndSetProjectSplashScreenImage(defaultSrcFilePath, destFilePath, possiblePreviousDestFilePath, cleanupOnly);
return;
}
const iconExtension = path.extname(cdvConfigPrefValue).toLowerCase();
if (iconExtension === '.png') {
// Put the image at this location.
destFilePath = path.join(destPngDir, destFileName + '.png');
// Check for this file and remove.
possiblePreviousDestFilePath = path.join(destXmlDir, destFileName + '.xml');
// copy the png to correct mipmap folder with name of ic_cdv_splashscreen.png
// delete ic_cdv_splashscreen.xml from drawable folder
// update themes.xml windowSplashScreenAnimatedIcon value to @mipmap/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage(cdvConfigPrefValue, destFilePath, possiblePreviousDestFilePath);
} else if (iconExtension === '.xml') {
// copy the xml to drawable folder with name of ic_cdv_splashscreen.xml
// delete ic_cdv_splashscreen.png from mipmap folder
// update themes.xml windowSplashScreenAnimatedIcon value to @drawable/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage(cdvConfigPrefValue, destFilePath, possiblePreviousDestFilePath);
} else {
// use the default destFilePath & possiblePreviousDestFilePath, no update require.
events.emit('warn', `The "${cdvConfigPrefKey}" had an unsupported extension. Cordova's default will be used.`);
cleanupAndSetProjectSplashScreenImage(defaultSrcFilePath, destFilePath, possiblePreviousDestFilePath);
}
}
// Consturct the default value for versionCode as
// PATCH + MINOR * 100 + MAJOR * 10000
// see http://developer.android.com/tools/publishing/versioning.html
@ -380,68 +624,6 @@ function getAdaptiveImageResourcePath (resourcesDir, type, density, name, source
return resourcePath;
}
function makeSplashCleanupMap (projectRoot, resourcesDir) {
// Build an initial resource map that deletes all existing splash screens
const existingSplashPaths = glob.sync(
`${resourcesDir.replace(/\\/g, '/')}/drawable-*/screen.{png,9.png,webp,jpg,jpeg}`,
{ cwd: projectRoot }
);
return makeCleanResourceMap(existingSplashPaths);
}
function updateSplashes (cordovaProject, platformResourcesDir) {
const resources = cordovaProject.projectConfig.getSplashScreens('android');
// if there are no "splash" elements in config.xml
if (resources.length === 0) {
events.emit('verbose', 'This app does not have splash screens defined');
// We must not return here!
// If the user defines no splash screens, the cleanup map will cause any
// existing splash screen images (e.g. the defaults that we copy into a
// new app) to be removed from the app folder, which is what we want.
}
// Build an initial resource map that deletes all existing splash screens
const resourceMap = makeSplashCleanupMap(cordovaProject.root, platformResourcesDir);
let hadMdpi = false;
resources.forEach(function (resource) {
if (!resource.density) {
return;
}
if (resource.density === 'mdpi') {
hadMdpi = true;
}
const targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', resource.density, 'screen', path.basename(resource.src));
resourceMap[targetPath] = resource.src;
});
// There's no "default" drawable, so assume default == mdpi.
if (!hadMdpi && resources.defaultResource) {
const targetPath = getImageResourcePath(
platformResourcesDir, 'drawable', 'mdpi', 'screen', path.basename(resources.defaultResource.src));
resourceMap[targetPath] = resources.defaultResource.src;
}
events.emit('verbose', 'Updating splash screens at ' + platformResourcesDir);
FileUpdater.updatePaths(
resourceMap, { rootDir: cordovaProject.root }, logFileOp);
}
function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) {
const resources = projectConfig.getSplashScreens('android');
if (resources.length > 0) {
const resourceMap = makeSplashCleanupMap(projectRoot, platformResourcesDir);
events.emit('verbose', 'Cleaning splash screens at ' + platformResourcesDir);
// No source paths are specified in the map, so updatePaths() will delete the target files.
FileUpdater.updatePaths(
resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
}
}
function updateIcons (cordovaProject, platformResourcesDir) {
const icons = cordovaProject.projectConfig.getIcons('android');
@ -521,7 +703,7 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
const android_icons = preparedIcons.android_icons;
const default_icon = preparedIcons.default_icon;
// The source paths for icons and splashes are relative to
// The source paths for icons are relative to
// project's config.xml location, so we use it as base path.
let background;
let foreground;
@ -620,7 +802,7 @@ function updateIconResourceForLegacy (preparedIcons, resourceMap, platformResour
const android_icons = preparedIcons.android_icons;
const default_icon = preparedIcons.default_icon;
// The source paths for icons and splashes are relative to
// The source paths for icons are relative to
// project's config.xml location, so we use it as base path.
for (const density in android_icons) {
const targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher', path.basename(android_icons[density].src));
@ -750,16 +932,6 @@ function mapImageResources (rootDir, subDir, type, resourceName) {
return pathMap;
}
/** Returns resource map that deletes all given paths */
function makeCleanResourceMap (resourcePaths) {
const pathMap = {};
resourcePaths.map(path.normalize)
.forEach(resourcePath => {
pathMap[resourcePath] = null;
});
return pathMap;
}
function updateFileResources (cordovaProject, platformDir) {
const files = cordovaProject.projectConfig.getFileResources('android');

View File

@ -83,18 +83,6 @@ function mockGetIconItem (data) {
}, data);
}
/**
* Create a mock item from the getSplashScreen collection with the supplied updated data.
*
* @param {Object} data Changes to apply to the mock getSplashScreen item
*/
function mockGetSplashScreenItem (data) {
return Object.assign({}, {
src: undefined,
density: undefined
}, data);
}
describe('prepare', () => {
// Rewire
let prepare;
@ -779,7 +767,6 @@ describe('prepare', () => {
prepare.__set__('updateProjectAccordingTo', jasmine.createSpy('updateProjectAccordingTo')
.and.returnValue(Promise.resolve()));
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').and.returnValue(Promise.resolve()));
prepare.__set__('updateSplashes', jasmine.createSpy('updateSplashes').and.returnValue(Promise.resolve()));
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
prepare.__set__('updateConfigFilesFrom',
jasmine.createSpy('updateConfigFilesFrom')
@ -871,7 +858,7 @@ describe('prepare', () => {
prepare.__set__('updateWww', jasmine.createSpy('updateWww'));
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').and.returnValue(Promise.resolve()));
prepare.__set__('updateSplashes', jasmine.createSpy('updateSplashes').and.returnValue(Promise.resolve()));
prepare.__set__('updateProjectSplashScreen', jasmine.createSpy('updateProjectSplashScreen'));
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
prepare.__set__('updateConfigFilesFrom',
jasmine.createSpy('updateConfigFilesFrom')
@ -960,110 +947,4 @@ describe('prepare', () => {
});
});
});
describe('updateSplashes method', function () {
// Mock Data
let cordovaProject;
let platformResourcesDir;
beforeEach(function () {
cordovaProject = {
root: '/mock',
projectConfig: {
path: '/mock/config.xml',
cdvNamespacePrefix: 'cdv'
},
locations: {
plugins: '/mock/plugins',
www: '/mock/www'
}
};
platformResourcesDir = PATH_RESOURCE;
// mocking initial responses for mapImageResources
prepare.__set__('makeSplashCleanupMap', (rootDir, resourcesDir) => ({
[path.join(resourcesDir, 'drawable-mdpi/screen.png')]: null,
[path.join(resourcesDir, 'drawable-mdpi/screen.webp')]: null
}));
});
it('Test#001 : Should detect no defined splash screens.', function () {
const updateSplashes = prepare.__get__('updateSplashes');
// mock data.
cordovaProject.projectConfig.getSplashScreens = function (platform) {
return [];
};
updateSplashes(cordovaProject, platformResourcesDir);
// The emit was called
expect(emitSpy).toHaveBeenCalled();
// The emit message was.
const actual = emitSpy.calls.argsFor(0)[1];
const expected = 'This app does not have splash screens defined';
expect(actual).toEqual(expected);
});
it('Test#02 : Should update splash png icon.', function () {
const updateSplashes = prepare.__get__('updateSplashes');
// mock data.
cordovaProject.projectConfig.getSplashScreens = function (platform) {
return [mockGetSplashScreenItem({
density: 'mdpi',
src: 'res/splash/android/mdpi-screen.png'
})];
};
updateSplashes(cordovaProject, platformResourcesDir);
// The emit was called
expect(emitSpy).toHaveBeenCalled();
// The emit message was.
const actual = emitSpy.calls.argsFor(0)[1];
const expected = 'Updating splash screens at ' + PATH_RESOURCE;
expect(actual).toEqual(expected);
const actualResourceMap = updatePathsSpy.calls.argsFor(0)[0];
const expectedResourceMap = {};
expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.png')] = 'res/splash/android/mdpi-screen.png';
expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.webp')] = null;
expect(actualResourceMap).toEqual(expectedResourceMap);
});
it('Test#03 : Should update splash webp icon.', function () {
const updateSplashes = prepare.__get__('updateSplashes');
// mock data.
cordovaProject.projectConfig.getSplashScreens = function (platform) {
return [mockGetSplashScreenItem({
density: 'mdpi',
src: 'res/splash/android/mdpi-screen.webp'
})];
};
// Creating Spies
updateSplashes(cordovaProject, platformResourcesDir);
// The emit was called
expect(emitSpy).toHaveBeenCalled();
// The emit message was.
const actual = emitSpy.calls.argsFor(0)[1];
const expected = 'Updating splash screens at ' + PATH_RESOURCE;
expect(actual).toEqual(expected);
const actualResourceMap = updatePathsSpy.calls.argsFor(0)[0];
const expectedResourceMap = {};
expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.webp')] = 'res/splash/android/mdpi-screen.webp';
expectedResourceMap[path.join(PATH_RESOURCE, 'drawable-mdpi', 'screen.png')] = null;
expect(actualResourceMap).toEqual(expectedResourceMap);
});
});
});

View File

@ -20,6 +20,7 @@
package __ID__;
import android.os.Bundle;
import org.apache.cordova.*;
public class __ACTIVITY__ extends CordovaActivity

View File

@ -35,7 +35,7 @@
<activity android:name="__ACTIVITY__"
android:label="@string/activity_name"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:theme="@style/Theme.App.SplashScreen"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:exported="true">

View File

@ -287,6 +287,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

View File

@ -0,0 +1,34 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<group
android:pivotX="243"
android:pivotY="256"
android:scaleX="0.5"
android:scaleY="0.5">
<path
android:fillColor="#9D9D9C"
android:fillType="evenOdd"
android:pathData="M425.22,448h-62.16l4.34,-54.86h-30.54L332.52,448H203.68l-4.34,-54.86H168.8l4.34,54.86h-62.16L76.1,210.22L163.38,64h209.44l87.28,146.22L425.22,448zM355.29,137.21h-56.02l3.79,27.43h-69.93l3.79,-27.43h-56.02l-35.06,73.02l17.53,146.22h209.44l17.53,-146.22L355.29,137.21zM324.75,308.02c-4.88,0 -8.67,-15.13 -8.67,-34.05s3.98,-34.05 8.67,-34.05c4.88,0 8.67,15.13 8.67,34.05S329.63,308.02 324.75,308.02zM214.7,310.86c-4.88,0 -8.67,-15.13 -8.67,-34.05s3.98,-34.05 8.67,-34.05c4.88,0 8.67,15.13 8.67,34.05S219.4,310.86 214.7,310.86z" />
</group>
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="cdv_splashscreen_background">#FFFFFFFF</color>
</resources>

View File

@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<resources>
<!-- App label shown within list of installed apps, battery & network usage screens. -->
<string name="app_name">__NAME__</string>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<resources>
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen.IconBackground">
<!-- Optional: Set the splash screen background. (Default: #FFFFFF) -->
<item name="windowSplashScreenBackground">@color/cdv_splashscreen_background</item>
<!-- Required: Add either a drawable or an animated drawable -->
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_cdv_splashscreen</item>
<!-- Required: For animated icons -->
<item name="windowSplashScreenAnimationDuration">200</item>
<!-- Required: Set the theme of the Activity that directly follows your splash screen. -->
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
</style>
</resources>

View File

@ -49,7 +49,6 @@
<preference name="loglevel" value="DEBUG" />
<!--
<preference name="splashscreen" value="splash" />
<preference name="backgroundColor" value="0xFFF" />
<preference name="loadUrlTimeoutValue" value="20000" />
<preference name="InAppBrowserStorageEnabled" value="true" />