Skip to content

Commit

Permalink
Merge branch 'master' into TIMOB-27862
Browse files Browse the repository at this point in the history
  • Loading branch information
ssekhri committed May 13, 2020
2 parents 9e59b76 + 1d20bab commit 16f2b5b
Show file tree
Hide file tree
Showing 67 changed files with 2,355 additions and 419 deletions.
110 changes: 108 additions & 2 deletions android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2303,7 +2303,10 @@ AndroidBuilder.prototype.generateAppProject = async function generateAppProject(
this.generateI18N(),

// Generate a "res/values" styles XML file if a custom theme was assigned in app's "AndroidManifest.xml".
this.generateTheme()
this.generateTheme(),

// Generate "semantic.colors.xml" in "res/values" and "res/values-night"
this.generateSemanticColors()
]);

// Generate an "AndroidManifest.xml" for the app and copy in any custom manifest settings from "tiapp.xml".
Expand Down Expand Up @@ -3314,6 +3317,101 @@ AndroidBuilder.prototype.generateI18N = async function generateI18N() {
}
};

AndroidBuilder.prototype.generateSemanticColors = async function generateSemanticColors() {
this.logger.info(__('Generating semantic colors resources'));
const _t = this;
const xmlFileName = 'ti.semantic.colors.xml';
const valuesDirPath = path.join(this.buildAppMainResDir, 'values');
const valuesNightDirPath = path.join(this.buildAppMainResDir, 'values-night');
await fs.ensureDir(valuesDirPath);
await fs.ensureDir(valuesNightDirPath);
const destLight = path.join(valuesDirPath, xmlFileName);
const destNight = path.join(valuesNightDirPath, xmlFileName);

let colorsFile = path.join(this.projectDir, 'Resources', 'android', 'semantic.colors.json');

if (!fs.existsSync(colorsFile)) {
// Fallback to root of Resources folder for Classic applications
colorsFile = path.join(this.projectDir, 'Resources', 'semantic.colors.json');
}

if (!fs.existsSync(colorsFile)) {
this.logger.debug(__('Skipping colorset generation as "semantic.colors.json" file does not exist'));
return;
}

const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;

function hexToRgb(hex) {
let alphaHex = 'ff';
let color = hex;
if (hex.color) {
color = hex.color;
let alpha = Math.round(255 * parseFloat(hex.alpha) / 100);
if (alpha <= 255) {
alphaHex = alpha.toString(16);
if (alpha < 16) {
alphaHex = '0' + alphaHex;
}
}
}
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
color = color.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);

var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
if (alphaHex === 'ff') {
return `#${result[1]}${result[2]}${result[3]}`;
} else {
return `#${alphaHex}${result[1]}${result[2]}${result[3]}`;
}
}

function appendToXml(dom, root, color, colorValue) {
const appnameNode = dom.createElement('color');

appnameNode.setAttribute('name', `${color}`);
appnameNode.appendChild(dom.createTextNode(hexToRgb(colorValue)));
root.appendChild(dom.createTextNode('\n\t'));
root.appendChild(appnameNode);
}

function writeXml(dom, dest, mode) {
if (fs.existsSync(dest)) {
_t.logger.debug(__('Merging %s semantic colors => %s', mode.cyan, dest.cyan));
} else {
_t.logger.debug(__('Writing %s semantic colors => %s', mode.cyan, dest.cyan));
}
return fs.writeFile(dest, '<?xml version="1.0" encoding="UTF-8"?>\n' + dom.documentElement.toString());
}

const colors = fs.readJSONSync(colorsFile);
const domLight = new DOMParser().parseFromString('<resources/>', 'text/xml');
const domNight = new DOMParser().parseFromString('<resources/>', 'text/xml');

const rootLight = domLight.documentElement;
const rootNight = domNight.documentElement;

for (const [ color, colorValue ] of Object.entries(colors)) {
if (!colorValue.light) {
this.logger.warn(`Skipping ${color} as it does not include a light value`);
continue;
}

if (!colorValue.dark) {
this.logger.warn(`Skipping ${color} as it does not include a dark value`);
continue;
}

appendToXml(domLight, rootLight, color, colorValue.light);
appendToXml(domNight, rootNight, color, colorValue.dark);
}

return Promise.all([
writeXml(domLight, destLight, 'light'),
writeXml(domNight, destNight, 'night')
]);
};

AndroidBuilder.prototype.generateTheme = async function generateTheme() {
// Log the theme XML file we're about to generate.
const valuesDirPath = path.join(this.buildAppMainResDir, 'values');
Expand Down Expand Up @@ -3689,7 +3787,15 @@ AndroidBuilder.prototype.buildAppProject = async function buildAppProject() {

// Set path to the app-bundle file that was built up above.
// Our "package.js" event hook will later copy it to the developer's chosen destination directory.
this.aabFile = path.join(this.buildDir, 'app', 'build', 'outputs', 'bundle', 'release', 'app.aab');
this.aabFile = path.join(this.buildDir, 'app', 'build', 'outputs', 'bundle', 'release', 'app-release.aab');
}

// Verify that we can find the above built file(s).
if (!await fs.exists(this.apkFile)) {
throw new Error(`Failed to find built APK file: ${this.apkFile}`);
}
if (this.aabFile && !await fs.exists(this.aabFile)) {
throw new Error(`Failed to find built AAB file: ${this.aabFile}`);
}
};

Expand Down
34 changes: 13 additions & 21 deletions android/cli/hooks/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,32 @@ exports.init = function (logger, config, cli) {
return finished();
}

// Fetch a path to the built APK file.
let sourceFilePath = builder.apkFile;
if (!sourceFilePath || !fs.existsSync(sourceFilePath)) {
logger.error(__('No APK file to deploy, skipping'));
return finished();
}

// Do not continue if developer did not provide a destination directory.
const outputDir = builder.outputDir;
if (!outputDir) {
logger.error(__('Packaging output directory path cannot be empty.'));
return finished();
}

// Copy built app file(s) to destination directory.
if (outputDir !== path.dirname(sourceFilePath)) {
// Create the destination directory.
fs.ensureDirSync(outputDir);
// Create the destination directory.
fs.ensureDirSync(outputDir);

// Copy built APK to destination.
let outputFilePath = path.join(outputDir, builder.tiapp.name + '.apk');
// Copy built APK to destination, if available.
if (builder.apkFile && fs.existsSync(builder.apkFile)) {
const outputFilePath = path.join(outputDir, builder.tiapp.name + '.apk');
if (fs.existsSync(outputFilePath)) {
fs.unlinkSync(outputFilePath);
}
appc.fs.copyFileSync(sourceFilePath, outputFilePath, { logger: logger.debug });
appc.fs.copyFileSync(builder.apkFile, outputFilePath, { logger: logger.debug });
}

// Copy built app-bundle to destination, if available.
if (builder.aabFile && fs.existsSync(builder.aabFile)) {
outputFilePath = path.join(outputDir, builder.tiapp.name + '.aab');
if (fs.existsSync(outputFilePath)) {
fs.unlinkSync(outputFilePath);
}
appc.fs.copyFileSync(builder.aabFile, outputFilePath, { logger: logger.debug });
// Copy built app-bundle to destination, if available.
if (builder.aabFile && fs.existsSync(builder.aabFile)) {
const outputFilePath = path.join(outputDir, builder.tiapp.name + '.aab');
if (fs.existsSync(outputFilePath)) {
fs.unlinkSync(outputFilePath);
}
appc.fs.copyFileSync(builder.aabFile, outputFilePath, { logger: logger.debug });
}

logger.info(__('Packaging complete'));
Expand Down
68 changes: 67 additions & 1 deletion android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2020 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package ti.modules.titanium.ui;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.KrollRuntime;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.titanium.TiApplication;
Expand All @@ -22,6 +24,11 @@
import org.appcelerator.titanium.util.TiUIHelper;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
Expand Down Expand Up @@ -377,11 +384,36 @@ public class UIModule extends KrollModule
@Kroll.constant
public static final int HIDDEN_BEHAVIOR_INVISIBLE = View.INVISIBLE;

@Kroll.constant
public static final int USER_INTERFACE_STYLE_LIGHT = Configuration.UI_MODE_NIGHT_NO;
@Kroll.constant
public static final int USER_INTERFACE_STYLE_DARK = Configuration.UI_MODE_NIGHT_YES;
@Kroll.constant
public static final int USER_INTERFACE_STYLE_UNSPECIFIED = Configuration.UI_MODE_NIGHT_UNDEFINED;

protected static final int MSG_LAST_ID = KrollProxy.MSG_LAST_ID + 101;

public UIModule()
{
super();

// Register the module's broadcast receiver.
final UIModule.Receiver broadcastReceiver = new UIModule.Receiver(this);
TiApplication.getInstance().registerReceiver(broadcastReceiver,
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));

// Set up a listener to be invoked when the JavaScript runtime is about to be terminated/disposed.
KrollRuntime.addOnDisposingListener(new KrollRuntime.OnDisposingListener() {
@Override
public void onDisposing(KrollRuntime runtime)
{
// Remove this listener from the runtime's static collection.
KrollRuntime.removeOnDisposingListener(this);

// Unregister this module's broadcast receviers.
TiApplication.getInstance().unregisterReceiver(broadcastReceiver);
}
});
}

@Kroll.setProperty(runOnUiThread = true)
Expand Down Expand Up @@ -483,9 +515,43 @@ protected void doSetOrientation(int tiOrientationMode)
}
}

@Kroll.getProperty
public int getUserInterfaceStyle()
{
return TiApplication.getInstance().getApplicationContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
}

@Override
public String getApiName()
{
return "Ti.UI";
}

private class Receiver extends BroadcastReceiver
{
private UIModule module;
private int lastEmittedStyle;

public Receiver(UIModule module)
{
super();
this.module = module;
lastEmittedStyle = this.module.getUserInterfaceStyle();
}

@Override
public void onReceive(Context context, Intent intent)
{
int currentMode = this.module.getUserInterfaceStyle();
if (currentMode == lastEmittedStyle) {
return;
}
lastEmittedStyle = currentMode;

KrollDict event = new KrollDict();
event.put(TiC.PROPERTY_VALUE, lastEmittedStyle);
this.module.fireEvent(TiC.EVENT_USER_INTERFACE_STYLE, event);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,13 @@ private boolean checkImageScrollBeyondBorders(float dx, float dy)

public void setTintColor(String color)
{
this.tintColor = TiColorHelper.parseColor(color);
if (this.tintColor == 0) {
if (color == null || color.isEmpty()) {
imageView.clearColorFilter();
} else {
imageView.setColorFilter(this.tintColor, Mode.SRC_ATOP);
return;
}

this.tintColor = TiColorHelper.parseColor(color);
imageView.setColorFilter(this.tintColor, Mode.SRC_IN);
}

public int getTintColor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ protected void handleHide()
@Override
public void onCancel(DialogInterface dialog)
{
if (dialog != this.progressDialog) {
return;
}

this.visible = false;
this.progressDialog = null;
fireEvent(TiC.EVENT_CANCEL, null);
Expand All @@ -287,6 +291,10 @@ public void onCancel(DialogInterface dialog)
@Override
public void onDismiss(DialogInterface dialog)
{
if (dialog != this.progressDialog) {
return;
}

this.visible = false;
this.progressDialog = null;
}
Expand Down
9 changes: 9 additions & 0 deletions android/templates/module/generated/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ preBuild.doFirst {
}
}

// If Titanium failed to set Android NDK path in "local.properties", then assume NDK is not installed.
// Have gradle auto-download NDK by setting the version we want below. (Will fail if a different version is installed.)
// Must be set to a stable release version listed here: https://developer.android.com/ndk/downloads
def localProperties = new Properties()
localProperties.load(file("${rootDir}/local.properties").newDataInputStream())
if (localProperties.get('ndk.dir') == null) {
android.ndkVersion '20.1.5948944'
}

// Set up project to compile Java side before compiling the C/C++ side.
// We must do this because our "kroll-apt" Java annotation processor generates C++ source files.
project.afterEvaluate {
Expand Down
9 changes: 7 additions & 2 deletions android/titanium/src/java/org/appcelerator/titanium/TiC.java
Original file line number Diff line number Diff line change
Expand Up @@ -697,12 +697,17 @@ public class TiC
/**
* @module.api
*/
public static final String EVENT_USER_LEAVE_HINT = "userleavehint";
public static final String EVENT_USER_INTERACTION = "userinteraction";

/**
* @module.api
*/
public static final String EVENT_USER_INTERACTION = "userinteraction";
public static final String EVENT_USER_INTERFACE_STYLE = "userinterfacestyle";

/**
* @module.api
*/
public static final String EVENT_USER_LEAVE_HINT = "userleavehint";

/**
* @module.api
Expand Down

0 comments on commit 16f2b5b

Please sign in to comment.