Skip to content

Commit

Permalink
Add Dark Mode support to the App template and NewAppScreen components (
Browse files Browse the repository at this point in the history
…#28711)

Summary:
This PR adds support for the dark mode and dynamic theme changing to the default App template and to the related `NewAppScreen` components. Using `useColorScheme` hook forced me to refactor a bit main `App.js` file, but I think those changes are step in the right direction according to way in which React Native is used in larger apps, so new `Section` component has been extracted to reduce code redundancy/repetition inside `App`.

Additional color `darker` has been added to the `Colors` statics from `NewAppScreen` because `dark` was too bright for the Dark Mode backgrounds.

Also main `StoryBoard` on iOS has been updated to use theme based colors instead of static or hardcoded ones. There was also an unused, empty `Label` which I have removed.

~~I'm not so much experienced with Android. If someone could also update Android splash screen (if Android requires such change) it will be nice. I want to look at this later using simulator.~~
> I have updated the Android splash screen and tested this change on the Android emulator.

If you have any comment or corrections feel free to post them out, I would like to put more work into this PR if it's needed. Dark Mode this days is a part of near every OS, so it could be considered as a standard feature. I hope those changes helps people which struggle with the basic theming implementation (+ there is now an example of hook and `children` prop usage in the template).

## Changelog

[Internal] [Added] - Add dark mode support to the default app template
Pull Request resolved: #28711

Test Plan:
I have tested the App from the template on the iOS device and in Android emulator with RN `0.63.0-rc`.

Screen recording on iOS (demonstarates both modes, both splash screens and transition):
![ezgif-6-e24ee8e839c9](https://user-images.githubusercontent.com/719641/80025923-a04b0300-84e1-11ea-824a-b4363db48892.gif)

Screenshot of iOS app in Dark Mode:
![IMG_6542](https://user-images.githubusercontent.com/719641/79885748-c98f6480-83f7-11ea-8c73-1351a721d5d6.PNG)

Screenshot of iOS app splash screen in Dark Mode:
![IMG_6544](https://user-images.githubusercontent.com/719641/79960431-add29f80-8485-11ea-985c-b39176024ffa.PNG)

Screenshot of Android app in the emulator:
![Screenshot_1587566100](https://user-images.githubusercontent.com/719641/79995454-88f72000-84b7-11ea-810b-dfb70de03c2a.png)

Differential Revision: D21236148

Pulled By: shergin

fbshipit-source-id: 0c8a9534d3a3f8f8099af939243a889ac4df6cda
  • Loading branch information
Simek authored and facebook-github-bot committed Apr 24, 2020
1 parent fa69356 commit 2b56011
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 102 deletions.
1 change: 1 addition & 0 deletions Libraries/NewAppScreen/components/Colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export default {
lighter: '#F3F3F3',
light: '#DAE1E7',
dark: '#444',
darker: '#222',
black: '#000',
};
38 changes: 26 additions & 12 deletions Libraries/NewAppScreen/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,40 @@
'use strict';
import Colors from './Colors';
import type {Node} from 'react';
import {Text, StyleSheet, ImageBackground} from 'react-native';
import {ImageBackground, StyleSheet, Text, useColorScheme} from 'react-native';
import React from 'react';

const Header = (): Node => (
<ImageBackground
accessibilityRole={'image'}
source={require('./logo.png')}
style={styles.background}
imageStyle={styles.logo}>
<Text style={styles.text}>Welcome to React</Text>
</ImageBackground>
);
const Header = (): Node => {
const isDarkMode = useColorScheme() === 'dark';
return (
<ImageBackground
accessibilityRole="image"
source={require('./logo.png')}
style={[
styles.background,
{
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
},
]}
imageStyle={styles.logo}>
<Text
style={[
styles.text,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
Welcome to React
</Text>
</ImageBackground>
);
};

const styles = StyleSheet.create({
background: {
paddingBottom: 40,
paddingTop: 96,
paddingHorizontal: 32,
backgroundColor: Colors.lighter,
},
logo: {
opacity: 0.2,
Expand All @@ -48,7 +63,6 @@ const styles = StyleSheet.create({
fontSize: 40,
fontWeight: '600',
textAlign: 'center',
color: Colors.black,
},
});

Expand Down
56 changes: 38 additions & 18 deletions Libraries/NewAppScreen/components/LearnMoreLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
import Colors from './Colors';
import type {Node} from 'react';
import openURLInBrowser from 'react-native/Libraries/Core/Devtools/openURLInBrowser';
import {View, Text, StyleSheet, TouchableOpacity} from 'react-native';
import React from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
useColorScheme,
View,
} from 'react-native';
import React, {Fragment} from 'react';

const links = [
{
Expand Down Expand Up @@ -70,24 +76,40 @@ const links = [
},
];

const LinkList = (): Node => (
<View style={styles.container}>
{links.map(({id, title, link, description}) => {
return (
<React.Fragment key={id}>
<View style={styles.separator} />
const LinkList = (): Node => {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.container}>
{links.map(({id, title, link, description}) => (
<Fragment key={id}>
<View
style={[
styles.separator,
{
backgroundColor: isDarkMode ? Colors.dark : Colors.light,
},
]}
/>
<TouchableOpacity
accessibilityRole={'button'}
accessibilityRole="button"
onPress={() => openURLInBrowser(link)}
style={styles.linkContainer}>
<Text style={styles.link}>{title}</Text>
<Text style={styles.description}>{description}</Text>
<Text
style={[
styles.description,
{
color: isDarkMode ? Colors.lighter : Colors.dark,
},
]}>
{description}
</Text>
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
</Fragment>
))}
</View>
);
};

const styles = StyleSheet.create({
container: {
Expand All @@ -112,11 +134,9 @@ const styles = StyleSheet.create({
paddingVertical: 16,
fontWeight: '400',
fontSize: 18,
color: Colors.dark,
},
separator: {
backgroundColor: Colors.light,
height: 1,
height: StyleSheet.hairlineWidth,
},
});

Expand Down
140 changes: 82 additions & 58 deletions template/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,102 +7,126 @@
*/

import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';

import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

const App: () => React$Node = () => {
const Section = ({children, title}): Node => {
const isDarkMode = useColorScheme() === 'dark';
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Header />
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
<Text style={styles.footer}>Engine: Hermes</Text>
</View>
)}
<View style={styles.body}>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Step One</Text>
<Text style={styles.sectionDescription}>
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>See Your Changes</Text>
<Text style={styles.sectionDescription}>
<ReloadInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Debug</Text>
<Text style={styles.sectionDescription}>
<DebugInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Learn More</Text>
<Text style={styles.sectionDescription}>
Read the docs to discover what to do next:
</Text>
</View>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
</>
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
};

const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';

const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};

const hermes = global.HermesInternal ? (
<View style={styles.engine}>
<Text
style={[
styles.footer,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
Engine: Hermes
</Text>
</View>
) : null;

return (
<SafeAreaView style={backgroundStyle}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
{hermes}
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Step One">
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Section>
<Section title="See Your Changes">
<ReloadInstructions />
</Section>
<Section title="Debug">
<DebugInstructions />
</Section>
<Section title="Learn More">
Read the docs to discover what to do next:
</Section>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
},
body: {
backgroundColor: Colors.white,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
color: Colors.dark,
},
highlight: {
fontWeight: '700',
},
footer: {
color: Colors.dark,
fontSize: 12,
fontWeight: '600',
padding: 4,
Expand Down
2 changes: 1 addition & 1 deletion template/android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:textColor">#000000</item>
</style>
Expand Down
6 changes: 5 additions & 1 deletion template/ios/HelloWorld/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
moduleName:@"HelloWorld"
initialProperties:nil];

rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
Expand Down
13 changes: 1 addition & 12 deletions template/ios/HelloWorld/LaunchScreen.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,21 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
<rect key="frame" x="0.0" y="647" width="375" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="HelloWorld" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
<rect key="frame" x="0.0" y="626" width="375" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="bottom" secondItem="MN2-I3-ftu" secondAttribute="bottom" constant="20" id="OZV-Vh-mqD"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="SfN-ll-jLj"/>
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="akx-eg-2ui"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" id="i1E-0Y-4RG"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
Expand Down

0 comments on commit 2b56011

Please sign in to comment.