Skip to content

Commit

Permalink
feat(android): default to material DayNight theme
Browse files Browse the repository at this point in the history
Fixes TIMOB-28301
  • Loading branch information
jquick-axway authored and sgtcoolguy committed Mar 5, 2021
1 parent 0386c9b commit 86a704f
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 53 deletions.
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</intent>
</queries>

<application android:name=".TitaniumTestApplication" android:icon="@drawable/appicon" android:label="TitaniumTest" android:theme="@style/Theme.MaterialComponents.Bridge" android:usesCleartextTraffic="true">
<application android:name=".TitaniumTestApplication" android:icon="@drawable/appicon" android:label="TitaniumTest" android:theme="@style/Base.Theme.Titanium" android:usesCleartextTraffic="true">
<!-- The root Titanium splash activity which hosts the JS runtime. -->
<activity android:name=".TitaniumTestActivity" android:theme="@style/Theme.Titanium" android:alwaysRetainTaskState="true" android:configChanges="${tiActivityConfigChanges}">
<intent-filter>
Expand Down
21 changes: 10 additions & 11 deletions android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -1648,14 +1648,6 @@ AndroidBuilder.prototype.initialize = async function initialize() {
const loadFromSDCardProp = this.tiapp.properties['ti.android.loadfromsdcard'];
this.loadFromSDCard = loadFromSDCardProp && loadFromSDCardProp.value === true;

// Set default theme to be used in "AndroidManifest.xml" and style resources.
this.defaultAppThemeName = 'Theme.MaterialComponents.Bridge';
if (this.tiapp.fullscreen || this.tiapp['statusbar-hidden']) {
this.defaultAppThemeName = 'Theme.MaterialComponents.Fullscreen.Bridge';
} else if (this.tiapp['navbar-hidden']) {
this.defaultAppThemeName = 'Theme.MaterialComponents.NoActionBar.Bridge';
}

// Array of gradle/maven compatible library reference names the app project depends on.
// Formatted as: "<group.id>:<artifact-id>:<version>"
// Example: "com.google.android.gms:play-services-base:11.0.4"
Expand Down Expand Up @@ -3257,9 +3249,17 @@ AndroidBuilder.prototype.generateTheme = async function generateTheme() {
const xmlFilePath = path.join(valuesDirPath, 'ti_styles.xml');
this.logger.info(__('Generating theme file: %s', xmlFilePath.cyan));

// Set default theme to be used in "AndroidManifest.xml" and style resources.
let defaultAppThemeName = 'Theme.MaterialComponents.DayNight.DarkActionBar';
if (this.tiapp.fullscreen || this.tiapp['statusbar-hidden']) {
defaultAppThemeName = 'Theme.MaterialComponents.DayNight.Fullscreen';
} else if (this.tiapp['navbar-hidden']) {
defaultAppThemeName = 'Theme.MaterialComponents.DayNight.NoActionBar';
}

// Set up "Base.Theme.Titanium.Customizable" inherited themes to use <application/> defined theme, if provided.
// Note: Do not assign it if set to a Titanium theme, which would cause a circular reference.
let customizableParentThemeName = this.defaultAppThemeName;
let customizableParentThemeName = 'Base.Theme.Titanium';
if (this.customAndroidManifest) {
const appTheme = this.customAndroidManifest.getAppAttribute('android:theme');
if (appTheme && !appTheme.startsWith('@style/Theme.Titanium') && !appTheme.startsWith('@style/Base.Theme.Titanium')) {
Expand All @@ -3272,7 +3272,7 @@ AndroidBuilder.prototype.generateTheme = async function generateTheme() {
let xmlLines = [
'<?xml version="1.0" encoding="utf-8"?>',
'<resources>',
` <style name="Base.Theme.Titanium.Basic" parent="${this.defaultAppThemeName}"/>`,
` <style name="Base.Theme.Titanium.RootStyle" parent="${defaultAppThemeName}"/>`,
` <style name="Base.Theme.Titanium.Customizable" parent="${customizableParentThemeName}"/>`,
'',
' <!-- Theme used by "TiRootActivity" derived class which displays the splash screen. -->',
Expand Down Expand Up @@ -3513,7 +3513,6 @@ AndroidBuilder.prototype.generateAndroidManifest = async function generateAndroi
appChildXmlLines: appChildXmlLines,
appIcon: '@drawable/' + this.tiapp.icon.replace(/((\.9)?\.(png|jpg))$/, ''),
appLabel: this.tiapp.name,
appTheme: `@style/${this.defaultAppThemeName}`,
classname: this.classname,
storagePermissionMaxSdkVersion: neededManifestSettings.storagePermissionMaxSdkVersion,
packageName: this.appid,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ti.modules.titanium.ui.widget.picker.CustomDatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/titanium_ui_date_picker_spinner"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:datePickerMode ="spinner"/>
1 change: 0 additions & 1 deletion android/modules/ui/res/layout/titanium_ui_edittext.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ti.modules.titanium.ui.widget.TiUIEditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/titanium_ui_edittext"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
1 change: 0 additions & 1 deletion android/modules/ui/res/layout/titanium_ui_spinner.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Spinner xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/titanium_ui_spinner"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
/>
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TimePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/titanium_ui_time_picker_spinner"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:timePickerMode ="spinner"/>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
Expand Down Expand Up @@ -149,13 +150,13 @@ public abstract class TiUIAbstractTabGroup extends TiUIView
protected boolean smoothScrollOnTabClick = true;
protected boolean tabsDisabled = false;
protected int numTabsWhenDisabled;
protected int colorPrimaryInt;
protected PagerAdapter tabGroupPagerAdapter;
protected ViewPager tabGroupViewPager;
protected TiInsetsProvider insetsProvider = new TiInsetsProvider();
// endregion

// region private fields
private int colorSurfaceInt;
private int textColorInt;
private int unselectedTextColorInt;
private AtomicLong fragmentIdGenerator = new AtomicLong();
Expand All @@ -169,20 +170,19 @@ public TiUIAbstractTabGroup(final TabGroupProxy proxy, TiBaseActivity activity)

// Fetch primary background and text colors from ActionBar style assigned to activity theme.
// Note: We use ActionBar style for backward compatibility with Titanium versions older than 8.0.0.
this.colorPrimaryInt = 0xFF212121; // Default to dark gray.
this.textColorInt = 0xFFFFFFFF; // Default to white.
this.textColorInt = 0xFF212121; // Default to dark gray.
this.unselectedTextColorInt = 0xFFBBBBBB; // Default to light gray.
this.colorSurfaceInt = Color.TRANSPARENT;
try {
final int styleAttributeId = TiRHelper.getResource("attr.actionBarStyle");
final int[] idArray = new int[] {
TiRHelper.getResource("attr.colorPrimary"),
TiRHelper.getResource("attr.textColorPrimary"),
TiRHelper.getResource("attr.colorControlNormal")
TiRHelper.getResource("attr.colorSurface"),
TiRHelper.getResource("attr.colorOnSurface")
};
final TypedArray typedArray = activity.obtainStyledAttributes(null, idArray, styleAttributeId, 0);
final TypedArray typedArray = activity.getTheme().obtainStyledAttributes(idArray);

this.colorPrimaryInt = typedArray.getColor(0, this.colorPrimaryInt);
this.textColorInt = typedArray.getColor(1, this.textColorInt);
this.textColorInt = typedArray.getColor(0, this.textColorInt);
this.colorSurfaceInt = typedArray.getColor(1, this.colorSurfaceInt);
this.unselectedTextColorInt = typedArray.getColor(2, this.unselectedTextColorInt);
typedArray.recycle();
} catch (Exception ex) {
Expand Down Expand Up @@ -318,7 +318,7 @@ protected Drawable createBackgroundDrawableForState(TiViewProxy tabProxy, int st
// If the TabGroup has backgroundColor property, use it. If not - use the primaryColor of the theme.
colorInt = proxy.hasPropertyAndNotNull(TiC.PROPERTY_TABS_BACKGROUND_COLOR)
? TiColorHelper.parseColor(proxy.getProperty(TiC.PROPERTY_TABS_BACKGROUND_COLOR).toString())
: this.colorPrimaryInt;
: this.colorSurfaceInt;
// If the Tab has its own backgroundColor property, use it instead.
colorInt = tabProxy.hasPropertyAndNotNull(TiC.PROPERTY_BACKGROUND_COLOR)
? TiColorHelper.parseColor(tabProxy.getProperty(TiC.PROPERTY_BACKGROUND_COLOR).toString())
Expand Down Expand Up @@ -430,7 +430,7 @@ public void processProperties(KrollDict d)
if (d.containsKeyAndNotNull(TiC.PROPERTY_TABS_BACKGROUND_COLOR)) {
setBackgroundColor(TiColorHelper.parseColor(d.get(TiC.PROPERTY_TABS_BACKGROUND_COLOR).toString()));
} else {
setBackgroundColor(this.colorPrimaryInt);
setBackgroundColor(this.colorSurfaceInt);
}
super.processProperties(d);
}
Expand Down
2 changes: 1 addition & 1 deletion android/templates/build/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
android:label="<%- appLabel %>"
android:name="<%- classname %>Application"
android:usesCleartextTraffic="true"
android:theme="<%- appTheme %>">
android:theme="@style/Base.Theme.Titanium.Customizable">

<activity
android:name=".<%- classname %>Activity"
Expand Down
10 changes: 10 additions & 0 deletions android/titanium/res/values-night/values.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Titanium's dark theme colors. -->
<color name="ti_primary">#92B4F2</color>
<color name="ti_primary_dark">#000000</color>
<color name="ti_accent">#92B4F2</color>
<color name="ti_surface">#353639</color>
<color name="ti_background">#202124</color>
<color name="ti_navigation_bar">#000000</color>
</resources>
57 changes: 50 additions & 7 deletions android/titanium/res/values/values.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base theme applied to Titanium's splash screen activity. -->
<!-- Our "_build.js" will replace it with ".NoActionBar" or ".Fullscreen" theme if set in "tiapp.xml" file. -->
<style name="Base.Theme.Titanium.Basic" parent="Theme.MaterialComponents.Bridge"/>
<!-- Theme colors used by Titanium's splash screen activity. -->
<color name="ti_splash_primary">#000000</color>
<color name="ti_splash_primary_dark">#000000</color>
<color name="ti_splash_navigation_bar">#000000</color>

<!-- Base theme applied to all Titanium opaque windows. -->
<!-- Our "_build.js" script will replace it if custom theme is applied to application in manifest. -->
<style name="Base.Theme.Titanium.Customizable" parent="Theme.AppCompat"/>
<!-- Titanium's light theme colors. -->
<color name="ti_primary">#3475E0</color>
<color name="ti_primary_dark">#303F9F</color>
<color name="ti_accent">#3273DB</color>
<color name="ti_surface">#FFFFFF</color>
<color name="ti_background">#FFFFFF</color>
<color name="ti_navigation_bar">#303F9F</color>

<!-- Root theme inherited by all other Titanium themes. -->
<!-- Our "_build.js" will replace parent with ".NoActionBar" or ".Fullscreen" if set in "tiapp.xml". -->
<style name="Base.Theme.Titanium.RootStyle" parent="Theme.MaterialComponents.DayNight.DarkActionBar"/>

<!-- Titanium's custom theme applied to all activities. This theme is used by default. -->
<style name="Base.Theme.Titanium" parent="Base.Theme.Titanium.RootStyle">
<item name="colorPrimary">@color/ti_primary</item>
<item name="colorPrimaryDark">@color/ti_primary_dark</item>
<item name="colorAccent">@color/ti_accent</item>
<item name="colorSurface">@color/ti_surface</item>
<item name="android:colorBackground">@color/ti_background</item>
<item name="android:navigationBarColor">@color/ti_navigation_bar</item>
</style>

<!-- Base theme applied to all Titanium activities except splash. -->
<!-- Our "_build.js" will replace parent if "AndroidManifest.xml" has custom application theme. -->
<style name="Base.Theme.Titanium.Customizable" parent="Base.Theme.Titanium"/>

<!-- Base theme to be used by the "TiRootActivity" class. -->
<style name="Base.Theme.Titanium.Splash" parent="Base.Theme.Titanium.Basic">
<style name="Base.Theme.Titanium.Splash" parent="Base.Theme.Titanium">
<item name="colorPrimary">@color/ti_splash_primary</item>
<item name="colorPrimaryDark">@color/ti_splash_primary_dark</item>
<item name="android:navigationBarColor">@color/ti_splash_navigation_bar</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
Expand Down Expand Up @@ -56,7 +82,18 @@
<item name="android:windowFullscreen">true</item>
</style>

<!-- Inherits Google's "Theme.MaterialComponents.DayNight.DarkActionBar" and removes top status and title bar. -->
<style name="Theme.MaterialComponents.DayNight.Fullscreen" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

<!-- Inherits Google's "Theme.MaterialComponents.Bridge" and removes top status and title bar. -->
<!-- DEPRECATED: Should use non-bridge version of MaterialComponents theme to avoid material widget issues. -->
<style name="Theme.MaterialComponents.Fullscreen.Bridge" parent="Theme.MaterialComponents.Bridge">
<item name="android:windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item>
Expand All @@ -66,6 +103,12 @@
<item name="windowNoTitle">true</item>
</style>

<!-- Simple error dialog theme with a dark red background and white text. -->
<!-- Note: Cannot derive from material theme in case it's displaying a theming error. -->
<style name="Theme.Titanium.Dialog.Error" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:background">#8A0000</item>
</style>

<!-- Google's dark AppCompat activity theme without a top title bar. -->
<!-- DEPRECATED: Should use MaterialComponents theme instead in order to use material widgets. -->
<style name="Theme.AppCompat.NoTitleBar" parent="Theme.AppCompat">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public abstract class TiBaseActivity extends AppCompatActivity implements TiActi
private TiWeakList<OnPrepareOptionsMenuEvent> onPrepareOptionsMenuListeners =
new TiWeakList<OnPrepareOptionsMenuEvent>();
private boolean sustainMode = false;
private int lastUIModeFlags = 0;
private Intent launchIntent = null;
private TiActionBarStyleHandler actionBarStyleHandler;
private TiActivitySafeAreaMonitor safeAreaMonitor;
Expand Down Expand Up @@ -650,6 +651,25 @@ protected void onCreate(Bundle savedInstanceState)
this.launchIntent = getIntent();
this.safeAreaMonitor = new TiActivitySafeAreaMonitor(this);

// Fetch the current UI mode flags. Used to determine light/dark theme being used.
Configuration config = getResources().getConfiguration();
if (config != null) {
this.lastUIModeFlags = config.uiMode;
}

// If activity is being recreated/restored, then copy last saved intent extras.
// This is needed to acquire the Ti.UI.Window proxy ID this activity should be assigned to.
if ((this.launchIntent != null) && (savedInstanceState != null)) {
Bundle oldExtras = savedInstanceState.getBundle("tiLaunchIntentExtras");
if (oldExtras != null) {
Bundle newExtras = this.launchIntent.getExtras();
if (newExtras != null) {
oldExtras.putAll(newExtras);
}
this.launchIntent.putExtras(oldExtras);
}
}

TiApplication tiApp = getTiApp();
TiApplication.addToActivityStack(this);

Expand Down Expand Up @@ -768,7 +788,11 @@ public void onChanged(TiActivitySafeAreaMonitor monitor)
originalOrientationMode = getRequestedOrientation();

if (window != null) {
window.onWindowActivityCreated();
try {
window.onWindowActivityCreated();
} catch (Throwable t) {
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(null, t);
}
}
if (activityProxy != null) {
dispatchCallback(TiC.PROPERTY_ON_CREATE, null);
Expand Down Expand Up @@ -1161,7 +1185,7 @@ public boolean onPrepareOptionsMenu(Menu menu)
}

@Override
public void onConfigurationChanged(Configuration newConfig)
public void onConfigurationChanged(@NonNull Configuration newConfig)
{
super.onConfigurationChanged(newConfig);

Expand All @@ -1181,6 +1205,13 @@ public void onConfigurationChanged(Configuration newConfig)
listener.get().onConfigurationChanged(this, newConfig);
}
}

// Recreate this activity if the OS has switched between light/dark theme.
final int NIGHT_MASK = Configuration.UI_MODE_NIGHT_MASK;
if ((newConfig.uiMode & NIGHT_MASK) != (this.lastUIModeFlags & NIGHT_MASK)) {
this.recreate();
}
this.lastUIModeFlags = newConfig.uiMode;
}

@Override
Expand Down Expand Up @@ -1580,10 +1611,14 @@ protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);

// If the activity is forced to destroy by Android, save the supportHelperId so
// we can get it back when the activity is recovered.
if (!isFinishing() && supportHelper != null) {
outState.putInt("supportHelperId", supportHelperId);
// If activity is being temporarily destroyed, then save settings to be restored when activity is recreated.
if (!isFinishing()) {
if (supportHelper != null) {
outState.putInt("supportHelperId", supportHelperId);
}
if (this.launchIntent != null) {
outState.putBundle("tiLaunchIntentExtras", this.launchIntent.getExtras());
}
}

synchronized (instanceStateListeners.synchronizedList())
Expand Down

0 comments on commit 86a704f

Please sign in to comment.