mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-05-18 13:16:11 +02:00
migrate settings to new ui
This commit is contained in:
@@ -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<MFAStep>({
|
||||
id: 'mfapick',
|
||||
props: {
|
||||
|
||||
@@ -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 (
|
||||
<View
|
||||
onLayout={event => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
button={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
await PremiumService.verify(menuRef.current?.show);
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 10,
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>{settings.homepage}</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{MenuItemsList.slice(0, MenuItemsList.length - 1).map(
|
||||
item =>
|
||||
item.name !== 'Monographs' && (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
onPress={async () => {
|
||||
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}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccentColorPicker = ({ settings = true, wrap = false }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
function changeAccentColor(color) {
|
||||
|
||||
@@ -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.
|
||||
</Paragraph>
|
||||
<Seperator half />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderRadius: 5,
|
||||
overflow: 'hidden',
|
||||
flexShrink: 1,
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{
|
||||
title: 'Never',
|
||||
value: 'useroff'
|
||||
},
|
||||
{
|
||||
title: 'Daily',
|
||||
value: 'daily'
|
||||
},
|
||||
{
|
||||
title: 'Weekly',
|
||||
value: 'weekly'
|
||||
},
|
||||
{
|
||||
title: 'Monthly',
|
||||
value: 'monthly'
|
||||
}
|
||||
].map((item, index) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={async () => {
|
||||
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
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
color={settings.reminder === item.value ? 'white' : colors.icon}
|
||||
size={SIZE.sm - 1}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
<AutomaticBackupsSelector />
|
||||
</View>
|
||||
|
||||
{!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 (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
borderRadius: 5,
|
||||
overflow: 'hidden',
|
||||
flexShrink: 1,
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{
|
||||
title: 'Never',
|
||||
value: 'useroff'
|
||||
},
|
||||
{
|
||||
title: 'Daily',
|
||||
value: 'daily'
|
||||
},
|
||||
{
|
||||
title: 'Weekly',
|
||||
value: 'weekly'
|
||||
},
|
||||
{
|
||||
title: 'Monthly',
|
||||
value: 'monthly'
|
||||
}
|
||||
].map((item, index) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={async () => {
|
||||
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
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
color={settings.reminder === item.value ? 'white' : colors.icon}
|
||||
size={SIZE.sm - 1}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsBackupAndRestore;
|
||||
|
||||
@@ -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: <Issue />
|
||||
});
|
||||
},
|
||||
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<RouteParams, 'SettingsHome'
|
||||
},
|
||||
desc: 'Notesnook app can be downloaded on all platforms'
|
||||
},
|
||||
{
|
||||
name: 'Documentation',
|
||||
func: async () => {
|
||||
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<RouteParams, 'SettingsHome'
|
||||
}
|
||||
];
|
||||
|
||||
const renderItem = ({ item, index }: { item: SettingSection; index: number }) => (
|
||||
<SectionGroup item={item} />
|
||||
);
|
||||
const renderItem = ({ item, index }: { item: SettingSection; index: number }) =>
|
||||
item.name === 'account' ? <SettingsUserSection item={item} /> : <SectionGroup item={item} />;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ContainerHeader>
|
||||
<Header title="Settings" isBack={true} screen="Settings" />
|
||||
<Header title="Settings" isBack={false} screen="Settings" />
|
||||
</ContainerHeader>
|
||||
|
||||
<FlatList
|
||||
data={groups}
|
||||
keyExtractor={(item, index) => item.name || index.toString()}
|
||||
ListFooterComponent={<View style={{ height: 200 }} />}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -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 : (
|
||||
<View
|
||||
style={{
|
||||
marginVertical: item.sections ? 10 : 0
|
||||
@@ -19,10 +20,10 @@ export const SectionGroup = ({ item }: { item: SettingSection }) => {
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
color={colors.icon}
|
||||
size={SIZE.sm}
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
{item.name}
|
||||
{item.name.toUpperCase()}
|
||||
</Heading>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -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: <AccentColorPicker wrap={true} />
|
||||
colorpicker: <AccentColorPicker wrap={true} />,
|
||||
homeselector: <HomagePageSelector />,
|
||||
autobackups: <AutomaticBackupsSelector />
|
||||
};
|
||||
|
||||
export const SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const settings = useSettingStore(state => state.settings);
|
||||
const navigation = useNavigation<NavigationProp<RouteParams>>();
|
||||
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 : (
|
||||
<PressableButton
|
||||
key={item.name}
|
||||
disabled={item.type === 'component'}
|
||||
@@ -46,18 +52,22 @@ export const SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
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;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 1
|
||||
flexShrink: 1,
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -71,7 +81,7 @@ export const SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
borderRadius: 100
|
||||
}}
|
||||
>
|
||||
{!!item.icon && <Icon name={item.icon} size={30} />}
|
||||
{!!item.icon && <Icon color={colors.icon} name={item.icon} size={30} />}
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -83,15 +93,24 @@ export const SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
<Paragraph color={colors.heading} size={SIZE.md + 1}>
|
||||
{item.name}
|
||||
</Paragraph>
|
||||
<Paragraph size={SIZE.sm}>{item.description}</Paragraph>
|
||||
{!!item.description && (
|
||||
<Paragraph size={SIZE.sm}>
|
||||
{typeof item.description === 'function' ? item.description() : item.description}
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
{!!item.component && components[item.component]}
|
||||
{!!item.component && (
|
||||
<>
|
||||
<Seperator />
|
||||
{components[item.component]}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{item.type === 'switch' && item.property && (
|
||||
<ToggleSwitch
|
||||
isOn={item.getter ? item.getter(item.property) : settings[item.property]}
|
||||
isOn={item.getter ? item.getter(item.property || current) : settings[item.property]}
|
||||
onColor={colors.accent}
|
||||
offColor={colors.icon}
|
||||
size="small"
|
||||
|
||||
@@ -3,13 +3,15 @@ import { Settings } from '../../stores/interfaces';
|
||||
export type SettingSection = {
|
||||
type?: 'screen' | 'switch' | 'component';
|
||||
name?: string;
|
||||
description?: string;
|
||||
description?: string | (() => string);
|
||||
icon?: string;
|
||||
property?: keyof Settings;
|
||||
sections?: SettingSection[];
|
||||
component?: string;
|
||||
modifer?: (property: keyof Settings) => Partial<Settings>;
|
||||
getter?: (property: keyof Settings) => any;
|
||||
modifer?: (...args: any[]) => void;
|
||||
getter?: (...args: any[]) => any;
|
||||
useHook?: (...args: any[]) => unknown;
|
||||
hidden?: (current: any) => boolean;
|
||||
};
|
||||
|
||||
export type SettingsGroup = {
|
||||
|
||||
@@ -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 = () => {
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 15,
|
||||
marginBottom: 15
|
||||
marginTop: 15
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -100,54 +104,73 @@ const SettingsUserSection = () => {
|
||||
<View
|
||||
style={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
paddingBottom: 4,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: colors.accent
|
||||
paddingBottom: 4
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderRadius: 100,
|
||||
borderColor: colors.accent,
|
||||
width: 20,
|
||||
height: 20,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
flexDirection: 'row'
|
||||
}}
|
||||
>
|
||||
<Icon size={SIZE.md} color={colors.accent} name="account-outline" />
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.shade,
|
||||
borderRadius: 100,
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Icon size={SIZE.xl} color={colors.accent} name="camera-outline" />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.accent} size={SIZE.xs + 1}>
|
||||
{SUBSCRIPTION_STATUS_STRINGS[user.subscription?.type].toUpperCase()}
|
||||
</Heading>
|
||||
|
||||
<Paragraph color={colors.heading} size={SIZE.sm}>
|
||||
{user?.email}
|
||||
</Paragraph>
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
Last synced{' '}
|
||||
<TimeSince
|
||||
style={{ fontSize: SIZE.xs, color: colors.icon }}
|
||||
time={lastSynced}
|
||||
/>
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Paragraph
|
||||
color={colors.heading}
|
||||
size={SIZE.sm}
|
||||
<Button
|
||||
height={35}
|
||||
style={{
|
||||
marginLeft: 5
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{user?.email}
|
||||
</Paragraph>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
padding: 5,
|
||||
paddingVertical: 2.5
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.accent} size={SIZE.sm}>
|
||||
{SUBSCRIPTION_STATUS_STRINGS[user.subscription?.type]}
|
||||
</Heading>
|
||||
fontSize={SIZE.xs}
|
||||
type="accent"
|
||||
title="GET PRO"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
@@ -195,17 +218,19 @@ const SettingsUserSection = () => {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM &&
|
||||
{/* {user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM &&
|
||||
user.subscription?.type !== SUBSCRIPTION_STATUS.BETA && (
|
||||
<>
|
||||
<Seperator />
|
||||
<Button
|
||||
onPress={manageSubscription}
|
||||
width="100%"
|
||||
style={{
|
||||
paddingHorizontal: 0
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 100,
|
||||
height: 40,
|
||||
alignSelf: 'flex-end'
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
fontSize={SIZE.sm}
|
||||
title={
|
||||
!user.isEmailConfirmed
|
||||
? 'Confirm your email'
|
||||
@@ -216,14 +241,14 @@ const SettingsUserSection = () => {
|
||||
Platform.OS === 'android'
|
||||
? `Resubscribe from Google Playstore`
|
||||
: user.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED
|
||||
? `Resubscribe to Notesnook Pro (${monthlyPlan?.product?.localizedPrice} / mo)`
|
||||
: `Subscribe to Notesnook Pro (${monthlyPlan?.product?.localizedPrice} / mo)`
|
||||
? `Resubscribe to Pro (${monthlyPlan?.product?.localizedPrice} / mo)`
|
||||
: `Get Pro (${monthlyPlan?.product?.localizedPrice} / mo)`
|
||||
}
|
||||
height={50}
|
||||
type="accent"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
</View>
|
||||
|
||||
{user?.subscription?.provider &&
|
||||
@@ -255,84 +280,9 @@ const SettingsUserSection = () => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{[
|
||||
{
|
||||
name: 'Save data recovery key',
|
||||
func: async () => {
|
||||
verifyUser(null, async () => {
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
});
|
||||
},
|
||||
desc: 'Recover your data using the recovery key if your password is lost.'
|
||||
},
|
||||
{
|
||||
name: 'Manage attachments',
|
||||
func: () => {
|
||||
eSendEvent(eOpenAttachmentsDialog);
|
||||
},
|
||||
desc: 'Manage all attachments in one place.'
|
||||
},
|
||||
{
|
||||
name: 'Change password',
|
||||
func: async () => {
|
||||
ChangePassword.present();
|
||||
},
|
||||
desc: 'Setup a new password for your account.'
|
||||
},
|
||||
{
|
||||
name: 'Having problems with syncing?',
|
||||
func: async () => {
|
||||
Progress.present();
|
||||
await Sync.run('global', true);
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
},
|
||||
desc: 'Try force sync to resolve issues with syncing.'
|
||||
},
|
||||
{
|
||||
name: 'Subscription not activated?',
|
||||
func: 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'
|
||||
});
|
||||
},
|
||||
desc: 'Verify your subscription to Notesnook Pro'
|
||||
}
|
||||
].map(item =>
|
||||
item.name === 'Subscription not activated?' &&
|
||||
(Platform.OS !== 'ios' || PremiumService.get()) ? null : (
|
||||
<CustomButton
|
||||
key={item.name}
|
||||
title={item.name}
|
||||
onPress={item.func}
|
||||
tagline={item.desc}
|
||||
color={item.name === 'Logout' ? colors.errorText : colors.pri}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
<TwoFactorAuth />
|
||||
{item.sections.map(item => (
|
||||
<SectionItem key={item.name} item={item} />
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -68,7 +68,7 @@ async function getProducts() {
|
||||
}
|
||||
|
||||
function get() {
|
||||
// if (__DEV__) return true;
|
||||
if (__DEV__) return true;
|
||||
|
||||
return SUBSCRIPTION_STATUS.BASIC !== premiumStatus;
|
||||
}
|
||||
|
||||
42
apps/mobile/src/utils/hooks/useVaultStatus.js
Normal file
42
apps/mobile/src/utils/hooks/useVaultStatus.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import BiometicService from '../../services/biometrics';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { db } from '../database';
|
||||
|
||||
const VaultStatusCache = {
|
||||
exists: false,
|
||||
biometryEnrolled: false,
|
||||
isBiometryAvailable: false
|
||||
};
|
||||
|
||||
export const useVaultStatus = () => {
|
||||
const [vaultStatus, setVaultStatus] = React.useState(VaultStatusCache);
|
||||
|
||||
const checkVaultStatus = useCallback(() => {
|
||||
db.vault.exists().then(async exists => {
|
||||
let available = await BiometicService.isBiometryAvailable();
|
||||
let fingerprint = await BiometicService.hasInternetCredentials();
|
||||
if (
|
||||
VaultStatusCache.exists === exists &&
|
||||
VaultStatusCache.biometryEnrolled === fingerprint &&
|
||||
VaultStatusCache.isBiometryAvailable === available
|
||||
)
|
||||
return;
|
||||
setVaultStatus({
|
||||
exists: exists,
|
||||
biometryEnrolled: fingerprint,
|
||||
isBiometryAvailable: available ? true : false
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkVaultStatus();
|
||||
eSubscribeEvent('vaultUpdated', () => checkVaultStatus());
|
||||
return () => {
|
||||
eUnSubscribeEvent('vaultUpdated', () => checkVaultStatus());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return vaultStatus;
|
||||
};
|
||||
Reference in New Issue
Block a user