Skip to content

Commit

Permalink
feat: add support for semantic colors
Browse files Browse the repository at this point in the history
This adds a cross platfom method for loading semanitc colors, on iOS 11+ we will use the native Ti.UI.iOS.fetchSemanticColor to load the right color, in all other cases we use the Ti.UI.semanticColorType and the provided json file to obtain the correct value

Fixes TIMOB-27126
  • Loading branch information
ewanharris committed Jul 31, 2019
1 parent 8871299 commit 064739e
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 2 deletions.
26 changes: 26 additions & 0 deletions apidoc/Titanium/UI/UI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ methods:
type: Number
constants: Titanium.UI.UNIT_*

- name: fetchSemanticColor
summary: |
Fetches the correct color to be used with a UI element dependent on the users current dark mode setting on iOS 11+ or the [Titanium.UI.semanticColorType](Titanium.UI.semanticColorType) setting in other instances.
parameters:
- name: colorName
summary: Name of the semantic color defined in the applications colorset.
type: String
returns:
- type: String
since: "8.2.0"

properties:
- name: ANIMATION_CURVE_EASE_IN
Expand Down Expand Up @@ -2309,6 +2319,22 @@ properties:
type: Number
permission: read-only

- name: SEMANTIC_COLOR_TYPE_DARK
summary: Return the dark value from the applications colorset
type: String
permission: read-only
since: "8.2.0"

- name: SEMANTIC_COLOR_TYPE_LIGHT
summary: Return the dark value from the applications colorset.
type: String
permission: read-only
since: "8.2.0"

- name: semanticColorType
summary: When running on Android, iOS 10 or lower, or Windows the value to return form the applications colorset.
constants: Titanium.UI.SEMANTIC_COLOR_TYPE_*
since: "8.2.0"

- name: SIZE
summary: SIZE behavior for UI layout.
Expand Down
6 changes: 4 additions & 2 deletions build/lib/packager.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,12 @@ class Packager {
input: `${tmpBundleDir}/Resources/ti.main.js`,
plugins: [
resolve(),
commonjs(),
commonjs({
ignore: [ '/semantic.colors.json' ]
}),
babel(babelOptions)
],
external: [ './app', 'com.appcelerator.aca' ]
external: [ './app', 'com.appcelerator.aca', '/semantic.colors.json' ]
});

// write the bundle to disk
Expand Down
1 change: 1 addition & 0 deletions common/Resources/ti.internal/extensions/ti/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// Load extensions to polyfill our own APIs
import './ti.blob';
import './ti.ui';
46 changes: 46 additions & 0 deletions common/Resources/ti.internal/extensions/ti/ti.ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Appcelerator Titanium Mobile
* Copyright (c) 2019 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/

let colorset;
let osVersion;

// As Android passes a new instance of Ti.UI to every JS file we can't just
// Ti.UI within this file, we must call kroll.binding to get the Titanium
// namespace that is passed in with require and that deal with the .UI
// namespace that is on that directly.
let uiModule = Ti.UI;
if (Ti.Android) {
uiModule = kroll.binding('Titanium').Titanium.UI;
}

uiModule.SEMANTIC_COLOR_TYPE_LIGHT = 'light';
uiModule.SEMANTIC_COLOR_TYPE_DARK = 'dark';
uiModule.semanticColorType = Ti.UI.SEMANTIC_COLOR_TYPE_LIGHT;

uiModule.fetchSemanticColor = function fetchSemanticColor (colorName) {
if (!osVersion) {
osVersion = parseInt(Ti.Platform.version.split('.')[0]);
}

if (Ti.iOS && osVersion >= 11) {
return Ti.UI.iOS.fetchSemanticColor(colorName);
} else {
if (!colorset) {
try {
colorset = require('/semantic.colors.json'); // eslint-disable-line import/no-absolute-path
} catch (error) {
console.error('Failed to require colors file at /semantic.colors.json');
return;
}
}
try {
return colorset[colorName][uiModule.semanticColorType];
} catch (error) {
console.log(`Failed to lookup color for ${colorName}`);
}
}
};
11 changes: 11 additions & 0 deletions iphone/Classes/TiUIiOSProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -841,5 +841,16 @@ - (id)createWebViewProcessPool:(id)args
MAKE_SYSTEM_PROP(INJECTION_TIME_DOCUMENT_END, WKUserScriptInjectionTimeAtDocumentEnd);
#endif

- (TiColor *)fetchSemanticColor:(id)color
{
ENSURE_SINGLE_ARG(color, NSString);

if ([TiUtils isIOSVersionOrGreater:@"11.0"]) {
return [[TiColor alloc] initWithColor:[UIColor colorNamed:color] name:nil];
} else {
return [[TiColor alloc] initWithColor:UIColor.blackColor name:@"black"];
}
}

@end
#endif
103 changes: 103 additions & 0 deletions iphone/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5772,6 +5772,109 @@ iOSBuilder.prototype.copyResources = function copyResources(next) {
}, this);
},

function generateSemanticColors() {
const colorsFile = path.join(this.projectDir, 'Resources', 'iphone', 'semantic.colors.json');
const assetCatalog = path.join(this.buildDir, 'Assets.xcassets');

function hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.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(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}

if (!fs.existsSync(colorsFile)) {
return;
}
const colors = fs.readJSONSync(colorsFile);

for (const [ color, colorValue ] of Object.entries(colors)) {
const colorDir = path.join(assetCatalog, `${color}.colorset`);

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

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

const defaultRGB = hexToRgb(colorValue.default || colorValue.light);
const lightRGB = hexToRgb(colorValue.light);
const darkRGB = hexToRgb(colorValue.dark);

const colorSource = {
info: {
version: 1,
author: 'xcode'
},
colors: []
};

// Default
colorSource.colors.push({
idiom: 'universal',
color: {
'color-space': 'srgb',
components: {
red: `${defaultRGB.r}`,
green: `${defaultRGB.g}`,
blue: `${defaultRGB.b}`,
alpha: '1.000'
}
}
});

// Light
colorSource.colors.push({
idiom: 'universal',
appearances: [ {
appearance: 'luminosity',
value: 'light'
} ],
color: {
'color-space': 'srgb',
components: {
red: `${lightRGB.r}`,
green: `${lightRGB.g}`,
blue: `${lightRGB.b}`,
alpha: '1.000'
}
}
});

// Dark
colorSource.colors.push({
idiom: 'universal',
appearances: [ {
appearance: 'luminosity',
value: 'dark'
} ],
color: {
'color-space': 'srgb',
components: {
red: `${darkRGB.r}`,
green: `${darkRGB.g}`,
blue: `${darkRGB.b}`,
alpha: '1.000'
}
}
});

fs.ensureDirSync(colorDir);
fs.writeJsonSync(path.join(colorDir, 'Contents.json'), colorSource);
this.unmarkBuildDirFile(path.join(colorDir, 'Contents.json'));
}
},

function copyResources() {
this.logger.debug(__('Copying resources'));
Object.keys(resourcesToCopy).forEach(function (file) {
Expand Down

0 comments on commit 064739e

Please sign in to comment.