diff --git a/apps/mobile/app/components/dialog/functions.ts b/apps/mobile/app/components/dialog/functions.ts
index 4798c5986..25d643895 100644
--- a/apps/mobile/app/components/dialog/functions.ts
+++ b/apps/mobile/app/components/dialog/functions.ts
@@ -17,10 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import { KeyboardTypeOptions } from "react-native";
+import { KeyboardTypeOptions, TextInput } from "react-native";
import { eSendEvent } from "../../services/event-manager";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
import { ButtonProps } from "../ui/button";
+import { FieldValidator, FormRef } from "../ui/input/form-input";
+import { RefObject } from "react";
export type DialogInfo = {
title?: string;
@@ -43,6 +45,18 @@ export type DialogInfo = {
| "errorShade";
icon?: string;
paragraphColor: string;
+ form?: {
+ formRef: FormRef;
+ items: {
+ name: string;
+ placeholder: string;
+ label?: string;
+ validators: FieldValidator[];
+ defaultValue?: string;
+ ref: RefObject;
+ }[];
+ onFormSubmit?: (form: FormRef) => Promise;
+ };
input: boolean;
inputPlaceholder: string;
defaultValue: string;
diff --git a/apps/mobile/app/components/dialog/index.tsx b/apps/mobile/app/components/dialog/index.tsx
index a9cff4864..9e8db457e 100644
--- a/apps/mobile/app/components/dialog/index.tsx
+++ b/apps/mobile/app/components/dialog/index.tsx
@@ -18,7 +18,13 @@ along with this program. If not, see .
*/
import { useThemeColors } from "@notesnook/theme";
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, {
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+ RefObject
+} from "react";
import { TextInput, View, ViewStyle } from "react-native";
import { DDS } from "../../services/device-detection";
import {
@@ -34,6 +40,7 @@ import { sleep } from "../../utils/time";
import { Toast } from "../toast";
import { Button } from "../ui/button";
import Input from "../ui/input";
+import { FormInput, type FormRef } from "../ui/input/form-input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import BaseDialog from "./base-dialog";
@@ -53,9 +60,34 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
});
const inputRef = useRef(null);
const [dialogInfo, setDialogInfo] = useState();
+ const formRef = useRef(dialogInfo?.form?.formRef);
+ formRef.current = dialogInfo?.form?.formRef;
const onPressPositive = async () => {
- if (dialogInfo?.positivePress) {
+ // Handle form submission if form is available
+ if (dialogInfo?.form && formRef.current) {
+ inputRef.current?.blur();
+ setLoading(true);
+ try {
+ const isValid = await formRef.current.validate();
+ if (!isValid) {
+ setLoading(false);
+ return;
+ }
+
+ if (dialogInfo.form.onFormSubmit) {
+ const result = await dialogInfo.form.onFormSubmit(formRef.current);
+ if (result === false) {
+ setLoading(false);
+ return;
+ }
+ }
+ } catch (e) {
+ /** Empty */
+ }
+ setLoading(false);
+ } else if (dialogInfo?.positivePress) {
+ // Handle old input-based submission
inputRef.current?.blur();
setLoading(true);
let result = false;
@@ -76,6 +108,7 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
setChecked(false);
values.current.inputValue = undefined;
+ formRef.current = undefined;
setVisible(false);
};
@@ -85,6 +118,7 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
if (data.context !== context) return;
setDialogInfo(data);
setChecked(data.check?.defaultValue);
+ formRef.current = data?.form?.formRef;
values.current.inputValue = data.defaultValue;
setVisible(true);
},
@@ -94,6 +128,7 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
const hide = React.useCallback(() => {
setChecked(false);
values.current.inputValue = undefined;
+ formRef.current = undefined;
setVisible(false);
setDialogInfo(undefined);
dialogInfo?.onClose?.();
@@ -134,19 +169,30 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
? false
: dialogInfo.statusBarTranslucent
}
- bounce={!dialogInfo.input}
+ bounce={!dialogInfo.input && !dialogInfo.form}
closeOnTouch={!dialogInfo.disableBackdropClosing}
background={dialogInfo.background}
transparent={
dialogInfo.transparent === undefined ? true : dialogInfo.transparent
}
onShow={async () => {
- if (dialogInfo.input) {
+ if (dialogInfo.input && !dialogInfo.form) {
inputRef.current?.setNativeProps({
text: dialogInfo.defaultValue
});
await sleep(300);
inputRef.current?.focus();
+ } else if (dialogInfo.form) {
+ const items = dialogInfo.form?.items;
+ const firstItem = items[0];
+ for (const item of items) {
+ if (item.defaultValue) {
+ item.ref.current?.setNativeProps({
+ text: dialogInfo.defaultValue
+ });
+ }
+ }
+ firstItem?.ref?.current?.focus();
}
}}
visible={true}
@@ -170,7 +216,36 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
/>
- {dialogInfo.input ? (
+ {dialogInfo.form ? (
+
+ {dialogInfo.form.items.map((item, index) => (
+ }
+ validators={item.validators}
+ defaultValue={item.defaultValue}
+ secureTextEntry={dialogInfo.secureTextEntry}
+ onSubmitEditing={() => {
+ const nextItem = dialogInfo?.form?.items?.[index + 1];
+ if (nextItem) {
+ nextItem?.ref.current?.focus();
+ } else {
+ onPressPositive();
+ }
+ }}
+ />
+ ))}
+
+ ) : dialogInfo.input ? (
{
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
- //defaultValue={dialogInfo.defaultValue}
+ defaultValue={dialogInfo.defaultValue}
onSubmit={() => {
onPressPositive();
}}
@@ -237,7 +312,10 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
{
+ Walkthrough.present("prouser", false, true);
+ }, 500);
}
await PremiumService.setPremiumStatus();
useMessageStore.getState().setAnnouncement();
diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx
index c2369337c..3a8089e2b 100644
--- a/apps/mobile/app/screens/settings/settings-data.tsx
+++ b/apps/mobile/app/screens/settings/settings-data.tsx
@@ -31,9 +31,10 @@ import dayjs from "dayjs";
import React from "react";
import { Appearance, Linking, Platform } from "react-native";
import { getVersion } from "react-native-device-info";
+import { TextInput } from "react-native-gesture-handler";
import * as RNIap from "react-native-iap";
-import ScreenGuardModule from "react-native-screenguard";
import { DatabaseLogger, db } from "../../common/database";
+import { MMKV } from "../../common/database/mmkv";
import filesystem from "../../common/filesystem";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
@@ -42,43 +43,45 @@ 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 {
+ createFormRef,
+ validators
+} from "../../components/ui/input/form-input";
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,
+ VaultRequestType,
eSendEvent,
eSubscribeEvent,
openVault,
- presentSheet,
- VaultRequestType
+ 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 { clearAllStores } from "../../stores";
+import { refreshAllStores } from "../../stores/create-db-collection-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
+import { EDITOR_LINE_HEIGHT } from "../../utils/constants";
import {
eAfterSync,
eCloseSheet,
eOpenRecoveryKeyDialog
} from "../../utils/events";
-import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time";
+import { resetTabStore } from "../editor/tiptap/use-tab-store";
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";
-import { EDITOR_LINE_HEIGHT } from "../../utils/constants";
-import { MMKV } from "../../common/database/mmkv";
-import { resetTabStore } from "../editor/tiptap/use-tab-store";
-import { clearAllStores } from "../../stores";
-import { refreshAllStores } from "../../stores/create-db-collection-store";
export const settingsGroups: SettingSection[] = [
{
@@ -234,18 +237,29 @@ export const settingsGroups: SettingSection[] = [
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"
- });
- });
- }
+ form: {
+ formRef: createFormRef({
+ code: ""
+ }),
+ items: [
+ {
+ name: "code",
+ placeholder: strings.code(),
+ ref: React.createRef(),
+ validators: [validators.required(strings.giftCodeRequired())]
+ }
+ ],
+ onFormSubmit: async (form) => {
+ try {
+ await db.subscriptions.redeemCode(form.getValue("code"));
+ return true;
+ } catch (e) {
+ form.setError("code", (e as Error).message);
+ return false;
+ }
+ }
+ },
+ positiveText: strings.redeem()
});
}
},
diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po
index a16f04684..311ee8c33 100644
--- a/packages/intl/locale/en.po
+++ b/packages/intl/locale/en.po
@@ -3101,6 +3101,10 @@ msgstr "Getting information"
msgid "Getting recovery codes"
msgstr "Getting recovery codes"
+#: src/strings.ts:2663
+msgid "Gift code required"
+msgstr "Gift code required"
+
#: src/strings.ts:2172
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr "GNU GENERAL PUBLIC LICENSE Version 3"
diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po
index 2d924d193..0f076d21c 100644
--- a/packages/intl/locale/pseudo-LOCALE.po
+++ b/packages/intl/locale/pseudo-LOCALE.po
@@ -3083,6 +3083,10 @@ msgstr ""
msgid "Getting recovery codes"
msgstr ""
+#: src/strings.ts:2663
+msgid "Gift code required"
+msgstr ""
+
#: src/strings.ts:2172
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr ""
diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts
index eca1169f3..0b5621788 100644
--- a/packages/intl/src/strings.ts
+++ b/packages/intl/src/strings.ts
@@ -2659,5 +2659,6 @@ Use this if changes from other devices are not appearing on this device. This wi
t`Value must be between ${min} and ${max}`,
passwordRequired: () => t`Password required`,
confirmPasswordRequired: () => t`Confirm password required`,
- enterAValidEmailAddress: () => t`Please enter a valid email address`
+ enterAValidEmailAddress: () => t`Please enter a valid email address`,
+ giftCodeRequired: () => t`Gift code required`
};