Files
notesnook/apps/mobile/app/screens/settings/settings-data.tsx
2025-12-10 10:11:21 +05:00

1615 lines
52 KiB
TypeScript

/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatBytes } from "@notesnook/common";
import {
SubscriptionPlan,
SubscriptionProvider,
SubscriptionStatus,
User
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import notifee from "@notifee/react-native";
import Clipboard from "@react-native-clipboard/clipboard";
import dayjs from "dayjs";
import React from "react";
import { Appearance, Linking, Platform } from "react-native";
import { getVersion } from "react-native-device-info";
import * as RNIap from "react-native-iap";
//@ts-ignore
import { enabled } from "react-native-privacy-snapshot";
import ScreenGuardModule from "react-native-screenguard";
import { DatabaseLogger, db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
import { endProgress, startProgress } from "../../components/dialogs/progress";
import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
import { Progress } from "../../components/sheets/progress";
import { Update } from "../../components/sheets/update";
import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
import { BackgroundSync } from "../../services/background-sync";
import BackupService from "../../services/backup";
import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Notifications from "../../services/notifications";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time";
import { MFARecoveryCodes, MFASheet } from "./2fa";
import { useDragState } from "./editor/state";
import { verifyUser, verifyUserWithApplock } from "./functions";
import { logoutUser } from "./logout";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
export const settingsGroups: SettingSection[] = [
{
id: "account",
name: strings.account(),
useHook: () => useUserStore((state) => state.user),
hidden: (current) => !current,
sections: [
{
id: "subscription-status",
useHook: () => useUserStore((state) => state.user),
hidden: (current) => {
const user = current as User;
return (
!user ||
!user.subscription ||
user.subscription.provider === undefined ||
!strings.subscriptionProviderInfo[user?.subscription?.provider] ||
user.subscription?.plan === SubscriptionPlan.FREE
);
},
name: (current) => {
const user = (current as User) || useUserStore.getState().user;
return (
strings.subscriptionProviderInfo[
user?.subscription?.provider
]?.title() || `Unknown provider id: ${user?.subscription?.provider}`
);
},
icon: "credit-card",
modifer: () => {
const user = useUserStore.getState().user;
if (!user) return;
const subscriptionProviderInfo =
strings.subscriptionProviderInfo[user?.subscription?.provider];
if (!subscriptionProviderInfo) return;
const isCurrentPlatform =
(user.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
(user.subscription?.provider === SubscriptionProvider.GOOGLE &&
Platform.OS === "android");
if (
(user.subscription?.provider === SubscriptionProvider.GOOGLE ||
user.subscription?.provider === SubscriptionProvider.APPLE) &&
isCurrentPlatform &&
user?.subscription?.productId
) {
RNIap.deepLinkToSubscriptions({
sku: user?.subscription.productId
});
} else {
presentSheet({
title: subscriptionProviderInfo.title(),
paragraph: subscriptionProviderInfo.desc()
});
}
},
description: (current) => {
const user = current as User;
if (!user) return strings.neverHesitate();
const subscriptionDaysLeft =
user && getTimeLeft(user.subscription?.expiry);
const expiryDate = dayjs(user?.subscription?.expiry).format(
"dddd, MMMM D, YYYY h:mm A"
);
const startDate = dayjs(user?.subscription?.start).format(
"dddd, MMMM D, YYYY h:mm A"
);
if (
user.subscription?.plan !== SubscriptionPlan.FREE &&
user.subscription?.productId
) {
const status = user.subscription?.status;
return status === SubscriptionStatus.TRIAL
? strings.trialEndsOn(
dayjs(user?.subscription?.start)
.add(
user?.subscription?.productId?.includes("monthly")
? 7
: 14
)
.format("dddd, MMMM D, YYYY h:mm A")
)
: status === SubscriptionStatus.ACTIVE
? strings.subRenewOn(expiryDate)
: status === SubscriptionStatus.CANCELED ||
status === SubscriptionStatus.PAUSED
? strings.subEndsOn(expiryDate)
: status === SubscriptionStatus.EXPIRED
? subscriptionDaysLeft.time < -3
? strings.subEnded()
: strings.accountDowngradedIn(3)
: strings.neverHesitate();
}
return strings.neverHesitate();
}
},
{
id: "redeem-gift-code",
name: strings.redeemGiftCode(),
description: strings.redeemGiftCodeDesc(),
hidden: (current) => {
return !current as boolean;
},
useHook: () =>
useUserStore(
(state) => state.user?.subscription?.plan === SubscriptionPlan.FREE
),
icon: "gift",
modifer: () => {
presentDialog({
title: strings.redeemGiftCode(),
paragraph: strings.redeemGiftCodeDesc(),
input: true,
inputPlaceholder: strings.code(),
positiveText: strings.redeem(),
positivePress: async (value) => {
db.subscriptions.redeemCode(value).catch((e) => {
ToastManager.show({
heading: "Error redeeming code",
message: (e as Error).message,
type: "error"
});
});
}
});
}
},
{
id: "account-settings",
type: "screen",
name: strings.manageAccount(),
icon: "account-cog",
description: strings.manageAccountDesc(),
sections: [
{
id: "remove-profile-picture",
name: strings.removeProfilePicture(),
description: strings.removeProfilePictureDesc(),
useHook: () =>
useUserStore((state) => state.profile?.profilePicture),
hidden: () => !useUserStore.getState().profile?.profilePicture,
modifer: () => {
presentDialog({
title: strings.removeProfilePicture(),
paragraph: strings.removeProfilePictureConfirmation(),
positiveText: strings.remove(),
positivePress: async () => {
db.settings
.setProfile({
profilePicture: undefined
})
.then(async () => {
useUserStore.setState({
profile: db.settings.getProfile()
});
});
}
});
}
},
{
id: "remove-name",
name: strings.removeFullName(),
description: strings.removeFullNameDesc(),
useHook: () => useUserStore((state) => state.profile?.fullName),
hidden: () => !useUserStore.getState().profile?.fullName,
modifer: () => {
presentDialog({
title: strings.removeFullName(),
paragraph: strings.removeFullNameConfirmation(),
positiveText: strings.remove(),
positivePress: async () => {
db.settings
.setProfile({
fullName: undefined
})
.then(async () => {
useUserStore.setState({
profile: db.settings.getProfile()
});
});
}
});
}
},
{
id: "recovery-key",
name: strings.saveDataRecoveryKey(),
modifer: async () => {
verifyUser(null, async () => {
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
});
},
description: strings.saveDataRecoveryKeyDesc(),
icon: "key"
},
{
id: "manage-attachments",
name: strings.manageAttachments(),
icon: "attachment",
type: "screen",
component: "attachments-manager",
description: strings.manageAttachmentsDesc(),
hideHeader: true
},
{
id: "change-password",
name: strings.changePassword(),
type: "screen",
description: strings.changePasswordDesc(),
component: "change-password",
icon: "form-textbox-password"
},
{
id: "change-email",
name: strings.changeEmail(),
type: "screen",
component: "change-email",
description: strings.changeEmailDesc(),
icon: "at"
},
{
id: "2fa-settings",
type: "screen",
name: strings.twoFactorAuth(),
description: strings.twoFactorAuthDesc(),
icon: "two-factor-authentication",
sections: [
{
id: "enable-2fa",
name: strings.change2faMethod(),
modifer: () => {
verifyUser("global", async () => {
MFASheet.present();
});
},
useHook: () => useUserStore((state) => state.user),
description: strings.change2faMethodDesc()
},
{
id: "2fa-fallback",
name: strings.addFallback2faMethod(),
useHook: () => useUserStore((state) => state.user),
hidden: (user) => {
return (
!!(user as User)?.mfa?.secondaryMethod ||
!(user as User)?.mfa?.isEnabled
);
},
modifer: () => {
verifyUser("global", async () => {
MFASheet.present(true);
});
},
description: strings.addFallback2faMethodDesc()
},
{
id: "change-2fa-method",
name: strings.change2faFallbackMethod(),
useHook: () => useUserStore((state) => state.user),
hidden: (user) => {
return (
!(user as User)?.mfa?.secondaryMethod ||
!(user as User)?.mfa?.isEnabled
);
},
modifer: () => {
verifyUser("global", async () => {
MFASheet.present(true);
});
},
description: strings.change2faFallbackMethod()
},
{
id: "view-2fa-codes",
name: strings.viewRecoveryCodes(),
modifer: () => {
verifyUser("global", async () => {
MFARecoveryCodes.present("sms");
});
},
useHook: () => useUserStore((state) => state.user),
hidden: (user) => {
return !(user as User)?.mfa?.isEnabled;
},
description: strings.viewRecoveryCodesDesc()
}
]
},
{
id: "subscription-not-active",
name: strings.subscriptionNotActivated(),
hidden: () => Platform.OS !== "ios",
modifer: async () => {
if (Platform.OS === "android") return;
presentSheet({
title: strings.loadingSubscription(),
paragraph: strings.loadingSubscriptionDesc()
});
const subscriptions = await RNIap.getPurchaseHistory();
subscriptions.sort(
(a, b) => b.transactionDate - a.transactionDate
);
const currentSubscription = subscriptions[0];
presentSheet({
title: strings.notesnookPro(),
paragraph: strings.subscribedOnVerify(
new Date(currentSubscription.transactionDate).toLocaleString()
),
action: async () => {
presentSheet({
title: strings.verifySubscription(),
paragraph: strings.subscriptionVerifyWait()
});
await PremiumService.subscriptions.verify(
currentSubscription
);
eSendEvent(eCloseSheet);
},
icon: "information-outline",
actionText: strings.verify()
});
},
description: strings.verifySubDesc()
},
{
id: "clear-cache",
name: strings.clearCache(),
icon: "delete",
modifer: async () => {
presentDialog({
title: strings.clearCacheConfirm(),
paragraph: strings.clearCacheConfirmDesc(),
positiveText: strings.clear(),
positivePress: async () => {
filesystem.clearCache();
ToastManager.show({
heading: strings.cacheCleared(),
message: strings.cacheClearedDesc(),
type: "success"
});
}
});
},
description(current) {
return strings.clearCacheDesc(current as number);
},
useHook: () => {
const [cacheSize, setCacheSize] = React.useState(0);
React.useEffect(() => {
filesystem
.getCacheSize()
.then(setCacheSize)
.catch(() => {
/* empty */
});
const sub = eSubscribeEvent("cache-cleared", () => {
setCacheSize(0);
});
return () => {
sub?.unsubscribe();
};
}, []);
return formatBytes(cacheSize);
}
},
{
id: "logout",
name: strings.logout(),
description: strings.logoutWarnin(),
icon: "logout",
modifer: logoutUser
},
{
id: "delete-account",
type: "danger",
name: strings.deleteAccount(),
icon: "alert",
description: strings.deleteAccountDesc(),
modifer: () => {
presentDialog({
title: strings.deleteAccount(),
paragraphColor: "red",
paragraph: strings.deleteAccountDesc(),
positiveType: "errorShade",
input: true,
secureTextEntry: true,
inputPlaceholder: strings.enterAccountPassword(),
positiveText: strings.delete(),
positivePress: async (value) => {
try {
const verified = await db.user?.verifyPassword(value);
if (verified) {
setTimeout(async () => {
try {
startProgress({
title: "Deleting account",
paragraph:
"Please wait while we delete your account"
});
await db.user?.deleteUser(value);
DatabaseLogger.info("User account deleted");
Navigation.navigate("Notes");
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
} catch (e) {
endProgress();
DatabaseLogger.error(e);
ToastManager.error(
e as Error,
strings.failedToDeleteAccount(),
"global"
);
}
}, 300);
} else {
ToastManager.show({
heading: strings.passwordIncorrect(),
type: "error",
context: "global"
});
}
} catch (e) {
ToastManager.error(
e as Error,
strings.failedToDeleteAccount(),
"global"
);
}
}
});
}
}
]
},
{
id: "sync-settings",
name: strings.syncSettings(),
description: strings.syncSettingsDesc(),
type: "screen",
icon: "autorenew",
component: "offline-mode-progress",
sections: [
{
id: "offline-mode",
icon: "download-multiple",
name: strings.fullOfflineMode(),
description: strings.fullOfflineModeDesc(),
type: "switch",
property: "offlineMode",
featureId: "fullOfflineMode",
modifer: () => {
const current = SettingsService.get().offlineMode;
if (current) {
SettingsService.setProperty("offlineMode", false);
db.fs().cancel("offline-mode");
return;
}
SettingsService.setProperty("offlineMode", true);
db.attachments.cacheAttachments().catch(() => {
/* empty */
});
}
},
{
id: "auto-sync",
name: strings.disableAutoSync(),
description: strings.disableAutoSyncDesc(),
type: "switch",
property: "disableAutoSync",
featureId: "syncControls",
icon: "sync-off"
},
{
id: "disable-realtime-sync",
name: strings.disableRealtimeSync(),
description: strings.disableRealtimeSyncDesc(),
type: "switch",
property: "disableRealtimeSync",
featureId: "syncControls"
},
{
id: "disable-sync",
name: strings.disableSync(),
description: strings.disableSyncDesc(),
type: "switch",
property: "disableSync",
featureId: "syncControls",
icon: "cloud-off-outline"
},
{
id: "background-sync",
name: strings.backgroundSync(),
description: strings.backgroundSyncDesc(),
type: "switch",
property: "backgroundSync",
icon: "cloud-upload-outline",
onChange: (value) => {
if (value) {
BackgroundSync.start();
} else {
BackgroundSync.stop();
}
}
},
{
id: "pull-sync",
name: strings.forcePullChanges(),
description: strings.forcePullChangesDesc(),
icon: "download",
modifer: () => {
presentDialog({
title: strings.forcePullChanges(),
paragraph: strings.forceSyncNotice(),
negativeText: strings.cancel(),
positiveText: strings.start(),
positivePress: async () => {
eSendEvent(eCloseSheet);
await sleep(300);
Progress.present();
Sync.run("global", true, "fetch", () => {
eSendEvent(eCloseSheet);
});
}
});
}
},
{
id: "push-sync",
name: strings.forcePushChanges(),
description: strings.forcePushChangesDesc(),
icon: "upload",
modifer: () => {
presentDialog({
title: strings.forcePushChanges(),
paragraph: strings.forceSyncNotice(),
negativeText: strings.cancel(),
positiveText: strings.start(),
positivePress: async () => {
eSendEvent(eCloseSheet);
await sleep(300);
Progress.present();
Sync.run("global", true, "send", () => {
eSendEvent(eCloseSheet);
});
}
});
}
}
]
},
{
id: "notesnook-circle",
name: strings.notesnookCircle(),
icon: "circle-outline",
type: "screen",
description: strings.notesnookCircleDesc(),
component: "notesnook-circle"
}
]
},
{
id: "customize",
name: strings.customization(),
sections: [
{
id: "personalization",
type: "screen",
name: strings.appearance(),
description: strings.appearanceDesc(),
icon: "shape",
sections: [
{
id: "theme-picker",
type: "screen",
name: strings.themes(),
description: strings.themesDesc(),
component: "theme-selector",
icon: "shape"
},
{
id: "use-system-theme",
type: "switch",
name: strings.useSystemTheme(),
description: strings.useSystemThemeDesc(),
property: "useSystemTheme",
icon: "circle-half",
modifer: () => {
const current = SettingsService.get().useSystemTheme;
SettingsService.set({
useSystemTheme: !current
});
if (!current) {
useThemeStore
.getState()
.setColorScheme(Appearance.getColorScheme() as any);
}
}
},
{
id: "enable-dark-mode",
type: "switch",
name: strings.darkMode(),
description: strings.darkModeDesc(),
property: "colorScheme",
icon: "brightness-6",
modifer: () => {
useThemeStore.getState().setColorScheme();
},
getter: () => useThemeStore.getState().colorScheme === "dark"
}
]
},
{
id: "behaviour",
type: "screen",
icon: "brain",
name: strings.behavior(),
description: strings.behaviorDesc(),
sections: [
{
id: "default-sidebar-view",
type: "component",
name: strings.defaultSidebarTab(),
description: strings.defaultSidebarTabDesc(),
component: "sidebar-tab-selector"
},
{
id: "date-format",
name: strings.dateFormat(),
description: strings.dateFormatDesc(),
type: "component",
component: "date-format-selector",
icon: "calendar-blank"
},
{
id: "time-format",
name: strings.timeFormat(),
description: strings.timeFormatDesc(),
type: "component",
component: "time-format-selector",
icon: "clock-digital"
},
{
id: "clear-trash-interval",
type: "component",
name: strings.clearTrashInterval(),
description: strings.clearTrashIntervalDesc(),
component: "trash-interval-selector",
icon: "delete"
},
{
id: "default-notebook",
name: strings.clearDefaultNotebook(),
description: strings.clearDefaultNotebookDesc(),
modifer: () => {
db.settings.setDefaultNotebook(undefined);
ToastManager.show({
heading: strings.defaultNotebookCleared(),
type: "success"
});
},
disabled: () => !db.settings.getDefaultNotebook(),
icon: "notebook-minus"
},
{
id: "disable-update-check",
type: "switch",
name: strings.autoUpdateCheck(),
description: strings.autoUpdateCheckDesc(),
property: "checkForUpdates",
icon: "update"
}
]
},
{
id: "editor",
name: strings.editor(),
type: "screen",
icon: "note-edit-outline",
description: strings.editorDesc(),
sections: [
{
id: "configure-toolbar",
type: "screen",
name: strings.customizeToolbar(),
description: strings.customizeToolbarDesc(),
component: "configuretoolbar"
},
{
id: "reset-toolbar",
name: strings.resetToolbar(),
description: strings.resetToolbarDesc(),
modifer: () => {
useDragState.getState().setPreset("default");
ToastManager.show({
heading: strings.toolbarReset(),
type: "success"
});
}
},
{
id: "double-spaced-lines",
name: strings.doubleSpacedLines(),
description: strings.doubleSpacedLinesDesc(),
type: "switch",
property: "doubleSpacedLines",
icon: "format-line-spacing",
onChange: () => {
ToastManager.show({
heading: strings.lineSpacingChanged(),
type: "success"
});
}
},
{
id: "default-font-size",
name: strings.defaultFontSize(),
description: strings.defaultFontSizeDesc(),
type: "input-selector",
minInputValue: 8,
maxInputValue: 120,
icon: "format-size",
property: "defaultFontSize"
},
{
id: "default-font-family",
name: strings.defaultFontFamily(),
description: strings.defaultFontFamilyDesc(),
type: "component",
icon: "format-font",
property: "defaultFontFamily",
component: "font-selector"
},
{
id: "title-format",
name: strings.titleFormat(),
component: "title-format",
description: strings.titleFormatDesc(),
type: "component"
},
{
id: "toggle-markdown",
name: strings.mardownShortcuts(),
property: "markdownShortcuts",
description: strings.mardownShortcutsDesc(),
type: "switch",
featureId: "markdownShortcuts"
}
]
},
{
id: "servers",
type: "screen",
name: strings.servers(),
description: strings.serversConfigurationDesc(),
icon: "server",
component: "server-config"
}
]
},
{
id: "privacy-security",
name: strings.privacyAndSecurity(),
sections: [
{
id: "marketing-emails",
type: "switch",
icon: "email-newsletter",
name: strings.marketingEmails(),
description: strings.marketingEmailsDesc(),
modifer: async () => {
try {
await db.user?.changeMarketingConsent(
!useUserStore.getState().user?.marketingConsent
);
useUserStore.getState().setUser(await db.user?.fetchUser());
} catch (e) {
ToastManager.error(e as Error);
}
},
getter: (current: any) => current?.marketingConsent,
useHook: () => useUserStore((state) => state.user),
hidden: (current) => !current
},
{
id: "cors-bypass",
type: "input",
name: strings.corsBypass(),
description: strings.corsBypassDesc(),
inputProperties: {
defaultValue: "https://cors.notesnook.com",
autoCorrect: false,
keyboardType: "url"
},
property: "corsProxy",
icon: "arrow-decision-outline"
},
{
id: "vault",
type: "screen",
name: strings.vault(),
description: strings.vaultDesc(),
icon: "key",
sections: [
{
id: "create-vault",
name: strings.createVault(),
description: strings.createVaultDesc(),
icon: "key",
useHook: useVaultStatus,
hidden: (current) => (current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
novault: false,
title: strings.createVault()
});
}
},
{
id: "change-vault-password",
useHook: useVaultStatus,
name: strings.changeVaultPassword(),
description: strings.changeVaultPasswordDesc(),
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () =>
openVault({
item: {},
changePassword: true,
novault: true,
title: strings.changeVaultPassword()
})
},
{
id: "clear-vault",
useHook: useVaultStatus,
description: strings.clearVaultDesc(),
name: strings.clearVault(),
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
clearVault: true,
novault: true,
title: strings.clearVault() + "?"
});
}
},
{
id: "delete-vault",
name: strings.deleteVault(),
description: strings.deleteVaultDesc(),
useHook: useVaultStatus,
hidden: (current) => !(current as VaultStatusType)?.exists,
modifer: () => {
openVault({
item: {},
deleteVault: true,
novault: true,
title: strings.deleteVault() + "?"
});
}
},
{
id: "biometric-unlock",
type: "switch",
name: strings.biometricUnlock(),
icon: "fingerprint",
useHook: useVaultStatus,
description: strings.biometricUnlockDesc(),
hidden: (current) => {
const _current = current as VaultStatusType;
return !_current?.exists || !_current?.isBiometryAvailable;
},
getter: (current) => (current as VaultStatusType)?.biometryEnrolled,
modifer: (current) => {
const _current = current as VaultStatusType;
openVault({
item: {},
fingerprintAccess: !_current.biometryEnrolled,
revokeFingerprintAccess: _current.biometryEnrolled,
novault: true,
title: _current.biometryEnrolled
? strings.revokeBiometricUnlock()
: strings.vaultEnableBiometrics()
});
}
}
]
},
{
id: "privacy-mode",
type: "switch",
icon: "eye-off-outline",
name: strings.privacyMode(),
description: strings.privacyModeDesc(),
modifer: () => {
const settings = SettingsService.get();
if (Platform.OS === "ios") {
enabled(!settings.privacyScreen);
if (settings.privacyScreen) {
ScreenGuardModule.unregister();
} else {
ScreenGuardModule.register({
backgroundColor: "#000000"
});
}
} else {
NotesnookModule.setSecureMode(!settings.privacyScreen);
}
SettingsService.set({ privacyScreen: !settings.privacyScreen });
},
property: "privacyScreen"
},
{
id: "app-lock",
name: strings.appLock(),
type: "screen",
description: strings.appLockDesc(),
icon: "lock",
featureId: "appLock",
sections: [
{
id: "app-lock-mode",
name: strings.enableAppLock(),
description: strings.appLockDesc(),
icon: "lock",
type: "switch",
property: "appLockEnabled",
featureId: "appLock",
onChange: () => {
SettingsService.set({
privacyScreen: true
});
SettingsService.setPrivacyScreen(SettingsService.get());
},
onVerify: async () => {
const verified = await verifyUserWithApplock();
if (!verified) return false;
if (!SettingsService.getProperty("appLockEnabled")) {
if (
!SettingsService.getProperty("appLockHasPasswordSecurity") &&
(await BiometricService.isBiometryAvailable())
) {
SettingsService.setProperty("biometricsAuthEnabled", true);
}
if (
!(await BiometricService.isBiometryAvailable()) &&
!SettingsService.getProperty("appLockHasPasswordSecurity")
) {
ToastManager.show({
heading: strings.biometricsNotEnrolled(),
type: "error",
message: strings.biometricsNotEnrolledDesc()
});
return false;
}
}
return verified;
}
},
{
id: "app-lock-timer",
name: strings.appLockTimeout(),
description: strings.appLockTimeoutDesc(),
type: "component",
component: "applock-timer"
},
{
id: "app-lock-pin",
name: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.setupAppLockPin()
: strings.setupAppLockPassword(),
description: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.setupAppLockPinDesc()
: strings.setupAppLockPasswordDesc(),
hidden: () => {
return !!SettingsService.getProperty(
"appLockHasPasswordSecurity"
);
},
onVerify: () => {
return verifyUserWithApplock();
},
property: "appLockHasPasswordSecurity",
modifer: () => {
AppLockPassword.present("create");
}
},
{
id: "app-lock-pin-change",
name: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.changeAppLockPin()
: strings.changeAppLockPassword(),
description: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.changeAppLockPinDesc()
: strings.changeAppLockPasswordDesc(),
hidden: () => {
return !SettingsService.getProperty("appLockHasPasswordSecurity");
},
property: "appLockHasPasswordSecurity",
modifer: () => {
AppLockPassword.present("change");
}
},
{
id: "app-lock-pin-remove",
name: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.removeAppLockPin()
: strings.removeAppLockPassword(),
description: () =>
SettingsService.getProperty("applockKeyboardType") === "numeric"
? strings.removeAppLockPinDesc()
: strings.removeAppLockPasswordDesc(),
hidden: () => {
return !SettingsService.getProperty("appLockHasPasswordSecurity");
},
property: "appLockHasPasswordSecurity",
modifer: () => {
AppLockPassword.present("remove");
}
},
{
id: "app-lock-fingerprint",
name: strings.unlockWithBiometrics(),
description: strings.unlockWithBiometricsDesc(),
type: "switch",
property: "biometricsAuthEnabled",
onVerify: async () => {
const verified = await verifyUserWithApplock();
if (!verified) return false;
if (SettingsService.getProperty("biometricsAuthEnabled")) {
if (
!SettingsService.getProperty("appLockHasPasswordSecurity")
) {
SettingsService.setProperty("appLockEnabled", false);
ToastManager.show({
heading: strings.appLockDisabled(),
type: "success"
});
}
}
return verified;
},
icon: "fingerprint"
}
]
}
]
},
{
id: "back-restore",
name: strings.backupRestore(),
sections: [
{
id: "backups",
type: "screen",
name: strings.backups(),
icon: "backup-restore",
description: strings.backupsDesc(),
sections: [
{
id: "backup-now",
name: strings.backupNow(),
description: strings.backupNowDesc(),
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
await BackupService.run(true);
return;
}
verifyUser(null, () => BackupService.run(true));
}
},
{
id: "backup-now",
name: strings.backupNowWithAttachments(),
description: strings.backupNowWithAttachmentsDesc(),
hidden: () => !useUserStore.getState().user,
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
await BackupService.run(true, undefined, "full");
return;
}
verifyUser(null, () =>
BackupService.run(true, undefined, "full")
);
}
},
{
id: "auto-backups",
type: "component",
name: strings.automaticBackups(),
description: strings.automaticBackupsDesc(),
component: "autobackups"
},
{
id: "auto-backups-with-attachments",
type: "component",
hidden: () => !useUserStore.getState().user,
name: strings.automaticBackupsWithAttachments(),
description: [
...strings.automaticBackupsWithAttachmentsDesc()
].join("\n"),
component: "autobackupsattachments"
},
{
id: "select-backup-dir",
name: strings.selectBackupDir(),
description: () => {
const desc = strings.selectBackupDirDesc(
SettingsService.get().backupDirectoryAndroid?.path || ""
);
return desc[0] + " " + desc[1];
},
icon: "folder",
hidden: () =>
!!SettingsService.get().backupDirectoryAndroid ||
Platform.OS !== "android",
property: "backupDirectoryAndroid",
modifer: async () => {
let dir;
try {
dir = await BackupService.checkBackupDirExists(true);
} catch (e) {
console.error(e);
} finally {
if (!dir) {
ToastManager.show({
heading: strings.noDirectorySelected(),
type: "error"
});
}
}
}
},
{
id: "change-backup-dir",
name: strings.changeBackupDir(),
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) {
console.error(e);
} finally {
if (!dir) {
ToastManager.show({
heading: strings.noDirectorySelected(),
type: "error"
});
}
}
}
},
{
id: "enable-backup-encryption",
type: "switch",
name: strings.backupEncryption(),
description: strings.backupEncryptionDesc(),
icon: "lock",
property: "encryptedBackup",
modifer: async () => {
const user = useUserStore.getState().user;
const settings = SettingsService.get();
if (!user) {
ToastManager.show({
heading: strings.loginRequired(),
type: "error"
});
return;
}
if (settings.encryptedBackup) {
await verifyUser(null, () => {
SettingsService.set({
encryptedBackup: false
});
});
} else {
SettingsService.set({
encryptedBackup: true
});
}
}
}
]
},
{
id: "restore-backup",
name: strings.restoreBackup(),
description: strings.restoreBackupDesc(),
icon: "restore",
type: "screen",
component: "backuprestore"
},
{
id: "export-notes",
name: strings.exportAllNotes(),
icon: "export",
description: strings.exportAllNotesDesc(),
modifer: () => {
verifyUser(null, () => {
ExportNotesSheet.present(undefined, true);
});
}
}
]
},
{
id: "productivity",
name: strings.productivity(),
sections: [
{
id: "notification-notes",
type: "switch",
name: strings.quickNoteNotification(),
description: strings.quickNoteNotificationDesc(),
property: "notifNotes",
icon: "form-textbox",
modifer: async () => {
const settings = SettingsService.get();
if (settings.notifNotes) {
Notifications.unpinQuickNote();
} else {
Notifications.pinQuickNote(false);
}
SettingsService.set({
notifNotes: !settings.notifNotes
});
},
hidden: () => Platform.OS !== "android",
featureId: "createNoteFromNotificationDrawer"
},
{
id: "reminders",
type: "screen",
name: strings.reminders(),
icon: "bell",
description: strings.remindersDesc(),
sections: [
{
id: "enable-reminders",
property: "reminderNotifications",
type: "switch",
name: strings.reminderNotification(),
icon: "bell-outline",
onChange: (property) => {
if (property) {
Notifications.setupReminders();
} else {
Notifications.clearAllTriggers();
}
},
description: strings.reminderNotificationDesc()
},
{
id: "snooze-time",
property: "defaultSnoozeTime",
type: "input",
name: strings.defaultSnoozeTime(),
description: strings.defaultSnoozeTimeDesc(),
inputProperties: {
keyboardType: "decimal-pad",
defaultValue: 5 + "",
placeholder: strings.setSnoozeTimePlaceholder(),
onSubmitEditing: () => {
Notifications.setupReminders();
}
}
},
{
id: "reminder-sound-ios",
type: "screen",
name: strings.changeNotificationSound(),
description: strings.changeNotificationSoundDesc(),
component: "sound-picker",
icon: "bell-ring",
hidden: () =>
Platform.OS === "ios" ||
(Platform.OS === "android" && Platform.Version > 25)
},
{
id: "reminder-sound-android",
name: strings.changeNotificationSound(),
description: strings.changeNotificationSoundDesc(),
icon: "bell-ring",
hidden: () =>
Platform.OS === "ios" ||
(Platform.OS === "android" && Platform.Version < 26),
modifer: async () => {
const id = await Notifications.getChannelId("urgent");
if (id) {
await notifee.openNotificationSettings(id);
}
}
}
]
}
]
},
{
id: "help-support",
name: strings.helpAndSupport(),
sections: [
{
id: "report-issue",
name: strings.reportAnIssue(),
icon: "bug",
modifer: () => {
presentSheet({
//@ts-ignore Migrate to TS
component: <Issue />
});
},
description: strings.reportAnIssueDesc()
},
{
id: "email-support",
name: strings.emailSupport(),
icon: "email",
modifer: () => {
Clipboard.setString("support@streetwriters.co");
ToastManager.show({
heading: strings.emailCopied(),
type: "success",
icon: "content-copy"
});
setTimeout(() => {
Linking.openURL("mailto:support@streetwriters.co");
}, 1000);
},
description: strings.emailSupportDesc()
},
{
id: "docs-link",
name: strings.documentation(),
modifer: async () => {
Linking.openURL("https://help.notesnook.com/");
},
description: strings.documentationDesc(),
icon: "file-document"
},
{
id: "debugging",
name: strings.debugging(),
description: strings.debuggingDesc(),
type: "screen",
icon: "bug",
sections: [
{
id: "debug-logs",
type: "screen",
name: strings.debugLogs(),
description: strings.debugLogsDesc(),
component: "debug-logs"
}
]
}
]
},
{
id: "community",
name: strings.community(),
sections: [
{
id: "join-telegram",
name: strings.joinTelegram(),
description: strings.joinTelegramDesc(),
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
}
},
{
id: "join-mastodon",
name: strings.joinMastodon(),
description: strings.joinMastodonDesc(),
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(
console.log
);
}
},
{
id: "join-twitter",
name: strings.followOnX(),
description: strings.followOnXDesc(),
icon: "twitter",
modifer: () => {
Linking.openURL("https://twitter.com/notesnook").catch(() => {
/* empty */
});
}
},
{
id: "join-discord",
name: strings.joinDiscord(),
icon: "discord",
modifer: async () => {
Linking.openURL("https://discord.gg/zQBK97EE22").catch(() => {
/* empty */
});
},
description: strings.joinDiscordDesc()
}
]
},
{
id: "legal",
name: strings.legal(),
sections: [
{
id: "tos",
name: strings.tos(),
icon: "briefcase-outline",
modifer: async () => {
try {
await Linking.openURL("https://notesnook.com/tos");
} catch (e) {
console.error(e);
}
},
description: strings.tosDesc()
},
{
id: "privacy-policy",
name: strings.privacyPolicy(),
icon: "shield-outline",
modifer: async () => {
try {
await Linking.openURL("https://notesnook.com/privacy");
} catch (e) {
console.error(e);
}
},
description: strings.privacyPolicyDesc()
},
{
id: "licenses",
name: strings.licenses(),
type: "screen",
component: "licenses",
description: strings.ossLibs(),
icon: "open-source-initiative"
}
]
},
{
id: "about",
name: strings.about(),
sections: [
{
id: "download",
name: strings.downloadOnDesktop(),
icon: "monitor",
modifer: async () => {
try {
await Linking.openURL("https://notesnook.com/downloads");
} catch (e) {
console.error(e);
}
},
description: strings.downloadOnDesktopDesc()
},
{
id: "roadmap",
name: strings.roadmap(),
icon: "chart-timeline",
modifer: async () => {
try {
await Linking.openURL("https://notesnook.com/roadmap/");
} catch (e) {
console.error(e);
}
},
description: strings.roadmapDesc()
},
{
id: "check-for-updates",
name: strings.checkForUpdates(),
icon: "cellphone-arrow-down",
description: strings.checkForUpdatesDesc(),
modifer: async () => {
presentSheet({
//@ts-ignore // Migrate to ts
component: (ref) => <Update fwdRef={ref} />
});
}
},
{
id: "app-version",
name: strings.appVersion(),
icon: "alpha-v",
modifer: async () => {
try {
await Linking.openURL("https://notesnook.com");
} catch (e) {
console.error(e);
}
},
description: getVersion()
}
]
}
];