Merge pull request #688 from esteemapp/feature/custom-push

Feature/custom push
This commit is contained in:
uğur erdal 2019-03-27 00:39:05 +03:00 committed by GitHub
commit 3c0e75500c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 499 additions and 157 deletions

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>EnabledFullIndexStoreVisibility</key>
<false/>
<key>IssueFilterStyle</key>
<string>ShowActiveSchemeOnly</string>
<key>LiveSourceIssuesEnabled</key>
<true/>
</dict>
</plist>

View File

@ -34,11 +34,11 @@ class CollapsibleCardView extends PureComponent {
}
componentWillReceiveProps(nextProps) {
const { isExpanded, moreHeight } = this.props;
const { isExpanded, moreHeight, locked } = this.props;
const { expanded } = this.state;
if (
!nextProps.isExpanded
(locked || !nextProps.isExpanded)
&& isExpanded !== nextProps.isExpanded
&& expanded !== nextProps.isExpanded
) {

View File

@ -85,11 +85,11 @@ class SettingsItemView extends PureComponent {
};
render() {
const { title } = this.props;
const { title, titleStyle } = this.props;
return (
<View style={styles.wrapper}>
<Text style={styles.text}>{title}</Text>
<Text style={[styles.text, titleStyle]}>{title}</Text>
{this._renderItem()}
</View>
);

View File

@ -58,11 +58,20 @@
},
"settings": {
"settings": "Settings",
"general": "General",
"currency": "Currency",
"language": "Language",
"server": "Server",
"dark_theme": "Dark Theme",
"push_notification": "Push Notification",
"notification": {
"follow": "Follow",
"vote": "Vote",
"comment": "Comment",
"mention": "Mention",
"reblog": "Reblog",
"transfers": "Transfers"
},
"pincode": "PIN code",
"reset": "Reset",
"nsfw_content": "NSFW Content",

View File

@ -54,10 +54,6 @@ export default EStyleSheet.create({
backgroundColor: '$primaryLightBackground',
flex: 1,
},
settingsContainer: {
marginLeft: 42,
marginRight: 32,
},
hintText: {
color: '$iconColor',
alignSelf: 'center',

View File

@ -53,6 +53,12 @@ const settingsSchema = {
nsfw: { type: 'string', default: null },
server: { type: 'string', default: null },
upvotePercent: { type: 'string', default: null },
followNotification: { type: 'bool', default: true },
voteNotification: { type: 'bool', default: true },
commentNotification: { type: 'bool', default: true },
mentionNotification: { type: 'bool', default: true },
reblogNotification: { type: 'bool', default: true },
transfersNotification: { type: 'bool', default: true },
},
};
@ -105,6 +111,12 @@ if (Array.from(settings).length <= 0) {
server: '',
upvotePercent: '1',
nsfw: '0',
followNotification: true,
voteNotification: true,
commentNotification: true,
mentionNotification: true,
reblogNotification: true,
transfersNotification: true,
});
});
}
@ -450,10 +462,34 @@ export const setServer = selectedServer => new Promise((resolve, reject) => {
}
});
export const setNotificationIsOpen = notificationIsOpen => new Promise((resolve, reject) => {
export const setNotificationSettings = ({ type, action }) => new Promise((resolve, reject) => {
try {
realm.write(() => {
settings[0].notification = notificationIsOpen;
switch (type) {
case 'notification.follow':
settings[0].followNotification = action;
break;
case 'notification.vote':
settings[0].voteNotification = action;
break;
case 'notification.comment':
settings[0].commentNotification = action;
break;
case 'notification.mention':
settings[0].mentionNotification = action;
break;
case 'notification.reblog':
settings[0].reblogNotification = action;
break;
case 'notification.transfers':
settings[0].transfersNotification = action;
break;
case 'notification':
settings[0].notification = action;
break;
default:
break;
}
resolve(true);
});
} catch (error) {

View File

@ -2,6 +2,13 @@ import getSymbolFromCurrency from 'currency-symbol-map';
import { getCurrencyRate } from '../../providers/esteem/esteem';
import {
ACTIVE_APPLICATION,
CHANGE_COMMENT_NOTIFICATION,
CHANGE_FOLLOW_NOTIFICATION,
CHANGE_MENTION_NOTIFICATION,
CHANGE_REBLOG_NOTIFICATION,
CHANGE_TRANSFERS_NOTIFICATION,
CHANGE_ALL_NOTIFICATION_SETTINGS,
CHANGE_VOTE_NOTIFICATION,
CLOSE_PIN_CODE_MODAL,
IS_CONNECTED,
IS_DARK_THEME,
@ -64,11 +71,60 @@ export const setUpvotePercent = payload => ({
type: SET_UPVOTE_PERCENT,
});
export const isNotificationOpen = payload => ({
export const changeAllNotificationSettings = payload => ({
payload,
type: IS_NOTIFICATION_OPEN,
type: CHANGE_ALL_NOTIFICATION_SETTINGS,
});
export const changeNotificationSettings = (payload) => {
switch (payload.type) {
case 'notification.follow':
return {
payload: payload.action,
type: CHANGE_FOLLOW_NOTIFICATION,
};
case 'notification.vote':
return {
payload: payload.action,
type: CHANGE_VOTE_NOTIFICATION,
};
case 'notification.comment':
return {
payload: payload.action,
type: CHANGE_COMMENT_NOTIFICATION,
};
case 'notification.mention':
return {
payload: payload.action,
type: CHANGE_MENTION_NOTIFICATION,
};
case 'notification.reblog':
return {
payload: payload.action,
type: CHANGE_REBLOG_NOTIFICATION,
};
case 'notification.transfers':
return {
payload: payload.action,
type: CHANGE_TRANSFERS_NOTIFICATION,
};
case 'notification':
return {
payload: payload.action,
type: IS_NOTIFICATION_OPEN,
};
default:
break;
}
};
export const isDarkTheme = payload => ({
payload,
type: IS_DARK_THEME,

View File

@ -23,6 +23,13 @@ export const SET_CURRENCY = 'SET_CURRENCY';
export const SET_LANGUAGE = 'SET_LANGUAGE';
export const SET_UPVOTE_PERCENT = 'SET_UPVOTE_PERCENT';
export const SET_NSFW = 'SET_NSFW';
export const CHANGE_FOLLOW_NOTIFICATION = 'CHANGE_FOLLOW_NOTIFICATION';
export const CHANGE_VOTE_NOTIFICATION = 'CHANGE_VOTE_NOTIFICATION';
export const CHANGE_COMMENT_NOTIFICATION = 'CHANGE_COMMENT_NOTIFICATION';
export const CHANGE_MENTION_NOTIFICATION = 'CHANGE_MENTION_NOTIFICATION';
export const CHANGE_REBLOG_NOTIFICATION = 'CHANGE_REBLOG_NOTIFICATION';
export const CHANGE_TRANSFERS_NOTIFICATION = 'CHANGE_TRANSFERS_NOTIFICATION';
export const CHANGE_ALL_NOTIFICATION_SETTINGS = 'CHANGE_ALL_NOTIFICATION_SETTINGS';
// Accounts
export const ADD_OTHER_ACCOUNT = 'ADD_OTHER_ACCOUNT';

View File

@ -1,5 +1,12 @@
import {
ACTIVE_APPLICATION,
CHANGE_COMMENT_NOTIFICATION,
CHANGE_FOLLOW_NOTIFICATION,
CHANGE_MENTION_NOTIFICATION,
CHANGE_REBLOG_NOTIFICATION,
CHANGE_TRANSFERS_NOTIFICATION,
CHANGE_VOTE_NOTIFICATION,
CHANGE_ALL_NOTIFICATION_SETTINGS,
CLOSE_PIN_CODE_MODAL,
IS_CONNECTED,
IS_DARK_THEME,
@ -35,6 +42,14 @@ const initialState = {
isPinCodeReqiure: false,
language: 'en-US',
loading: false, // It is lock to all screen and shows loading animation.
notificationDetails: {
commentNotification: true,
followNotification: true,
mentionNotification: true,
reblogNotification: true,
transfersNotification: true,
voteNotification: true,
},
upvotePercent: 1,
nsfw: 'Always show',
};
@ -97,6 +112,61 @@ export default function (state = initialState, action) {
return Object.assign({}, state, {
isNotificationOpen: action.payload,
});
case CHANGE_COMMENT_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
commentNotification: action.payload,
},
});
case CHANGE_FOLLOW_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
followNotification: action.payload,
},
});
case CHANGE_MENTION_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
mentionNotification: action.payload,
},
});
case CHANGE_REBLOG_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
reblogNotification: action.payload,
},
});
case CHANGE_TRANSFERS_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
transfersNotification: action.payload,
},
});
case CHANGE_VOTE_NOTIFICATION:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
voteNotification: action.payload,
},
});
case CHANGE_ALL_NOTIFICATION_SETTINGS:
return Object.assign({}, state, {
notificationDetails: {
...state.notificationDetails,
mentionNotification: action.payload.mentionNotification,
reblogNotification: action.payload.reblogNotification,
transfersNotification: action.payload.transfersNotification,
voteNotification: action.payload.voteNotification,
followNotification: action.payload.followNotification,
commentNotification: action.payload.commentNotification,
},
});
case IS_DARK_THEME:
return Object.assign({}, state, {
isDarkTheme: action.payload,

View File

@ -5,7 +5,6 @@ import {
import { connect } from 'react-redux';
import { addLocaleData } from 'react-intl';
import Config from 'react-native-config';
import AppCenter from 'appcenter';
import { NavigationActions } from 'react-navigation';
import { bindActionCreators } from 'redux';
import Push from 'appcenter-push';
@ -29,11 +28,9 @@ import AUTH_TYPE from '../../../constants/authType';
import {
getAuthStatus,
getExistUser,
getPushTokenSaved,
getSettings,
getUserData,
removeUserData,
setPushTokenSaved,
getUserDataWithUsername,
removePinCode,
setAuthStatus,
@ -42,7 +39,6 @@ import {
setDefaultFooter,
} from '../../../realm/realm';
import { getUser } from '../../../providers/steem/dsteem';
import { setPushToken } from '../../../providers/esteem/esteem';
import { switchAccount } from '../../../providers/steem/auth';
// Actions
@ -57,7 +53,8 @@ import {
activeApplication,
isDarkTheme,
isLoginDone,
isNotificationOpen,
changeNotificationSettings,
changeAllNotificationSettings,
login,
logoutDone,
openPinCodeModal,
@ -229,7 +226,6 @@ class ApplicationContainer extends Component {
dispatch(openPinCodeModal());
}
this._connectNotificationServer(accountData.name);
this._setPushToken(accountData.name);
})
.catch((err) => {
Alert.alert(err);
@ -251,7 +247,9 @@ class ApplicationContainer extends Component {
if (response.upvotePercent !== '') dispatch(setUpvotePercent(Number(response.upvotePercent)));
if (response.isDefaultFooter !== '') dispatch(isDefaultFooter(response.isDefaultFooter));
if (response.notification !== '') {
dispatch(isNotificationOpen(response.notification));
dispatch(changeNotificationSettings({ type: 'notification', action: response.notification }));
dispatch(changeAllNotificationSettings(response));
Push.setEnabled(response.notification);
}
if (response.nsfw !== '') dispatch(setNsfw(response.nsfw));
@ -273,29 +271,6 @@ class ApplicationContainer extends Component {
};
};
_setPushToken = async (username) => {
const { notificationSettings } = this.props;
const token = await AppCenter.getInstallId();
getExistUser().then((isExistUser) => {
if (isExistUser) {
getPushTokenSaved().then((isPushTokenSaved) => {
if (!isPushTokenSaved) {
const data = {
username,
token,
system: Platform.OS,
allows_notify: Number(notificationSettings),
};
setPushToken(data).then(() => {
setPushTokenSaved(true);
});
}
});
}
});
};
_logout = async () => {
const { otherAccounts, currentAccount, dispatch } = this.props;

View File

@ -1,7 +1,8 @@
import React, { PureComponent } from 'react';
import { Alert, Linking } from 'react-native';
import { Alert, Linking, Platform } from 'react-native';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import AppCenter from 'appcenter';
// Services and Actions
import { login } from '../../../providers/steem/auth';
@ -13,6 +14,8 @@ import {
updateCurrentAccount,
} from '../../../redux/actions/accountAction';
import { login as loginAction, openPinCodeModal } from '../../../redux/actions/applicationActions';
import { setPushTokenSaved } from '../../../realm/realm';
import { setPushToken } from '../../../providers/esteem/esteem';
// Middleware
@ -57,6 +60,7 @@ class LoginContainer extends PureComponent {
setPinCodeState({ navigateTo: ROUTES.DRAWER.MAIN });
dispatch(loginAction(true));
userActivity(result.name, 20);
this._setPushToken(result.name);
}
})
.catch((err) => {
@ -71,6 +75,39 @@ class LoginContainer extends PureComponent {
});
};
_setPushToken = async (username) => {
const { notificationSettings, notificationDetails } = this.props;
const notifyTypesConst = {
vote: 1,
mention: 2,
follow: 3,
comment: 4,
reblog: 5,
transfers: 6,
};
const notifyTypes = [];
const token = await AppCenter.getInstallId();
Object.keys(notificationDetails).map((item) => {
const notificationType = item.replace('Notification', '');
if (notificationDetails[item]) {
notifyTypes.push(notifyTypesConst[notificationType]);
}
});
const data = {
username,
token,
system: Platform.OS,
allows_notify: Number(notificationSettings),
notify_types: notifyTypes,
};
setPushToken(data).then(() => {
setPushTokenSaved(true);
});
};
_getAccountsWithUsername = async (username) => {
const validUsers = await lookupAccounts(username);
return validUsers;
@ -98,6 +135,8 @@ class LoginContainer extends PureComponent {
const mapStateToProps = state => ({
account: state.accounts,
notificationDetails: state.application.notificationDetails,
notificationSettings: state.application.isNotificationOpen,
});
export default injectIntl(connect(mapStateToProps)(LoginContainer));

View File

@ -1,5 +1,5 @@
import React, { PureComponent, Fragment } from 'react';
import { View, Text, ScrollView } from 'react-native';
import { View, ScrollView } from 'react-native';
import { injectIntl } from 'react-intl';
// Components
@ -16,7 +16,6 @@ import { Posts } from '../../../components/posts';
import { ProfileSummary } from '../../../components/profileSummary';
import { TabBar } from '../../../components/tabBar';
import { Wallet } from '../../../components/wallet';
import { FormatedCurrency } from '../../../components/formatedElements';
// Constants
import { PROFILE_FILTERS } from '../../../constants/options/filters';

View File

@ -10,23 +10,25 @@ import VersionNumber from 'react-native-version-number';
import {
getExistUser,
setCurrency as setCurrency2DB,
setServer,
setNotificationSettings,
setDefaultFooter,
setLanguage as setLanguage2DB,
setNotificationIsOpen,
setNsfw as setNsfw2DB,
setServer,
setTheme,
} from '../../../realm/realm';
// Services and Actions
import {
setLanguage,
changeNotificationSettings,
setCurrency,
setApi,
isDarkTheme,
isDefaultFooter,
isNotificationOpen,
openPinCodeModal,
setApi,
setCurrency,
setLanguage,
setNsfw,
} from '../../../redux/actions/applicationActions';
import { toastNotification } from '../../../redux/actions/uiAction';
@ -55,6 +57,7 @@ class SettingsContainer extends Component {
super(props);
this.state = {
serverList: [],
isNotificationMenuOpen: props.isNotificationSettingsOpen,
};
}
@ -147,7 +150,13 @@ class SettingsContainer extends Component {
switch (actionType) {
case 'notification':
this._handleNotification(action);
case 'notification.follow':
case 'notification.vote':
case 'notification.comment':
case 'notification.mention':
case 'notification.reblog':
case 'notification.transfers':
this._handleNotification(action, actionType);
break;
case 'theme':
@ -164,16 +173,38 @@ class SettingsContainer extends Component {
}
};
_handleNotification = async (action) => {
const { dispatch } = this.props;
_handleNotification = async (action, actionType) => {
const { dispatch, notificationDetails } = this.props;
const notifyTypesConst = {
vote: 1,
mention: 2,
follow: 3,
comment: 4,
reblog: 5,
transfers: 6,
};
const notifyTypes = [];
dispatch(isNotificationOpen(action));
setNotificationIsOpen(action);
dispatch(changeNotificationSettings({ action, type: actionType }));
setNotificationSettings({ action, type: actionType });
const isPushEnabled = await Push.isEnabled();
if (actionType === 'notification') {
await Push.setEnabled(action);
this._setPushToken(action ? [1, 2, 3, 4, 5, 6] : notifyTypes);
} else {
Object.keys(notificationDetails).map((item) => {
const notificationType = item.replace('Notification', '');
await Push.setEnabled(!isPushEnabled);
this._setPushToken();
if (notificationType === actionType.replace('notification.', '')) {
if (action) {
notifyTypes.push(notifyTypesConst[notificationType]);
}
} else if (notificationDetails[item]) {
notifyTypes.push(notifyTypesConst[notificationType]);
}
});
this._setPushToken(notifyTypes);
}
};
_handleButtonPress = (actionType) => {
@ -207,8 +238,9 @@ class SettingsContainer extends Component {
}
};
_setPushToken = async () => {
_setPushToken = async (notifyTypes) => {
const { isNotificationSettingsOpen, isLoggedIn, username } = this.props;
if (isLoggedIn) {
const token = await AppCenter.getInstallId();
@ -219,6 +251,7 @@ class SettingsContainer extends Component {
token,
system: Platform.OS,
allows_notify: Number(isNotificationSettingsOpen),
notify_types: notifyTypes,
};
setPushToken(data);
}
@ -257,12 +290,13 @@ class SettingsContainer extends Component {
};
render() {
const { serverList } = this.state;
const { serverList, isNotificationMenuOpen } = this.state;
return (
<SettingsScreen
serverList={serverList}
handleOnChange={this._handleOnChange}
isNotificationMenuOpen={isNotificationMenuOpen}
handleOnButtonPress={this._handleButtonPress}
{...this.props}
/>
@ -276,6 +310,13 @@ const mapStateToProps = state => ({
isLoggedIn: state.application.isLoggedIn,
isNotificationSettingsOpen: state.application.isNotificationOpen,
nsfw: state.application.nsfw,
notificationDetails: state.application.notificationDetails,
commentNotification: state.application.notificationDetails.commentNotification,
followNotification: state.application.notificationDetails.followNotification,
mentionNotification: state.application.notificationDetails.mentionNotification,
reblogNotification: state.application.notificationDetails.reblogNotification,
transfersNotification: state.application.notificationDetails.transfersNotification,
voteNotification: state.application.notificationDetails.voteNotification,
selectedApi: state.application.api,
selectedCurrency: state.application.currency,
selectedLanguage: state.application.language,

View File

@ -13,9 +13,10 @@ import NSFW from '../../../constants/options/nsfw';
// Components
import { BasicHeader } from '../../../components/basicHeader';
import { SettingsItem } from '../../../components/settingsItem';
import { CollapsibleCard } from '../../../components/collapsibleCard';
// Styles
import globalStyles from '../../../globalStyles';
import styles from './settingsStyles';
class SettingsScreen extends PureComponent {
/* Props
@ -44,18 +45,32 @@ class SettingsScreen extends PureComponent {
selectedCurrency,
selectedLanguage,
serverList,
isNotificationMenuOpen,
commentNotification,
followNotification,
mentionNotification,
reblogNotification,
transfersNotification,
voteNotification,
handleOnButtonPress,
} = this.props;
return (
<View style={globalStyles.container}>
<Fragment>
<BasicHeader
title={intl.formatMessage({
id: 'settings.settings',
})}
/>
<ScrollView style={globalStyles.settingsContainer}>
<ScrollView>
<View style={styles.settingsCard}>
<SettingsItem
title={intl.formatMessage({
id: 'settings.general',
})}
titleStyle={styles.cardTitle}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.currency',
@ -93,11 +108,9 @@ class SettingsScreen extends PureComponent {
})}
type="dropdown"
actionType="nsfw"
options={NSFW.map(item =>
intl.formatMessage({
options={NSFW.map(item => intl.formatMessage({
id: item,
}),
)}
}))}
selectedOptionIndex={parseInt(nsfw, 10)}
handleOnChange={handleOnChange}
/>
@ -112,24 +125,17 @@ class SettingsScreen extends PureComponent {
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.push_notification',
id: 'settings.send_feedback',
})}
type="toggle"
actionType="notification"
isOn={isNotificationSettingsOpen}
handleOnChange={handleOnChange}
text={intl.formatMessage({
id: 'settings.send',
})}
type="button"
actionType="feedback"
handleOnButtonPress={handleOnButtonPress}
/>
{!!isLoggedIn && (
<Fragment>
{/* <SettingsItem
title={intl.formatMessage({
id: 'settings.default_footer',
})}
type="toggle"
actionType="default_footer"
isOn={isDefaultFooter}
handleOnChange={handleOnChange}
/> */}
<SettingsItem
title={intl.formatMessage({
id: 'settings.pincode',
@ -142,20 +148,97 @@ class SettingsScreen extends PureComponent {
handleOnButtonPress={handleOnButtonPress}
/>
</Fragment>
// <SettingsItem
// title={intl.formatMessage({
// id: 'settings.default_footer',
// })}
// type="toggle"
// actionType="default_footer"
// isOn={isDefaultFooter}
// handleOnChange={handleOnChange}
// />
)}
</View>
{!!isLoggedIn && (
<View style={styles.settingsCard}>
<CollapsibleCard
titleComponent={(
<SettingsItem
title={intl.formatMessage({
id: 'settings.send_feedback',
id: 'settings.push_notification',
})}
text={intl.formatMessage({
id: 'settings.send',
})}
type="button"
actionType="feedback"
handleOnButtonPress={handleOnButtonPress}
titleStyle={styles.cardTitle}
type="toggle"
actionType="notification"
isOn={isNotificationSettingsOpen}
handleOnChange={handleOnChange}
/>
</ScrollView>
)}
noBorder
fitContent
locked
isExpanded={isNotificationSettingsOpen}
expanded={isNotificationMenuOpen}
>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.follow',
})}
type="toggle"
actionType="notification.follow"
isOn={followNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.vote',
})}
type="toggle"
actionType="notification.vote"
isOn={voteNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.comment',
})}
type="toggle"
actionType="notification.comment"
isOn={commentNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.mention',
})}
type="toggle"
actionType="notification.mention"
isOn={mentionNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.reblog',
})}
type="toggle"
actionType="notification.reblog"
isOn={reblogNotification}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.notification.transfers',
})}
type="toggle"
actionType="notification.transfers"
isOn={transfersNotification}
handleOnChange={handleOnChange}
/>
</CollapsibleCard>
</View>
)}
</ScrollView>
</Fragment>
);
}
}

View File

@ -0,0 +1,13 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
settingsCard: {
backgroundColor: '$primaryBackgroundColor',
paddingLeft: 42,
paddingRight: 32,
marginVertical: 5,
},
cardTitle: {
color: '$primaryBlue',
},
});