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>
3
cordova-js-src/platform.js
vendored
@ -36,6 +36,9 @@ module.exports = {
|
|||||||
// TODO: Extract this as a proper plugin.
|
// TODO: Extract this as a proper plugin.
|
||||||
modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');
|
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';
|
var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';
|
||||||
|
|
||||||
// Inject a listener for the backbutton on the document.
|
// Inject a listener for the backbutton on the document.
|
||||||
|
33
cordova-js-src/plugin/android/splashscreen.js
vendored
Normal 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;
|
@ -80,6 +80,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
|
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
|
||||||
implementation "androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}"
|
implementation "androidx.webkit:webkit:${cordovaConfig.ANDROIDX_WEBKIT_VERSION}"
|
||||||
|
implementation "androidx.core:core-splashscreen:${cordovaConfig.ANDROIDX_CORE_SPLASHSCREEN_VERSION}"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"KOTLIN_VERSION": "1.5.21",
|
"KOTLIN_VERSION": "1.5.21",
|
||||||
"ANDROIDX_APP_COMPAT_VERSION": "1.4.2",
|
"ANDROIDX_APP_COMPAT_VERSION": "1.4.2",
|
||||||
"ANDROIDX_WEBKIT_VERSION": "1.4.0",
|
"ANDROIDX_WEBKIT_VERSION": "1.4.0",
|
||||||
|
"ANDROIDX_CORE_SPLASHSCREEN_VERSION": "1.0.0-rc01",
|
||||||
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.10",
|
"GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION": "4.3.10",
|
||||||
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
|
"IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED": false,
|
||||||
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false
|
"IS_GRADLE_PLUGIN_KOTLIN_ENABLED": false
|
||||||
|
@ -76,6 +76,14 @@ public class ConfigXmlParser {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pluginEntries.add(
|
||||||
|
new PluginEntry(
|
||||||
|
SplashScreenPlugin.PLUGIN_NAME,
|
||||||
|
"org.apache.cordova.SplashScreenPlugin",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
parse(action.getResources().getXml(id));
|
parse(action.getResources().getXml(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ import android.webkit.WebViewClient;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.splashscreen.SplashScreen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the main Android activity that represents the Cordova
|
* 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 ArrayList<PluginEntry> pluginEntries;
|
||||||
protected CordovaInterfaceImpl cordovaInterface;
|
protected CordovaInterfaceImpl cordovaInterface;
|
||||||
|
|
||||||
|
private SplashScreen splashScreen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is first created.
|
* Called when the activity is first created.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
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
|
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
|
||||||
@ -125,8 +131,6 @@ public class CordovaActivity extends AppCompatActivity {
|
|||||||
// (as was the case in previous cordova versions)
|
// (as was the case in previous cordova versions)
|
||||||
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
||||||
immersiveMode = true;
|
immersiveMode = true;
|
||||||
// The splashscreen plugin needs the flags set before we're focused to prevent
|
|
||||||
// the nav and title bars from flashing in.
|
|
||||||
setImmersiveUiVisibility();
|
setImmersiveUiVisibility();
|
||||||
} else {
|
} else {
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
@ -153,6 +157,9 @@ public class CordovaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
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.
|
// Wire the hardware volume controls to control media if desired.
|
||||||
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
||||||
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
||||||
@ -526,5 +533,4 @@ public class CordovaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
168
framework/src/org/apache/cordova/SplashScreenPlugin.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -73,6 +73,8 @@ class Api {
|
|||||||
configXml: path.join(appRes, 'xml', 'config.xml'),
|
configXml: path.join(appRes, 'xml', 'config.xml'),
|
||||||
defaultConfigXml: path.join(this.root, 'cordova', 'defaults.xml'),
|
defaultConfigXml: path.join(this.root, 'cordova', 'defaults.xml'),
|
||||||
strings: path.join(appRes, 'values', 'strings.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'),
|
manifest: path.join(appMain, 'AndroidManifest.xml'),
|
||||||
build: path.join(this.root, 'build'),
|
build: path.join(this.root, 'build'),
|
||||||
javaSrc: path.join(appMain, 'java')
|
javaSrc: path.join(appMain, 'java')
|
||||||
|
364
lib/prepare.js
@ -62,16 +62,14 @@ module.exports.prepare = function (cordovaProject, options) {
|
|||||||
updateUserProjectGradlePropertiesConfig(this, args);
|
updateUserProjectGradlePropertiesConfig(this, args);
|
||||||
|
|
||||||
// Update own www dir with project's www assets and plugins' assets and js-files
|
// Update own www dir with project's www assets and plugins' assets and js-files
|
||||||
return Promise.resolve(updateWww(cordovaProject, this.locations)).then(function () {
|
return Promise.resolve(updateWww(cordovaProject, this.locations))
|
||||||
// update project according to config.xml changes.
|
.then(() => updateProjectAccordingTo(self._config, self.locations))
|
||||||
return updateProjectAccordingTo(self._config, self.locations);
|
.then(function () {
|
||||||
}).then(function () {
|
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
|
||||||
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
|
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
|
||||||
updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
|
}).then(function () {
|
||||||
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
|
events.emit('verbose', 'Prepared android project successfully');
|
||||||
}).then(function () {
|
});
|
||||||
events.emit('verbose', 'Prepared android project successfully');
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @param {PlatformApi} project */
|
/** @param {PlatformApi} project */
|
||||||
@ -171,7 +169,6 @@ module.exports.clean = function (options) {
|
|||||||
return Promise.resolve().then(function () {
|
return Promise.resolve().then(function () {
|
||||||
cleanWww(projectRoot, self.locations);
|
cleanWww(projectRoot, self.locations);
|
||||||
cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res));
|
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));
|
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
|
* @param {Object} locations A map of locations for this platform
|
||||||
*/
|
*/
|
||||||
function updateProjectAccordingTo (platformConfig, locations) {
|
function updateProjectAccordingTo (platformConfig, locations) {
|
||||||
// Update app name by editing res/values/strings.xml
|
updateProjectStrings(platformConfig, locations);
|
||||||
const strings = xmlHelpers.parseElementtreeSync(locations.strings);
|
updateProjectSplashScreen(platformConfig, locations);
|
||||||
|
|
||||||
const name = platformConfig.name();
|
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
|
// Update app name for gradle project
|
||||||
fs.writeFileSync(path.join(locations.root, 'cdv-gradle-name.gradle'),
|
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
|
// Consturct the default value for versionCode as
|
||||||
// PATCH + MINOR * 100 + MAJOR * 10000
|
// PATCH + MINOR * 100 + MAJOR * 10000
|
||||||
// see http://developer.android.com/tools/publishing/versioning.html
|
// see http://developer.android.com/tools/publishing/versioning.html
|
||||||
@ -380,68 +624,6 @@ function getAdaptiveImageResourcePath (resourcesDir, type, density, name, source
|
|||||||
return resourcePath;
|
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) {
|
function updateIcons (cordovaProject, platformResourcesDir) {
|
||||||
const icons = cordovaProject.projectConfig.getIcons('android');
|
const icons = cordovaProject.projectConfig.getIcons('android');
|
||||||
|
|
||||||
@ -521,7 +703,7 @@ function updateIconResourceForAdaptive (preparedIcons, resourceMap, platformReso
|
|||||||
const android_icons = preparedIcons.android_icons;
|
const android_icons = preparedIcons.android_icons;
|
||||||
const default_icon = preparedIcons.default_icon;
|
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.
|
// project's config.xml location, so we use it as base path.
|
||||||
let background;
|
let background;
|
||||||
let foreground;
|
let foreground;
|
||||||
@ -620,7 +802,7 @@ function updateIconResourceForLegacy (preparedIcons, resourceMap, platformResour
|
|||||||
const android_icons = preparedIcons.android_icons;
|
const android_icons = preparedIcons.android_icons;
|
||||||
const default_icon = preparedIcons.default_icon;
|
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.
|
// project's config.xml location, so we use it as base path.
|
||||||
for (const density in android_icons) {
|
for (const density in android_icons) {
|
||||||
const targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher', path.basename(android_icons[density].src));
|
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;
|
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) {
|
function updateFileResources (cordovaProject, platformDir) {
|
||||||
const files = cordovaProject.projectConfig.getFileResources('android');
|
const files = cordovaProject.projectConfig.getFileResources('android');
|
||||||
|
|
||||||
|
@ -83,18 +83,6 @@ function mockGetIconItem (data) {
|
|||||||
}, 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', () => {
|
describe('prepare', () => {
|
||||||
// Rewire
|
// Rewire
|
||||||
let prepare;
|
let prepare;
|
||||||
@ -779,7 +767,6 @@ describe('prepare', () => {
|
|||||||
prepare.__set__('updateProjectAccordingTo', jasmine.createSpy('updateProjectAccordingTo')
|
prepare.__set__('updateProjectAccordingTo', jasmine.createSpy('updateProjectAccordingTo')
|
||||||
.and.returnValue(Promise.resolve()));
|
.and.returnValue(Promise.resolve()));
|
||||||
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').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__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
|
||||||
prepare.__set__('updateConfigFilesFrom',
|
prepare.__set__('updateConfigFilesFrom',
|
||||||
jasmine.createSpy('updateConfigFilesFrom')
|
jasmine.createSpy('updateConfigFilesFrom')
|
||||||
@ -871,7 +858,7 @@ describe('prepare', () => {
|
|||||||
|
|
||||||
prepare.__set__('updateWww', jasmine.createSpy('updateWww'));
|
prepare.__set__('updateWww', jasmine.createSpy('updateWww'));
|
||||||
prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').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__('updateProjectSplashScreen', jasmine.createSpy('updateProjectSplashScreen'));
|
||||||
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
|
prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve()));
|
||||||
prepare.__set__('updateConfigFilesFrom',
|
prepare.__set__('updateConfigFilesFrom',
|
||||||
jasmine.createSpy('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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
package __ID__;
|
package __ID__;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.apache.cordova.*;
|
import org.apache.cordova.*;
|
||||||
|
|
||||||
public class __ACTIVITY__ extends CordovaActivity
|
public class __ACTIVITY__ extends CordovaActivity
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<activity android:name="__ACTIVITY__"
|
<activity android:name="__ACTIVITY__"
|
||||||
android:label="@string/activity_name"
|
android:label="@string/activity_name"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
android:theme="@style/Theme.App.SplashScreen"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
@ -287,6 +287,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||||
implementation "androidx.appcompat:appcompat:${cordovaConfig.ANDROIDX_APP_COMPAT_VERSION}"
|
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) {
|
if (cordovaConfig.IS_GRADLE_PLUGIN_KOTLIN_ENABLED) {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${cordovaConfig.KOTLIN_VERSION}"
|
||||||
|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 190 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 192 KiB |
34
templates/project/res/drawable/ic_cdv_splashscreen.xml
Normal 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>
|
22
templates/project/res/values/colors.xml
Normal 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>
|
@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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>
|
<resources>
|
||||||
<!-- App label shown within list of installed apps, battery & network usage screens. -->
|
<!-- App label shown within list of installed apps, battery & network usage screens. -->
|
||||||
<string name="app_name">__NAME__</string>
|
<string name="app_name">__NAME__</string>
|
||||||
|
34
templates/project/res/values/themes.xml
Normal 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>
|
@ -49,7 +49,6 @@
|
|||||||
|
|
||||||
<preference name="loglevel" value="DEBUG" />
|
<preference name="loglevel" value="DEBUG" />
|
||||||
<!--
|
<!--
|
||||||
<preference name="splashscreen" value="splash" />
|
|
||||||
<preference name="backgroundColor" value="0xFFF" />
|
<preference name="backgroundColor" value="0xFFF" />
|
||||||
<preference name="loadUrlTimeoutValue" value="20000" />
|
<preference name="loadUrlTimeoutValue" value="20000" />
|
||||||
<preference name="InAppBrowserStorageEnabled" value="true" />
|
<preference name="InAppBrowserStorageEnabled" value="true" />
|
||||||
|