mirror of
				https://gitlab.com/ceda_ei/sonzai.git
				synced 2025-11-04 00:50:05 +01:00 
			
		
		
		
	Add Theme Picker.
Moved Provider into App.js. Add AppContainer to store theme in redux store. Add reducers and actions for theme, SET_THEME.
This commit is contained in:
		
							
								
								
									
										122
									
								
								App.js
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								App.js
									
									
									
									
									
								
							@@ -1,20 +1,22 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Sample React Native App
 | 
			
		||||
 * https://github.com/facebook/react-native
 | 
			
		||||
 *
 | 
			
		||||
 * @format
 | 
			
		||||
 * @flow
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import {
 | 
			
		||||
	Appbar,
 | 
			
		||||
	BottomNavigation,
 | 
			
		||||
	Text,
 | 
			
		||||
	Card,
 | 
			
		||||
	Dialog,
 | 
			
		||||
	Portal,
 | 
			
		||||
	Button,
 | 
			
		||||
	Provider,
 | 
			
		||||
	RadioButton,
 | 
			
		||||
	TouchableRipple
 | 
			
		||||
} from "react-native-paper";
 | 
			
		||||
 | 
			
		||||
import { View, StatusBar, StyleSheet } from "react-native";
 | 
			
		||||
 | 
			
		||||
import SubjectsContainer from "./containers/SubjectsContainer";
 | 
			
		||||
import themes from "./themes";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function Dummy() {
 | 
			
		||||
@@ -28,7 +30,32 @@ function Dummy() {
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
function ThemePicker({ onPress, selectionIdx }) {
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			{themes.map((t, idx) => (
 | 
			
		||||
				<TouchableRipple
 | 
			
		||||
					onPress={() => onPress(t, idx)}
 | 
			
		||||
					key={idx}
 | 
			
		||||
				>
 | 
			
		||||
					<View style={styles.radio}>
 | 
			
		||||
						<RadioButton
 | 
			
		||||
							status={selectionIdx === idx ? "checked": "unchecked"}
 | 
			
		||||
						/>
 | 
			
		||||
						<Text>{t.name}</Text>
 | 
			
		||||
					</View>
 | 
			
		||||
				</TouchableRipple>
 | 
			
		||||
			))}
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ThemePicker.propTypes = {
 | 
			
		||||
	onPress: PropTypes.func,
 | 
			
		||||
	selectionIdx: PropTypes.number,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const App = ({ theme, setTheme }) => {
 | 
			
		||||
	const [ pane, setPane ] = useState({
 | 
			
		||||
		index: 0,
 | 
			
		||||
		routes: [
 | 
			
		||||
@@ -38,30 +65,81 @@ const App = () => {
 | 
			
		||||
			{ key: "subjects", title: "Subjects", icon: "book-open" },
 | 
			
		||||
		],
 | 
			
		||||
	});
 | 
			
		||||
	const [ showDialog, setShowDialog ] = useState(false);
 | 
			
		||||
	const [ newTheme, setNewTheme ] = useState(themes.findIndex(
 | 
			
		||||
		i => JSON.stringify(i.theme) === JSON.stringify(theme)
 | 
			
		||||
	));
 | 
			
		||||
	const renderScene = BottomNavigation.SceneMap({
 | 
			
		||||
		add: Dummy,
 | 
			
		||||
		statistics: Dummy,
 | 
			
		||||
		timetable: Dummy,
 | 
			
		||||
		subjects: SubjectsContainer,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	function dialogSelection(theme, idx) {
 | 
			
		||||
		setNewTheme(idx);
 | 
			
		||||
		setTheme(theme.theme);
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<Appbar.Header>
 | 
			
		||||
				<Appbar.Content
 | 
			
		||||
					title="Sonzai"
 | 
			
		||||
				/>
 | 
			
		||||
			</Appbar.Header>
 | 
			
		||||
			<BottomNavigation
 | 
			
		||||
				navigationState={pane}
 | 
			
		||||
				onIndexChange={(index) => setPane({
 | 
			
		||||
					index,
 | 
			
		||||
					routes: pane.routes
 | 
			
		||||
				})}
 | 
			
		||||
				renderScene={renderScene}
 | 
			
		||||
				shifting={true}
 | 
			
		||||
			<StatusBar
 | 
			
		||||
				backgroundColor={
 | 
			
		||||
					theme.dark && theme.mode === "adaptive" ?
 | 
			
		||||
						theme.colors.surface :
 | 
			
		||||
						theme.colors.primary
 | 
			
		||||
				}
 | 
			
		||||
			/>
 | 
			
		||||
			<Provider theme={theme}>
 | 
			
		||||
				<Appbar.Header>
 | 
			
		||||
					<Appbar.Content
 | 
			
		||||
						title="Sonzai"
 | 
			
		||||
					/>
 | 
			
		||||
					<Appbar.Action
 | 
			
		||||
						icon="brush"
 | 
			
		||||
						onPress={() => setShowDialog(true)}
 | 
			
		||||
					/>
 | 
			
		||||
				</Appbar.Header>
 | 
			
		||||
				<BottomNavigation
 | 
			
		||||
					navigationState={pane}
 | 
			
		||||
					onIndexChange={(index) => setPane({
 | 
			
		||||
						index,
 | 
			
		||||
						routes: pane.routes
 | 
			
		||||
					})}
 | 
			
		||||
					renderScene={renderScene}
 | 
			
		||||
					shifting={true}
 | 
			
		||||
				/>
 | 
			
		||||
				<Portal>
 | 
			
		||||
					<Dialog
 | 
			
		||||
						visible={showDialog}
 | 
			
		||||
						onDismiss={() => setShowDialog(false)}>
 | 
			
		||||
						<Dialog.Title>Theme</Dialog.Title>
 | 
			
		||||
						<Dialog.Content>
 | 
			
		||||
							<ThemePicker
 | 
			
		||||
								onPress={dialogSelection}
 | 
			
		||||
								selectionIdx={newTheme}
 | 
			
		||||
							/>
 | 
			
		||||
						</Dialog.Content>
 | 
			
		||||
						<Dialog.Actions>
 | 
			
		||||
							<Button onPress={() => setShowDialog(false)}>Close</Button>
 | 
			
		||||
						</Dialog.Actions>
 | 
			
		||||
					</Dialog>
 | 
			
		||||
				</Portal>
 | 
			
		||||
			</Provider>
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
App.propTypes = {
 | 
			
		||||
	theme: PropTypes.object,
 | 
			
		||||
	setTheme: PropTypes.func,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const styles = StyleSheet.create({
 | 
			
		||||
	radio: {
 | 
			
		||||
		flexDirection: "row",
 | 
			
		||||
		alignItems: "center",
 | 
			
		||||
		margin: 5
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								AppContainer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								AppContainer.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import { connect } from "react-redux";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
import { setTheme } from "./actions";
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = state => {
 | 
			
		||||
	return {
 | 
			
		||||
		theme: state.theme,
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = dispatch => {
 | 
			
		||||
	return {
 | 
			
		||||
		setTheme: theme => dispatch(setTheme(theme))
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
 | 
			
		||||
 | 
			
		||||
export default AppContainer;
 | 
			
		||||
@@ -13,3 +13,10 @@ export function removeSubject(id) {
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function setTheme(theme) {
 | 
			
		||||
	return {
 | 
			
		||||
		type: "SET_THEME",
 | 
			
		||||
		theme: theme,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,8 @@ Subjects.propTypes = {
 | 
			
		||||
const style = StyleSheet.create({
 | 
			
		||||
	card: {
 | 
			
		||||
		marginTop: 12,
 | 
			
		||||
		marginLeft: 10,
 | 
			
		||||
		marginRight: 10,
 | 
			
		||||
	},
 | 
			
		||||
	text: {
 | 
			
		||||
		marginTop: 12,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								index.js
									
									
									
									
									
								
							@@ -1,40 +1,18 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import {AppRegistry} from "react-native";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
import AppContainer from "./AppContainer";
 | 
			
		||||
import {name as appName} from "./app.json";
 | 
			
		||||
import { DarkTheme, Provider } from "react-native-paper";
 | 
			
		||||
import { Provider as ReduxProvider } from "react-redux";
 | 
			
		||||
import { PersistGate } from "redux-persist/integration/react";
 | 
			
		||||
 | 
			
		||||
import configureStore from "./configureStore";
 | 
			
		||||
const { store, persistor } = configureStore();
 | 
			
		||||
 | 
			
		||||
const theme = {
 | 
			
		||||
	...DarkTheme,
 | 
			
		||||
	mode: "exact",
 | 
			
		||||
	colors: {
 | 
			
		||||
		primary: "#ededed",
 | 
			
		||||
		accent: "#1a237e",
 | 
			
		||||
		backdrop: "rgba(0, 0, 0, 0.5)",
 | 
			
		||||
		background: "#000000",
 | 
			
		||||
		disabled: "rgba(255, 255, 255, 0.38)",
 | 
			
		||||
		error: "#CF6679",
 | 
			
		||||
		notification: "#ff80ab",
 | 
			
		||||
		onBackground: "#FFFFFF",
 | 
			
		||||
		onSurface: "#FFFFFF",
 | 
			
		||||
		placeholder: "rgba(255, 255, 255, 0.54)",
 | 
			
		||||
		surface: "#121212",
 | 
			
		||||
		text: "#ffffff"
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function Main() {
 | 
			
		||||
	return (
 | 
			
		||||
		<ReduxProvider store={store}>
 | 
			
		||||
			<PersistGate persistor={persistor}>
 | 
			
		||||
				<Provider theme={theme}>
 | 
			
		||||
					<App />
 | 
			
		||||
				</Provider>
 | 
			
		||||
				<AppContainer />
 | 
			
		||||
			</PersistGate>
 | 
			
		||||
		</ReduxProvider>
 | 
			
		||||
	);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,13 @@ import { combineReducers } from "redux";
 | 
			
		||||
import timetable from "./timetable";
 | 
			
		||||
import classes from "./classes";
 | 
			
		||||
import subjects from "./subjects";
 | 
			
		||||
import theme from "./theme";
 | 
			
		||||
 | 
			
		||||
const rootReducer = combineReducers({
 | 
			
		||||
	timetable,
 | 
			
		||||
	classes,
 | 
			
		||||
	subjects,
 | 
			
		||||
	theme,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default rootReducer;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								reducers/theme.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								reducers/theme.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
import themes from "../themes";
 | 
			
		||||
export default function theme(state, action) {
 | 
			
		||||
	if (typeof state === "undefined")
 | 
			
		||||
		return themes[0].theme;
 | 
			
		||||
	if (action.type === "SET_THEME") {
 | 
			
		||||
		return action.theme;
 | 
			
		||||
	}
 | 
			
		||||
	return state;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								themes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								themes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
import { DefaultTheme, DarkTheme } from "react-native-paper";
 | 
			
		||||
export default [
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Dark: Pink and Gray",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DarkTheme,
 | 
			
		||||
			mode: "exact",
 | 
			
		||||
			colors: {
 | 
			
		||||
				primary: "#ff1744",
 | 
			
		||||
				accent: "#e0e0e0",
 | 
			
		||||
				backdrop: "rgba(0, 0, 0, 0.5)",
 | 
			
		||||
				background: "#000000",
 | 
			
		||||
				disabled: "rgba(255, 255, 255, 0.38)",
 | 
			
		||||
				error: "#CF6679",
 | 
			
		||||
				notification: "#ff80ab",
 | 
			
		||||
				onBackground: "#FFFFFF",
 | 
			
		||||
				onSurface: "#FFFFFF",
 | 
			
		||||
				placeholder: "rgba(255, 255, 255, 0.54)",
 | 
			
		||||
				surface: "#121212",
 | 
			
		||||
				text: "#ffffff"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Dark: Pastel Colors",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DarkTheme,
 | 
			
		||||
			mode: "exact",
 | 
			
		||||
			colors: {
 | 
			
		||||
				primary: "#ef9a9a",
 | 
			
		||||
				accent: "#fce4ec",
 | 
			
		||||
				backdrop: "rgba(0, 0, 0, 0.5)",
 | 
			
		||||
				background: "#000000",
 | 
			
		||||
				disabled: "rgba(255, 255, 255, 0.38)",
 | 
			
		||||
				error: "#CF6679",
 | 
			
		||||
				notification: "#ff80ab",
 | 
			
		||||
				onBackground: "#FFFFFF",
 | 
			
		||||
				onSurface: "#FFFFFF",
 | 
			
		||||
				placeholder: "rgba(255, 255, 255, 0.54)",
 | 
			
		||||
				surface: "#121212",
 | 
			
		||||
				text: "#ffffff"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Dark: Pastel Colors 2",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DarkTheme,
 | 
			
		||||
			mode: "exact",
 | 
			
		||||
			colors: {
 | 
			
		||||
				primary: "#e0f7fa",
 | 
			
		||||
				accent: "#ffebee",
 | 
			
		||||
				backdrop: "rgba(0, 0, 0, 0.5)",
 | 
			
		||||
				background: "#000000",
 | 
			
		||||
				disabled: "rgba(255, 255, 255, 0.38)",
 | 
			
		||||
				error: "#CF6679",
 | 
			
		||||
				notification: "#ff80ab",
 | 
			
		||||
				onBackground: "#FFFFFF",
 | 
			
		||||
				onSurface: "#FFFFFF",
 | 
			
		||||
				placeholder: "rgba(255, 255, 255, 0.54)",
 | 
			
		||||
				surface: "#121212",
 | 
			
		||||
				text: "#ffffff"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Absolute Dark: Pink and Gray",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DarkTheme,
 | 
			
		||||
			colors: {
 | 
			
		||||
				...DarkTheme.colors,
 | 
			
		||||
				primary: "#ff1744",
 | 
			
		||||
				accent: "#e0e0e0",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Absolute Dark: Purple and Gray",
 | 
			
		||||
		"theme": DarkTheme
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Light: Pink and Gray",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DefaultTheme,
 | 
			
		||||
			colors: {
 | 
			
		||||
				...DefaultTheme.colors,
 | 
			
		||||
				primary: "#ff1744",
 | 
			
		||||
				accent: "#e0e0e0",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Light: Pastel Colors",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DefaultTheme,
 | 
			
		||||
			mode: "exact",
 | 
			
		||||
			colors: {
 | 
			
		||||
				...DefaultTheme.colors,
 | 
			
		||||
				primary: "#ef9a9a",
 | 
			
		||||
				accent: "#fce4ec",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Light: Pastel Colors 2",
 | 
			
		||||
		"theme": {
 | 
			
		||||
			...DefaultTheme,
 | 
			
		||||
			colors: {
 | 
			
		||||
				...DefaultTheme.colors,
 | 
			
		||||
				primary: "#ffcccb",
 | 
			
		||||
				accent: "#fce4ec",
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"name": "Light: Purple and Green",
 | 
			
		||||
		"theme": DefaultTheme
 | 
			
		||||
	},
 | 
			
		||||
];
 | 
			
		||||
		Reference in New Issue
	
	Block a user