Verified Commit 1ebb16b8 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Move PushNotications screen into Settings as CardSection

parent 2d7f4f66
......@@ -427,9 +427,7 @@ exports[`Sidebar component renders correctly 1`] = `
}
>
<Text
accessible={true}
allowFontScaling={false}
ellipsizeMode="tail"
onPress={[Function]}
style={
Array [
......@@ -458,9 +456,6 @@ exports[`Sidebar component renders correctly 1`] = `
</Text>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Array [
Object {
......@@ -478,15 +473,8 @@ exports[`Sidebar component renders correctly 1`] = `
</View>
</View>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
hasTVPreferredFocus={undefined}
hitSlop={undefined}
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
......@@ -504,8 +492,6 @@ exports[`Sidebar component renders correctly 1`] = `
},
]
}
testID={undefined}
tvParallaxProperties={undefined}
>
<View
style={
......
// Group setting sections together to prepare for more submenus to be added.
export const pushNotificationsSettingsActions = {
RETRIEVE: 'SETTINGS_PUSH_NOTIFICATIONS',
LOADING: 'SETTINGS_PUSH_NOTIFICATIONS_LOADING',
// Actions are grouped based on the settings sections
export const settingsActions = {
INIT_START: 'SETTINGS_INIT_START',
INIT_COMPLETE: 'SETTINGS_INIT_COMPLETE',
initStart: () => ({
type: settingsActions.INIT_START,
}),
initComplete: () => ({
type: settingsActions.INIT_COMPLETE,
}),
};
export const notificationsSettingsActions = {
SUCCESS: 'SETTINGS_PUSH_NOTIFICATIONS_SUCCESS',
FAILURE: 'SETTINGS_PUSH_NOTIFICATIONS_FAILURE',
SAVE_CATEGORIES: 'SETTINGS_PUSH_NOTIFICATIONS_SAVE_CATEGORIES',
retrieve: () => ({
type: pushNotificationsSettingsActions.RETRIEVE,
}),
loading: () => ({
type: pushNotificationsSettingsActions.LOADING,
}),
success: categoryList => ({
type: pushNotificationsSettingsActions.SUCCESS,
type: notificationsSettingsActions.SUCCESS,
categoryList,
}),
failure: () => ({
type: pushNotificationsSettingsActions.FAILURE,
type: notificationsSettingsActions.FAILURE,
}),
saveCategories: categories => ({
type: pushNotificationsSettingsActions.SAVE_CATEGORIES,
type: notificationsSettingsActions.SAVE_CATEGORIES,
categories,
}),
};
const files = {};
files['app/ui/screens/settings/NotificationsSectionNL'] = require('./nl/app/ui/screens/settings/NotificationsSection.json');
files['app/ui/screens/user/ProfileNL'] = require('./nl/app/ui/screens/user/Profile.json');
files['app/ui/screens/user/LoginNL'] = require('./nl/app/ui/screens/user/Login.json');
files['app/ui/screens/memberList/MemberListNL'] = require('./nl/app/ui/screens/memberList/MemberList.json');
files['app/ui/screens/welcome/EventDetailCardNL'] = require('./nl/app/ui/screens/welcome/EventDetailCard.json');
files['app/ui/screens/welcome/WelcomeNL'] = require('./nl/app/ui/screens/welcome/Welcome.json');
files['app/ui/screens/pizza/PizzaNL'] = require('./nl/app/ui/screens/pizza/Pizza.json');
files['app/ui/screens/settings/SettingsNL'] = require('./nl/app/ui/screens/settings/Settings.json');
files['app/ui/screens/settings/PushNotificationsNL'] = require('./nl/app/ui/screens/settings/PushNotifications.json');
files['app/ui/screens/events/CalendarNL'] = require('./nl/app/ui/screens/events/Calendar.json');
files['app/ui/screens/events/RegistrationNL'] = require('./nl/app/ui/screens/events/Registration.json');
files['app/ui/screens/welcome/EventDetailCardNL'] = require('./nl/app/ui/screens/welcome/EventDetailCard.json');
files['app/ui/screens/memberList/MemberListNL'] = require('./nl/app/ui/screens/memberList/MemberList.json');
files['app/ui/screens/events/CalendarItemNL'] = require('./nl/app/ui/screens/events/CalendarItem.json');
files['app/ui/screens/events/EventNL'] = require('./nl/app/ui/screens/events/Event.json');
files['app/ui/screens/events/RegistrationNL'] = require('./nl/app/ui/screens/events/Registration.json');
files['app/ui/screens/events/CalendarNL'] = require('./nl/app/ui/screens/events/Calendar.json');
files['app/ui/screens/pizza/PizzaNL'] = require('./nl/app/ui/screens/pizza/Pizza.json');
files['app/ui/components/standardHeader/StandardHeaderNL'] = require('./nl/app/ui/components/standardHeader/StandardHeader.json');
files['app/ui/components/errorScreen/ErrorScreenNL'] = require('./nl/app/ui/components/errorScreen/ErrorScreen.json');
files['app/ui/components/navigator/SidebarNL'] = require('./nl/app/ui/components/navigator/Sidebar.json');
files['app/ui/components/errorScreen/ErrorScreenNL'] = require('./nl/app/ui/components/errorScreen/ErrorScreen.json');
export default {
nl: {
'screens/settings/NotificationsSection': files['app/ui/screens/settings/NotificationsSectionNL'],
'screens/user/Profile': files['app/ui/screens/user/ProfileNL'],
'screens/user/Login': files['app/ui/screens/user/LoginNL'],
'screens/memberList/MemberList': files['app/ui/screens/memberList/MemberListNL'],
'screens/welcome/EventDetailCard': files['app/ui/screens/welcome/EventDetailCardNL'],
'screens/welcome/Welcome': files['app/ui/screens/welcome/WelcomeNL'],
'screens/pizza/Pizza': files['app/ui/screens/pizza/PizzaNL'],
'screens/settings/Settings': files['app/ui/screens/settings/SettingsNL'],
'screens/settings/PushNotifications': files['app/ui/screens/settings/PushNotificationsNL'],
'screens/events/Calendar': files['app/ui/screens/events/CalendarNL'],
'screens/events/Registration': files['app/ui/screens/events/RegistrationNL'],
'screens/welcome/EventDetailCard': files['app/ui/screens/welcome/EventDetailCardNL'],
'screens/memberList/MemberList': files['app/ui/screens/memberList/MemberListNL'],
'screens/events/CalendarItem': files['app/ui/screens/events/CalendarItemNL'],
'screens/events/Event': files['app/ui/screens/events/EventNL'],
'screens/events/Registration': files['app/ui/screens/events/RegistrationNL'],
'screens/events/Calendar': files['app/ui/screens/events/CalendarNL'],
'screens/pizza/Pizza': files['app/ui/screens/pizza/PizzaNL'],
'components/standardHeader/StandardHeader': files['app/ui/components/standardHeader/StandardHeaderNL'],
'components/errorScreen/ErrorScreen': files['app/ui/components/errorScreen/ErrorScreenNL'],
'components/navigator/Sidebar': files['app/ui/components/navigator/SidebarNL'],
'components/errorScreen/ErrorScreen': files['app/ui/components/errorScreen/ErrorScreenNL'],
},
};
{
"Notifications settings could not be loaded.": "De instellingen voor notificaties konden niet worden geladen.",
"(required)": "(verplicht)",
"Notifications": "Notificaties"
}
{
"Sorry, we couldn't load any data.": "Sorry, we konden geen gegevens laden.",
"(required)": "(verplicht)"
}
import { pushNotificationsSettingsActions } from '../actions/settings';
import pushNotifications, { initialState as initialPushNotificationsState } from './settings/pushNotifications';
import { settingsActions } from '../actions/settings';
const initialState = {
pushNotifications: {
categoryList: [],
status: 'loading',
},
pushNotifications: initialPushNotificationsState,
loading: true,
};
export default function settings(state = initialState, action = {}) {
export default function calendar(state = initialState, action = {}) {
switch (action.type) {
case pushNotificationsSettingsActions.LOADING: {
case settingsActions.INIT_START:
return {
...state,
pushNotifications: {
...state.pushNotifications,
status: 'loading',
},
loading: true,
};
}
case pushNotificationsSettingsActions.SUCCESS: {
case settingsActions.INIT_COMPLETE:
return {
...state,
pushNotifications: {
...state.pushNotifications,
categoryList: action.categoryList,
status: 'success',
},
loading: false,
};
}
case pushNotificationsSettingsActions.FAILURE: {
default:
return {
...state,
pushNotifications: {
...state.pushNotifications,
status: 'failure',
},
pushNotifications: pushNotifications(state.pushNotifications, action),
};
}
default: {
return state;
}
}
}
import { notificationsSettingsActions as actions } from '../../actions/settings';
export const initialState = {
categoryList: [],
status: 'loading',
};
export default function pushNotifications(state = initialState, action = {}) {
switch (action.type) {
case actions.SUCCESS:
return {
categoryList: action.categoryList,
status: 'success',
};
case actions.FAILURE:
return {
...state,
status: 'failure',
};
default:
return state;
}
}
import { AsyncStorage } from 'react-native';
import { takeEvery, select, call, put } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import {
all, call, put, select, takeEvery,
} from 'redux-saga/effects';
import { pushNotificationsSettingsActions } from '../actions/settings';
import * as navigationActions from '../actions/navigation';
import { notificationsSettingsActions, settingsActions } from '../actions/settings';
import { apiRequest, tokenSelector } from '../utils/url';
import * as pushNotifactionsActions from '../actions/pushNotifications';
const PUSHCATEGORYKEY = '@MyStore:pushCategories';
......@@ -20,9 +23,6 @@ function* pushNotifications() {
},
};
yield put(pushNotificationsSettingsActions.loading());
yield put(navigationActions.navigate('pushNotificationsSettings'));
try {
const categoryList = yield call(apiRequest, 'devices/categories', data);
const preferencesJson = yield call(AsyncStorage.getItem, PUSHCATEGORYKEY);
......@@ -38,9 +38,10 @@ function* pushNotifications() {
}
}
yield put(pushNotificationsSettingsActions.success(categoryList));
yield put(notificationsSettingsActions.success(categoryList));
} catch (error) {
yield put(pushNotificationsSettingsActions.failure());
Sentry.captureException(error);
yield put(notificationsSettingsActions.failure());
}
}
......@@ -49,14 +50,22 @@ function* saveCategories(action) {
try {
yield call(AsyncStorage.setItem, PUSHCATEGORYKEY, JSON.stringify(categories));
yield put(pushNotifactionsActions.register(categories));
} catch (error) {
// Swallow error
Sentry.captureException(error);
}
}
function* init() {
yield all([
pushNotifications(),
]);
yield put(settingsActions.initComplete());
}
function* settingsSaga() {
yield takeEvery(pushNotificationsSettingsActions.RETRIEVE, pushNotifications);
yield takeEvery(pushNotificationsSettingsActions.SAVE_CATEGORIES, saveCategories);
yield takeEvery(settingsActions.INIT_START, init);
yield takeEvery(notificationsSettingsActions.SAVE_CATEGORIES, saveCategories);
}
export default settingsSaga;
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
import {
StyleSheet, Text, View, ViewPropTypes,
} from 'react-native';
import styles from './style/CardSection';
const CardSection = props => (
<View style={[styles.section, props.style]}>
<Text style={styles.sectionHeader}>{props.sectionHeader}</Text>
<Text style={styles.sectionHeader}>
{props.sectionHeader}
</Text>
<View style={[styles.card, props.contentStyle]}>
{props.children}
</View>
......@@ -28,4 +32,4 @@ CardSection.defaultProps = {
contentStyle: defaultStyles,
};
export default CardSection;
\ No newline at end of file
export default CardSection;
......@@ -35,4 +35,4 @@ const styles = StyleSheet.create({
},
});
export default styles;
\ No newline at end of file
export default styles;
......@@ -16,7 +16,6 @@ import StandardHeader from '../standardHeader/StandardHeader';
import Registration from '../../screens/events/Registration';
import MemberList from '../../screens/memberList/MemberList';
import Settings from '../../screens/settings/Settings';
import PushNotifications from '../../screens/settings/PushNotifications';
import * as actions from '../../../actions/navigation';
import styles from './style/ReduxNavigator';
......@@ -40,8 +39,6 @@ const sceneToComponent = (scene) => {
return <MemberList />;
case 'settings':
return <Settings />;
case 'pushNotificationsSettings':
return <PushNotifications />;
default:
return <Welcome />;
}
......
import React, { Component } from 'react';
import { View, Text, Switch } from 'react-native';
import { Switch, Text, View } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import styles from './style/NotificationsSection';
import Colors from '../../style/Colors';
import LoadingScreen from '../../components/loadingScreen/LoadingScreen';
import ErrorScreen from '../../components/errorScreen/ErrorScreen';
import { notificationsSettingsActions } from '../../../actions/settings';
import CardSection from '../../components/cardSection/CardSection';
import styles from './style/PushNotifications';
import Colors from '../../style/Colors';
const GENERAL_KEY = 'general';
import { pushNotificationsSettingsActions } from '../../../actions/settings';
import * as pushNotificationsActions from '../../../actions/pushNotifications';
class NotificationsSection extends Component {
constructor(props) {
super(props);
this.state = {};
}
class PushNotifications extends Component {
static getDerivedStateFromProps = (props) => {
if (props.status !== 'success') {
return null;
......@@ -22,63 +25,72 @@ class PushNotifications extends Component {
const newState = {};
for (let i = 0; i < props.categoryList.length; i += 1) {
newState[props.categoryList[i].key] = props.categoryList[i].enabled;
if (props.categoryList[i].key === GENERAL_KEY) {
newState[props.categoryList[i].key] = true;
} else {
newState[props.categoryList[i].key] = props.categoryList[i].enabled;
}
}
return newState;
};
constructor(props) {
super(props);
this.state = {};
}
updateField = (key, value) => {
const update = {};
update[key] = value;
this.setState(update, () => {
const categories = Object.keys(this.state).filter(k => this.state[k]);
this.props.register(categories);
this.props.saveCategories(categories);
});
};
render() {
if (this.props.status === 'loading') {
return <LoadingScreen />;
} else if (this.props.status === 'failure') {
return <ErrorScreen message={this.props.t('Sorry, we couldn\'t load any data.')} />;
const { status, categoryList, t } = this.props;
let content = (
<Text style={styles.emptyText}>
{t('Notifications settings could not be loaded.')}
</Text>
);
if (status === 'success') {
content = categoryList.map((category, i) => (
<View
style={[styles.categoryContainer, i !== 0 && styles.borderTop]}
key={category.key}
>
<Text
style={styles.label}
>
{category.name}
{' '}
{category.key === GENERAL_KEY && this.props.t('(required)')}
</Text>
<Switch
value={this.state[category.key]}
onValueChange={value => this.updateField(category.key, value)}
onTintColor={Colors.magenta}
thumbTintColor={this.state[category.key]
? Colors.darkMagenta : Colors.gray}
disabled={category.key === GENERAL_KEY}
/>
</View>
));
}
return (
<View style={styles.container}>
{this.props.categoryList.map(category => (
<View style={styles.setting} key={category.key}>
<Text
style={styles.label}
key={category.key}
>{category.name} {category.key === 'general' && this.props.t('(required)')}</Text>
<Switch
value={this.state[category.key]}
onValueChange={value => this.updateField(category.key, value)}
thumbTintColor={this.state[category.key] ? Colors.darkMagenta : Colors.lightGray}
onTintColor={Colors.magenta}
disabled={category.key === 'general'}
/>
</View>
))}
</View>
<CardSection sectionHeader={t('Notifications')}>
{content}
</CardSection>
);
}
}
PushNotifications.propTypes = {
NotificationsSection.propTypes = {
categoryList: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
enabled: PropTypes.bool.isRequired,
})).isRequired,
status: PropTypes.string.isRequired,
register: PropTypes.func.isRequired,
saveCategories: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
......@@ -89,8 +101,7 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
register: categoryList => dispatch(pushNotificationsActions.register(categoryList)),
saveCategories: catList => dispatch(pushNotificationsSettingsActions.saveCategories(catList)),
saveCategories: catList => dispatch(notificationsSettingsActions.saveCategories(catList)),
});
export default connect(mapStateToProps, mapDispatchToProps)(translate('screens/settings/PushNotifications')(PushNotifications));
export default connect(mapStateToProps, mapDispatchToProps)(translate('screens/settings/PushNotifications')(NotificationsSection));
import React from 'react';
import { View, TouchableHighlight, Text } from 'react-native';
import { ScrollView, View } from 'react-native';
import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import PropTypes from 'prop-types';
import Colors from '../../style/Colors';
import styles from './style/Settings';
import { pushNotificationsSettingsActions } from '../../../actions/settings';
import { settingsActions } from '../../../actions/settings';
import LoadingScreen from '../../components/loadingScreen/LoadingScreen';
import NotificationsSection from './NotificationsSection';
class Settings extends React.Component {
componentDidMount() {
this.props.init();
}
render() {
const { loading } = this.props;
const Settings = props => (
<View style={styles.container}>
<TouchableHighlight
onPress={props.pushNotifications}
underlayColor={Colors.pressedWhite}
>
<Text style={styles.menuItem}>{props.t('Notifications')}</Text>
</TouchableHighlight>
</View>
);
if (loading) {
return <LoadingScreen />;
}
return (
<View style={styles.container}>
<ScrollView
style={styles.container}
>
<View style={styles.content}>
<NotificationsSection />
</View>
</ScrollView>
</View>
);
}
}
Settings.propTypes = {
t: PropTypes.func.isRequired,
pushNotifications: PropTypes.func.isRequired,
init: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
loading: state.settings.loading,
});
const mapDispatchToProps = dispatch => ({
pushNotifications: () => dispatch(pushNotificationsSettingsActions.retrieve()),
init: () => dispatch(settingsActions.initStart()),
});
export default connect(() => ({}), mapDispatchToProps)(translate('screens/settings/Settings')(Settings));
export default connect(mapStateToProps, mapDispatchToProps)(Settings);
......@@ -6,13 +6,15 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: Colors.background,
},
setting: {
categoryContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 16,
alignItems: 'center',
borderBottomColor: Colors.lightGray,
borderBottomWidth: 1,
padding: 16,
},
borderTop: {
borderTopColor: Colors.dividerGrey,
borderTopWidth: 1,
},
label: {
color: Colors.textColour,
......@@ -24,8 +26,8 @@ const styles = StyleSheet.create({
fontWeight: '600',
},
},