From 96d0ea5973648563e39588fb124509f6f048463b Mon Sep 17 00:00:00 2001 From: ammarahm-ed Date: Thu, 7 Apr 2022 04:20:13 +0500 Subject: [PATCH] migrate settings to new ui --- apps/mobile/src/screens/settings/2fa.tsx | 8 +- .../mobile/src/screens/settings/appearance.js | 76 +++ .../src/screens/settings/backuprestore.js | 160 ++--- apps/mobile/src/screens/settings/home.tsx | 591 +++++++++++++++++- .../src/screens/settings/sectiongroup.tsx | 9 +- .../src/screens/settings/sectionitem.tsx | 53 +- apps/mobile/src/screens/settings/types.ts | 8 +- .../src/screens/settings/usersection.js | 192 +++--- apps/mobile/src/services/premium.js | 2 +- apps/mobile/src/utils/hooks/useVaultStatus.js | 42 ++ 10 files changed, 890 insertions(+), 251 deletions(-) create mode 100644 apps/mobile/src/utils/hooks/useVaultStatus.js diff --git a/apps/mobile/src/screens/settings/2fa.tsx b/apps/mobile/src/screens/settings/2fa.tsx index b1a4d01c1..d97b1c081 100644 --- a/apps/mobile/src/screens/settings/2fa.tsx +++ b/apps/mobile/src/screens/settings/2fa.tsx @@ -151,7 +151,7 @@ type MFAStepProps = { method?: MFAMethod; isSetup?: boolean; }; -const MFAMethodsPickerStep = ({ recovery, onSuccess }: MFAStepProps) => { +export const MFAMethodsPickerStep = ({ recovery, onSuccess }: MFAStepProps) => { const colors = useThemeStore(state => state.colors); const user = useUserStore(state => state.user); @@ -209,7 +209,7 @@ const MFAMethodsPickerStep = ({ recovery, onSuccess }: MFAStepProps) => { ); }; -const MFASetup = ({ method, onSuccess, setStep, recovery }: MFAStepProps) => { +export const MFASetup = ({ method, onSuccess, setStep, recovery }: MFAStepProps) => { const colors = useThemeStore(state => state.colors); const user = useUserStore(state => state.user); const [authenticatorDetails, setAuthenticatorDetails] = useState({ @@ -437,7 +437,7 @@ const MFASetup = ({ method, onSuccess, setStep, recovery }: MFAStepProps) => { ); }; -const MFARecoveryCodes = ({ method, recovery, onSuccess, isSetup = true }: MFAStepProps) => { +export const MFARecoveryCodes = ({ method, recovery, onSuccess, isSetup = true }: MFAStepProps) => { const colors = useThemeStore(state => state.colors); const [codes, setCodes] = useState([]); const [loading, setLoading] = useState(true); @@ -647,7 +647,7 @@ const MFASuccess = ({ recovery }: MFAStepProps) => { ); }; -const MFASheet = ({ recovery }: { recovery?: boolean }) => { +export const MFASheet = ({ recovery }: { recovery?: boolean }) => { const [step, setStep] = useState({ id: 'mfapick', props: { diff --git a/apps/mobile/src/screens/settings/appearance.js b/apps/mobile/src/screens/settings/appearance.js index 2ffb23410..fa9649413 100644 --- a/apps/mobile/src/screens/settings/appearance.js +++ b/apps/mobile/src/screens/settings/appearance.js @@ -212,6 +212,82 @@ const SettingsAppearanceSection = () => { ); }; +export const HomagePageSelector = () => { + const colors = useThemeStore(state => state.colors); + const settings = useSettingStore(state => state.settings); + const menuRef = useRef(); + const [width, setWidth] = useState(0); + return ( + { + setWidth(event.nativeEvent.layout.width); + }} + style={{ + width: '100%' + }} + > + { + await PremiumService.verify(menuRef.current?.show); + }} + type="grayBg" + customStyle={{ + flexDirection: 'row', + alignItems: 'center', + marginTop: 10, + width: '100%', + justifyContent: 'space-between', + padding: 12 + }} + > + {settings.homepage} + + + } + > + {MenuItemsList.slice(0, MenuItemsList.length - 1).map( + item => + item.name !== 'Monographs' && ( + { + menuRef.current?.hide(); + await SettingsService.set({ homepage: item.name }); + ToastEvent.show({ + heading: 'Homepage set to ' + item.name, + message: 'Restart the app for changes to take effect.', + type: 'success' + }); + }} + style={{ + backgroundColor: settings.homepage === item.name ? colors.nav : 'transparent', + width: '100%', + maxWidth: width + }} + textStyle={{ + fontSize: SIZE.md, + color: settings.homepage === item.name ? colors.accent : colors.pri + }} + > + {item.name} + + ) + )} + + + ); +}; + export const AccentColorPicker = ({ settings = true, wrap = false }) => { const colors = useThemeStore(state => state.colors); function changeAccentColor(color) { diff --git a/apps/mobile/src/screens/settings/backuprestore.js b/apps/mobile/src/screens/settings/backuprestore.js index 439e3f6cf..0d5aa8d14 100644 --- a/apps/mobile/src/screens/settings/backuprestore.js +++ b/apps/mobile/src/screens/settings/backuprestore.js @@ -1,17 +1,16 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Platform, TouchableOpacity, View } from 'react-native'; import ToggleSwitch from 'toggle-switch-react-native'; import Seperator from '../../components/ui/seperator'; import Paragraph from '../../components/ui/typography/paragraph'; -import { useThemeStore } from '../../stores/theme'; -import { useSettingStore, useUserStore } from '../../stores/stores'; import Backup from '../../services/backup'; import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager'; import PremiumService from '../../services/premium'; import SettingsService from '../../services/settings'; +import { useSettingStore, useUserStore } from '../../stores/stores'; +import { useThemeStore } from '../../stores/theme'; import { eCloseProgressDialog, eOpenLoginDialog, eOpenRestoreDialog } from '../../utils/events'; import { openLinkInBrowser } from '../../utils/functions'; -import { MMKV } from '../../utils/database/mmkv'; import { SIZE } from '../../utils/size'; import { sleep } from '../../utils/time'; import { CustomButton } from './button'; @@ -137,12 +136,6 @@ const SettingsBackupAndRestore = ({ isSheet }) => { await SettingsService.set({ encryptedBackup: !settings.encryptedBackup }); }; - const updateAskForBackup = async () => { - SettingsService.set({ - nextBackupRequestTime: Date.now() + 86400000 * 3 - }); - }; - return ( <> {!isSheet && ( @@ -182,72 +175,7 @@ const SettingsBackupAndRestore = ({ isSheet }) => { Backup your data once every week or daily automatically. - - {[ - { - title: 'Never', - value: 'useroff' - }, - { - title: 'Daily', - value: 'daily' - }, - { - title: 'Weekly', - value: 'weekly' - }, - { - title: 'Monthly', - value: 'monthly' - } - ].map((item, index) => ( - { - if (item.value === 'useroff') { - await SettingsService.set({ reminder: item.value }); - } else { - await PremiumService.verify(async () => { - if (Platform.OS === 'android') { - let granted = await Backup.checkBackupDirExists(); - if (!granted) { - console.log('returning'); - return; - } - } - await SettingsService.set({ reminder: item.value }); - }); - } - updateAskForBackup(); - }} - key={item.value} - style={{ - backgroundColor: settings.reminder === item.value ? colors.accent : colors.nav, - justifyContent: 'center', - alignItems: 'center', - width: '25%', - height: 35, - borderRightWidth: index !== 3 ? 1 : 0, - borderRightColor: colors.border - }} - > - - {item.title} - - - ))} - + {!isSheet && ( @@ -273,4 +201,84 @@ const SettingsBackupAndRestore = ({ isSheet }) => { ); }; +export const AutomaticBackupsSelector = () => { + const colors = useThemeStore(state => state.colors); + const settings = useSettingStore(state => state.settings); + const user = useUserStore(state => state.user); + const updateAskForBackup = async () => { + SettingsService.set({ + nextBackupRequestTime: Date.now() + 86400000 * 3 + }); + }; + + return ( + + {[ + { + title: 'Never', + value: 'useroff' + }, + { + title: 'Daily', + value: 'daily' + }, + { + title: 'Weekly', + value: 'weekly' + }, + { + title: 'Monthly', + value: 'monthly' + } + ].map((item, index) => ( + { + if (item.value === 'useroff') { + await SettingsService.set({ reminder: item.value }); + } else { + await PremiumService.verify(async () => { + if (Platform.OS === 'android') { + let granted = await Backup.checkBackupDirExists(); + if (!granted) { + console.log('returning'); + return; + } + } + await SettingsService.set({ reminder: item.value }); + }); + } + updateAskForBackup(); + }} + key={item.value} + style={{ + backgroundColor: settings.reminder === item.value ? colors.accent : colors.nav, + justifyContent: 'center', + alignItems: 'center', + width: '25%', + height: 35, + borderRightWidth: index !== 3 ? 1 : 0, + borderRightColor: colors.border + }} + > + + {item.title} + + + ))} + + ); +}; + export default SettingsBackupAndRestore; diff --git a/apps/mobile/src/screens/settings/home.tsx b/apps/mobile/src/screens/settings/home.tsx index b330f5931..bb364e44f 100644 --- a/apps/mobile/src/screens/settings/home.tsx +++ b/apps/mobile/src/screens/settings/home.tsx @@ -1,16 +1,42 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'; import React from 'react'; -import { FlatList, Linking, View } from 'react-native'; +import { FlatList, Linking, Platform, View } from 'react-native'; +import { enabled } from 'react-native-privacy-snapshot'; import { APP_VERSION } from '../../../version'; +import { ChangePassword } from '../../components/auth/change-password'; import { ContainerHeader } from '../../components/container/containerheader'; import { Header } from '../../components/header'; import { Issue } from '../../components/sheets/github/issue'; -import { presentSheet } from '../../services/event-manager'; -import { useSettingStore } from '../../stores/stores'; +import { Progress } from '../../components/sheets/progress'; +import BackupService from '../../services/backup'; +import { eSendEvent, openVault, presentSheet, ToastEvent } from '../../services/event-manager'; +import Notifications from '../../services/notifications'; +import PremiumService from '../../services/premium'; +import SettingsService from '../../services/settings'; +import { useSettingStore, useUserStore } from '../../stores/stores'; import { useThemeStore } from '../../stores/theme'; +import { AndroidModule } from '../../utils'; +import { toggleDarkMode } from '../../utils/color-scheme/utils'; + +import * as RNIap from 'react-native-iap'; +import { + eCloseProgressDialog, + eOpenAttachmentsDialog, + eOpenLoginDialog, + eOpenRecoveryKeyDialog, + eOpenRestoreDialog +} from '../../utils/events'; import { openLinkInBrowser } from '../../utils/functions'; +import { useVaultStatus } from '../../utils/hooks/use-vault-status'; +import { sleep } from '../../utils/time'; +import AppLock from './app-lock'; +import { verifyUser } from './functions'; import { SectionGroup } from './section-group'; import { RouteParams, SettingSection } from './types'; +import SettingsUserSection from './user-section'; +import Sync from '../../services/sync'; +import { MFARecoveryCodes, MFASheet } from './2fa'; +import { db } from '../../utils/database'; const format = (ver: number) => { let parts = ver.toString().split(''); return `v${parts[0]}.${parts[1]}.${parts[2]?.startsWith('0') ? '' : parts[2]}${ @@ -19,6 +45,163 @@ const format = (ver: number) => { }; const groups: SettingSection[] = [ + { + name: 'account', + sections: [ + { + type: 'screen', + name: 'Account Settings', + icon: 'account-cog', + description: 'Manage account', + sections: [ + { + name: 'Save data recovery key', + modifer: async () => { + verifyUser(null, async () => { + await sleep(300); + eSendEvent(eOpenRecoveryKeyDialog); + }); + }, + description: 'Recover your data using the recovery key if your password is lost.' + }, + { + name: 'Manage attachments', + icon: 'attachment', + modifer: () => { + eSendEvent(eOpenAttachmentsDialog); + }, + description: 'Manage all attachments in one place.' + }, + { + name: 'Change password', + modifer: async () => { + ChangePassword.present(); + }, + description: 'Setup a new password for your account.' + }, + { + type: 'screen', + name: 'Two factor authentication', + description: 'Manage 2FA settings', + icon: 'two-factor-authentication', + sections: [ + { + name: 'Enable two-factor authentication', + modifer: () => { + verifyUser('global', async () => { + MFASheet.present(); + }); + }, + hidden: () => { + let user = useUserStore.getState().user; + return !!user?.mfa?.isEnabled; + }, + description: 'Increased security for your account' + }, + { + name: 'Add fallback 2FA method', + hidden: () => { + let user = useUserStore.getState().user; + + return !!user?.mfa?.secondaryMethod || !user?.mfa?.isEnabled; + }, + modifer: () => { + verifyUser('global', async () => { + MFASheet.present(true); + }); + }, + description: + 'You can use fallback 2FA method incase you are unable to login via primary method' + }, + { + name: 'Reconfigure fallback 2FA method', + hidden: () => { + let user = useUserStore.getState().user; + return !user?.mfa?.secondaryMethod || !user?.mfa?.isEnabled; + }, + modifer: () => { + verifyUser('global', async () => { + MFASheet.present(true); + }); + }, + description: + 'You can use fallback 2FA method incase you are unable to login via primary method' + }, + { + name: 'View recovery codes', + modifer: () => { + verifyUser('global', async () => { + MFARecoveryCodes.present('sms'); + }); + }, + hidden: () => { + let user = useUserStore.getState().user; + return !user?.mfa?.isEnabled; + }, + description: 'View and save recovery codes for to recover your account' + }, + { + name: 'Disable two-factor authentication', + modifer: () => { + verifyUser('global', async () => { + await db.mfa?.disable(); + let user = await db.user?.fetchUser(); + useUserStore.getState().setUser(user); + }); + }, + hidden: () => { + let user = useUserStore.getState().user; + return !user?.mfa?.isEnabled; + }, + description: 'Decreased security for your account' + } + ] + }, + { + name: 'Subscription not activated?', + hidden: () => Platform.OS !== 'ios', + modifer: async () => { + if (Platform.OS === 'android') return; + presentSheet({ + title: 'Loading subscriptions', + paragraph: `Please wait while we fetch your subscriptions.` + }); + let subscriptions = await RNIap.getPurchaseHistory(); + subscriptions.sort((a, b) => b.transactionDate - a.transactionDate); + let currentSubscription = subscriptions[0]; + presentSheet({ + title: 'Notesnook Pro', + paragraph: `You subscribed to Notesnook Pro on ${new Date( + currentSubscription.transactionDate + ).toLocaleString()}. Verify this subscription?`, + action: async () => { + presentSheet({ + title: 'Verifying subscription', + paragraph: `Please wait while we verify your subscription.` + }); + await PremiumService.subscriptions.verify(currentSubscription); + eSendEvent(eCloseProgressDialog); + }, + icon: 'information-outline', + actionText: 'Verify' + }); + }, + description: 'Verify your subscription to Notesnook Pro' + } + ] + }, + { + name: 'Having problems with sync', + description: 'Try force sync to resolve issues with syncing', + icon: 'sync-alert', + modifer: async () => { + Progress.present(); + await Sync.run('global', true); + eSendEvent(eCloseProgressDialog); + } + } + ] + }, { name: 'Customize', sections: [ @@ -36,7 +219,7 @@ const groups: SettingSection[] = [ }, { type: 'switch', - name: 'Follow system theme', + name: 'Use system theme', description: 'Automatically switch to dark mode when system theme changes', property: 'useSystemTheme', icon: 'circle-half' @@ -48,11 +231,7 @@ const groups: SettingSection[] = [ property: 'theme', icon: 'brightness-6', modifer: () => { - let current = { ...useSettingStore.getState().settings.theme }; - current.dark = !current.dark; - return { - theme: current - }; + toggleDarkMode(); }, getter: () => useSettingStore.getState().settings.theme.dark }, @@ -68,7 +247,15 @@ const groups: SettingSection[] = [ { type: 'screen', name: 'Behaviour', - description: 'Change app homepage' + description: 'Change app homepage', + sections: [ + { + type: 'component', + name: 'Homepage', + description: 'Default screen to open on app startup', + component: 'homeselector' + } + ] } ] }, @@ -85,11 +272,373 @@ const groups: SettingSection[] = [ }, { type: 'screen', - name: 'Vault' + name: 'Vault', + description: 'Multi-layer encryption to most important notes', + icon: 'key', + sections: [ + { + name: 'Create vault', + description: 'Set a password to create vault and lock notes.', + icon: 'key', + useHook: useVaultStatus, + hidden: current => current?.exists, + modifer: () => { + PremiumService.verify(() => { + openVault({ + item: {}, + novault: false, + title: 'Create vault', + description: 'Set a password to create vault and lock notes.' + }); + }); + } + }, + { + useHook: useVaultStatus, + name: 'Change vault password', + description: 'Setup a new password for your vault.', + hidden: current => !current?.exists, + modifer: () => + openVault({ + item: {}, + changePassword: true, + novault: true, + title: 'Change vault password', + description: 'Set a new password for your vault.' + }) + }, + { + useHook: useVaultStatus, + name: 'Clear vault', + description: 'Unlock all locked notes', + hidden: current => !current?.exists, + modifer: () => { + openVault({ + item: {}, + clearVault: true, + novault: true, + title: 'Clear vault', + description: 'Enter vault password to unlock and remove all notes from the vault.' + }); + } + }, + { + name: 'Delete vault', + description: 'Delete vault (and optionally remove all notes).', + useHook: useVaultStatus, + hidden: current => !current?.exists, + modifer: () => { + openVault({ + item: {}, + deleteVault: true, + novault: true, + title: 'Delete vault', + description: 'Enter your account password to delete your vault.' + }); + } + }, + { + type: 'switch', + name: 'Biometric unlocking', + icon: 'fingerprint', + useHook: useVaultStatus, + description: 'Access notes in vault using biometrics', + hidden: current => !current?.exists || !current?.isBiometryAvailable, + getter: current => current?.biometryEnrolled, + modifer: current => { + openVault({ + item: {}, + fingerprintAccess: !current.biometryEnrolled, + revokeFingerprintAccess: current.biometryEnrolled, + novault: true, + title: current.biometryEnrolled + ? 'Revoke biometric unlocking' + : 'Enable biometery unlock', + description: current.biometryEnrolled + ? 'Disable biometric unlocking for notes in vault' + : 'Enable biometric unlocking for notes in vault' + }); + } + } + ] }, + { + type: 'switch', + icon: 'eye-off-outline', + name: 'Privacy mode', + description: `Hide app contents when you switch to other apps. This will also disable screenshot taking in the app.`, + modifer: () => { + const settings = SettingsService.get(); + Platform.OS === 'android' + ? AndroidModule.setSecureMode(!settings.privacyScreen) + : enabled(true); + + SettingsService.set({ privacyScreen: !settings.privacyScreen }); + }, + property: 'privacyScreen' + }, + { + name: 'App lock', + description: 'Change app lock mode to suit your needs', + icon: 'fingerprint', + modifer: () => { + AppLock.present(); + } + } + ] + }, + { + name: 'Backup and restore', + sections: [ { type: 'screen', - name: 'App lock' + name: 'Backups', + icon: 'backup-restore', + description: 'Create a backup or change backup settings', + sections: [ + { + name: 'Backup now', + description: 'Create a backup of your data', + modifer: async () => { + const user = useUserStore.getState().user; + if (!user) { + await BackupService.run(true); + return; + } + verifyUser(null, () => BackupService.run(true)); + } + }, + { + type: 'component', + name: 'Automatic backups', + description: 'Backup your data once every week or daily automatically.', + component: 'autobackups' + }, + { + name: 'Select backup directory', + description: 'Select directory to store backups', + icon: 'folder', + hidden: () => + !!SettingsService.get().backupDirectoryAndroid || Platform.OS !== 'android', + property: 'backupDirectoryAndroid', + modifer: async () => { + let dir; + try { + let dir = await BackupService.checkBackupDirExists(true); + } catch (e) { + } finally { + if (!dir) { + ToastEvent.show({ + heading: 'No directory selected', + type: 'error' + }); + } + } + } + }, + { + name: 'Change backup directory', + description: () => SettingsService.get().backupDirectoryAndroid?.name || '', + icon: 'folder', + hidden: () => + !SettingsService.get().backupDirectoryAndroid || Platform.OS !== 'android', + property: 'backupDirectoryAndroid', + modifer: async () => { + let dir; + try { + dir = await BackupService.checkBackupDirExists(true); + } catch (e) { + } finally { + if (!dir) { + ToastEvent.show({ + heading: 'No directory selected', + type: 'error' + }); + } + } + } + }, + { + type: 'switch', + name: 'Backup encryption', + description: 'Encrypt all your backups.', + icon: 'lock', + property: 'encryptedBackup', + modifer: async () => { + const user = useUserStore.getState().user; + const settings = SettingsService.get(); + if (!user) { + ToastEvent.show({ + heading: 'Login required to enable encryption', + type: 'error', + func: () => { + eSendEvent(eOpenLoginDialog); + }, + actionText: 'Login' + }); + return; + } + await SettingsService.set({ encryptedBackup: !settings.encryptedBackup }); + } + } + ] + }, + { + name: 'Restore backup', + description: `Restore backup from phone storage.`, + modifer: () => { + const user = useUserStore.getState().user; + if (!user || !user?.email) { + ToastEvent.show({ + heading: 'Login required', + message: 'Please log in to your account to restore backup', + type: 'error', + context: 'global' + }); + return; + } + eSendEvent(eOpenRestoreDialog); + } + } + ] + }, + { + name: 'Productivity', + hidden: () => Platform.OS !== 'android', + sections: [ + { + type: 'switch', + name: 'Notes in notifications', + description: `Add quick notes from notifications without opening the app.`, + property: 'notifNotes', + icon: 'form-textbox', + modifer: () => { + const settings = SettingsService.get(); + if (settings.notifNotes) { + Notifications.unpinQuickNote(); + } else { + Notifications.pinQuickNote(false); + } + SettingsService.toggle('notifNotes'); + } + } + ] + }, + { + name: 'Help and support', + sections: [ + { + name: 'Report an issue', + icon: 'bug', + modifer: () => { + presentSheet({ + component: + }); + }, + description: 'Faced an issue or have a suggestion? Click here to create a bug report' + }, + { + name: 'Documentation', + modifer: async () => { + Linking.openURL('https://docs.notesnook.com'); + }, + description: 'Learn about every feature and how it works.' + } + ] + }, + { + name: 'community', + sections: [ + { + name: 'Join our Telegram group', + description: "We are on telegram, let's talk", + icon: 'telegram', + modifer: () => { + Linking.openURL('https://t.me/notesnook').catch(console.log); + } + }, + { + name: 'Join our Discord community', + icon: 'discord', + modifer: async () => { + presentSheet({ + title: 'Join our Discord Community', + iconColor: 'discord', + paragraph: 'We are not ghosts, chat with us and share your experience.', + valueArray: [ + 'Talk with us anytime.', + 'Follow the development process', + 'Give suggestions and report issues.', + 'Get early access to new features', + 'Meet other people using Notesnook' + ], + icon: 'discord', + action: async () => { + try { + Linking.openURL('https://discord.gg/zQBK97EE22').catch(console.log); + } catch (e) {} + }, + actionText: 'Join Now' + }); + }, + description: 'We are not ghosts, chat with us and share your experience.' + } + ] + }, + { + name: 'legal', + sections: [ + { + name: 'Terms of service', + modifer: async () => { + try { + await Linking.openURL('https://notesnook.com/tos'); + } catch (e) {} + }, + description: 'Read our terms of service' + }, + { + name: 'Privacy policy', + modifer: async () => { + try { + await Linking.openURL('https://notesnook.com/privacy'); + } catch (e) {} + }, + description: 'Read our privacy policy' + } + ] + }, + { + name: 'about', + sections: [ + { + name: 'Download on desktop', + icon: 'monitor', + modifer: async () => { + try { + await Linking.openURL('https://notesnook.com'); + } catch (e) {} + }, + description: 'Notesnook app can be downloaded on all platforms' + }, + { + name: 'Roadmap', + icon: 'chart-timeline', + modifer: async () => { + try { + await Linking.openURL('https://docs.notesnook.com/roadmap/'); + } catch (e) {} + }, + description: 'See what the future of Notesnook is going to be like.' + }, + { + name: 'About Notesnook', + modifer: async () => { + try { + await Linking.openURL('https://notesnook.com'); + } catch (e) {} + }, + description: format(APP_VERSION) } ] } @@ -167,15 +716,7 @@ const Home = ({ navigation }: NativeStackScreenProps { - try { - await openLinkInBrowser('https://docs.notesnook.com', colors); - } catch (e) {} - }, - desc: 'Learn about every feature and how it works.' - }, + { name: 'Roadmap', func: async () => { @@ -196,19 +737,19 @@ const Home = ({ navigation }: NativeStackScreenProps ( - - ); + const renderItem = ({ item, index }: { item: SettingSection; index: number }) => + item.name === 'account' ? : ; return ( -
+
item.name || index.toString()} + ListFooterComponent={} renderItem={renderItem} /> diff --git a/apps/mobile/src/screens/settings/sectiongroup.tsx b/apps/mobile/src/screens/settings/sectiongroup.tsx index 3b02459da..bb8a626d0 100644 --- a/apps/mobile/src/screens/settings/sectiongroup.tsx +++ b/apps/mobile/src/screens/settings/sectiongroup.tsx @@ -8,7 +8,8 @@ import { SettingSection } from './types'; export const SectionGroup = ({ item }: { item: SettingSection }) => { const colors = useThemeStore(state => state.colors); - return ( + const isHidden = item.hidden && item.hidden(null); + return isHidden ? null : ( { style={{ paddingHorizontal: 12 }} - color={colors.icon} - size={SIZE.sm} + color={colors.accent} + size={SIZE.xs} > - {item.name} + {item.name.toUpperCase()} ) : null} diff --git a/apps/mobile/src/screens/settings/sectionitem.tsx b/apps/mobile/src/screens/settings/sectionitem.tsx index 707badc61..fd1bbad5b 100644 --- a/apps/mobile/src/screens/settings/sectionitem.tsx +++ b/apps/mobile/src/screens/settings/sectionitem.tsx @@ -1,37 +1,43 @@ -import { NavigationProp, useNavigation } from '@react-navigation/native'; +import { NavigationProp, StackActions, useNavigation } from '@react-navigation/native'; import React, { ReactElement } from 'react'; import { View } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import ToggleSwitch from 'toggle-switch-react-native'; import { PressableButton } from '../../components/ui/pressable'; +import Seperator from '../../components/ui/seperator'; import Paragraph from '../../components/ui/typography/paragraph'; import SettingsService from '../../services/settings'; import { useSettingStore } from '../../stores/stores'; import { useThemeStore } from '../../stores/theme'; import { SIZE } from '../../utils/size'; -import { AccentColorPicker } from './appearance'; +import { AccentColorPicker, HomagePageSelector } from './appearance'; +import { AutomaticBackupsSelector } from './backup-restore'; import { RouteParams, SettingSection } from './types'; const components: { [name: string]: ReactElement } = { - colorpicker: + colorpicker: , + homeselector: , + autobackups: }; export const SectionItem = ({ item }: { item: SettingSection }) => { const colors = useThemeStore(state => state.colors); const settings = useSettingStore(state => state.settings); const navigation = useNavigation>(); + const current = item.useHook && item.useHook(item); + const isHidden = item.hidden && item.hidden(item.property || current); const onChangeSettings = () => { + if (item.modifer) { + item.modifer(item.property || current); + return; + } if (!item.property) return; - SettingsService.set( - item.modifer - ? item.modifer(item.property) - : { - [item.property]: !settings[item.property] - } - ); + SettingsService.set({ + [item.property]: !settings[item.property] + }); }; - return ( + return isHidden ? null : ( { onPress={() => { switch (item.type) { case 'screen': - navigation.navigate('SettingsGroup', item); + navigation.dispatch(StackActions.push('SettingsGroup', item)); break; case 'switch': onChangeSettings(); break; + default: + item.modifer && item.modifer(current); + break; } }} > { borderRadius: 100 }} > - {!!item.icon && } + {!!item.icon && } { {item.name} - {item.description} + {!!item.description && ( + + {typeof item.description === 'function' ? item.description() : item.description} + + )} - {!!item.component && components[item.component]} + {!!item.component && ( + <> + + {components[item.component]} + + )} {item.type === 'switch' && item.property && ( string); icon?: string; property?: keyof Settings; sections?: SettingSection[]; component?: string; - modifer?: (property: keyof Settings) => Partial; - getter?: (property: keyof Settings) => any; + modifer?: (...args: any[]) => void; + getter?: (...args: any[]) => any; + useHook?: (...args: any[]) => unknown; + hidden?: (current: any) => boolean; }; export type SettingsGroup = { diff --git a/apps/mobile/src/screens/settings/usersection.js b/apps/mobile/src/screens/settings/usersection.js index 30184fa99..dcdfcb6f1 100644 --- a/apps/mobile/src/screens/settings/usersection.js +++ b/apps/mobile/src/screens/settings/usersection.js @@ -1,6 +1,6 @@ import dayjs from 'dayjs'; import React from 'react'; -import { Linking, Platform, View } from 'react-native'; +import { Linking, Platform, Text, View } from 'react-native'; import * as RNIap from 'react-native-iap'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { ChangePassword } from '../../components/auth/change-password'; @@ -14,6 +14,8 @@ import PremiumService from '../../services/premium'; import Sync from '../../services/sync'; import { useUserStore } from '../../stores/stores'; import { useThemeStore } from '../../stores/theme'; + +import { SectionItem } from './section-item'; import { SUBSCRIPTION_PROVIDER, SUBSCRIPTION_STATUS, @@ -31,6 +33,7 @@ import { sleep } from '../../utils/time'; import TwoFactorAuth from './2fa'; import { CustomButton } from './button'; import { verifyUser } from './functions'; +import { TimeSince } from '../../components/ui/time-since'; const getTimeLeft = t2 => { let daysRemaining = dayjs(t2).diff(dayjs(), 'days'); @@ -40,7 +43,7 @@ const getTimeLeft = t2 => { }; }; -const SettingsUserSection = () => { +const SettingsUserSection = ({ item }) => { const colors = useThemeStore(state => state.colors); const user = useUserStore(state => state.user); @@ -50,6 +53,8 @@ const SettingsUserSection = () => { const startDate = dayjs(user?.subscription?.start).format('MMMM D, YYYY'); const monthlyPlan = usePricing('monthly'); + const lastSynced = useUserStore(state => state.lastSynced); + const manageSubscription = () => { if (!user.isEmailConfirmed) { PremiumService.showVerifyEmailDialog(); @@ -84,8 +89,7 @@ const SettingsUserSection = () => { { - + + + + + + + + + {SUBSCRIPTION_STATUS_STRINGS[user.subscription?.type].toUpperCase()} + + + + {user?.email} + + + Last synced{' '} + + + - - {user?.email} - - - - - {SUBSCRIPTION_STATUS_STRINGS[user.subscription?.type]} - + fontSize={SIZE.xs} + type="accent" + title="GET PRO" + /> @@ -195,17 +218,19 @@ const SettingsUserSection = () => { ) : null} - {user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM && + {/* {user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM && user.subscription?.type !== SUBSCRIPTION_STATUS.BETA && ( <>