mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
mobile: initial pricing changes
This commit is contained in:
@@ -25,7 +25,6 @@ import { eSendEvent, presentSheet } from "../../services/event-manager";
|
|||||||
import { eCloseAnnouncementDialog } from "../../utils/events";
|
import { eCloseAnnouncementDialog } from "../../utils/events";
|
||||||
import { AppFontSize } from "../../utils/size";
|
import { AppFontSize } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import { PricingPlans } from "../premium/pricing-plans";
|
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { allowedOnPlatform, getStyle } from "./functions";
|
import { allowedOnPlatform, getStyle } from "./functions";
|
||||||
@@ -45,18 +44,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
Linking.openURL(item.data).catch(() => {
|
Linking.openURL(item.data).catch(() => {
|
||||||
/* empty */
|
/* empty */
|
||||||
});
|
});
|
||||||
} else if (item.type === "promo") {
|
|
||||||
presentSheet({
|
|
||||||
component: (
|
|
||||||
<PricingPlans
|
|
||||||
marginTop={1}
|
|
||||||
promo={{
|
|
||||||
promoCode: item.data,
|
|
||||||
text: item.title
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export const AuthHeader = (props: { welcome?: boolean }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -17,101 +17,27 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { View } from "react-native";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import Navigation from "../../services/navigation";
|
|
||||||
import { Toast } from "../toast";
|
import { Toast } from "../toast";
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import { IconButton } from "../ui/icon-button";
|
|
||||||
import { AuthMode, initialAuthMode } from "./common";
|
import { AuthMode, initialAuthMode } from "./common";
|
||||||
import { Login } from "./login";
|
import { Login } from "./login";
|
||||||
import { Signup } from "./signup";
|
import { Signup } from "./signup";
|
||||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
|
||||||
|
|
||||||
const Auth = ({ navigation, route }) => {
|
const Auth = ({ navigation, route }) => {
|
||||||
const [currentAuthMode, setCurrentAuthMode] = useState(
|
const [currentAuthMode, setCurrentAuthMode] = useState(
|
||||||
route?.params?.mode || AuthMode.login
|
route?.params?.mode || AuthMode.login
|
||||||
);
|
);
|
||||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
|
||||||
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
||||||
useNavigationFocus(navigation, {});
|
useNavigationFocus(navigation, {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<SafeAreaView
|
||||||
<View
|
style={{ flex: 1, backgroundColor: colors.primary.background }}
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
paddingTop: insets.top,
|
|
||||||
top: 0,
|
|
||||||
zIndex: 999,
|
|
||||||
backgroundColor:
|
|
||||||
deviceMode === "mobile" ? colors.secondary.background : null,
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
width: "100%",
|
|
||||||
height: 50,
|
|
||||||
justifyContent:
|
|
||||||
initialAuthMode.current !== AuthMode.welcomeSignup
|
|
||||||
? "space-between"
|
|
||||||
: "flex-end"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
|
||||||
<IconButton
|
|
||||||
name="arrow-left"
|
|
||||||
onPress={() => {
|
|
||||||
if (initialAuthMode.current === 2) {
|
|
||||||
Navigation.replace("FluidPanelsView");
|
|
||||||
} else {
|
|
||||||
Navigation.goBack();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
color={colors.primary.paragraph}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
|
|
||||||
<Button
|
|
||||||
title={strings.skip()}
|
|
||||||
onPress={() => {
|
|
||||||
if (initialAuthMode.current === 2) {
|
|
||||||
Navigation.replace("FluidPanelsView");
|
|
||||||
setTimeout(() => {
|
|
||||||
Navigation.resetRootState();
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
Navigation.goBack();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
iconSize={16}
|
|
||||||
type="plain"
|
|
||||||
iconPosition="right"
|
|
||||||
icon="chevron-right"
|
|
||||||
height={25}
|
|
||||||
iconStyle={{
|
|
||||||
marginTop: 2
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{currentAuthMode !== AuthMode.login ? (
|
{currentAuthMode !== AuthMode.login ? (
|
||||||
<Signup
|
<Signup
|
||||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||||
@@ -126,7 +52,7 @@ const Auth = ({ navigation, route }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Toast context="local" />
|
<Toast context="local" />
|
||||||
</View>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,15 @@ import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
|||||||
import { SheetManager } from "react-native-actions-sheet";
|
import { SheetManager } from "react-native-actions-sheet";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
|
import Navigation from "../../services/navigation";
|
||||||
|
import PremiumService from "../../services/premium";
|
||||||
import Sync from "../../services/sync";
|
import Sync from "../../services/sync";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { eUserLoggedIn } from "../../utils/events";
|
import { eUserLoggedIn } from "../../utils/events";
|
||||||
import { AppFontSize } from "../../utils/size";
|
import { AppFontSize } from "../../utils/size";
|
||||||
|
import { DefaultAppStyles } from "../../utils/styles";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import SheetProvider from "../sheet-provider";
|
import { Dialog } from "../dialog";
|
||||||
import { Progress } from "../sheets/progress";
|
import { Progress } from "../sheets/progress";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
@@ -37,9 +40,8 @@ import Heading from "../ui/typography/heading";
|
|||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { hideAuth } from "./common";
|
import { hideAuth } from "./common";
|
||||||
import { ForgotPassword } from "./forgot-password";
|
import { ForgotPassword } from "./forgot-password";
|
||||||
|
import { AuthHeader } from "./header";
|
||||||
import { useLogin } from "./use-login";
|
import { useLogin } from "./use-login";
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
import { Dialog } from "../dialog";
|
|
||||||
|
|
||||||
const LoginSteps = {
|
const LoginSteps = {
|
||||||
emailAuth: 1,
|
emailAuth: 1,
|
||||||
@@ -62,15 +64,21 @@ export const Login = ({ changeMode }) => {
|
|||||||
setError,
|
setError,
|
||||||
login
|
login
|
||||||
} = useLogin(async () => {
|
} = useLogin(async () => {
|
||||||
hideAuth();
|
|
||||||
eSendEvent(eUserLoggedIn, true);
|
eSendEvent(eUserLoggedIn, true);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
Progress.present();
|
hideAuth();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!useUserStore.getState().syncing) {
|
if (!useUserStore.getState().syncing) {
|
||||||
Sync.run("global", false, "full");
|
Sync.run("global", false, "full");
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
if (!PremiumService.get()) {
|
||||||
|
Navigation.navigate("PayWall", {
|
||||||
|
context: "logged-in"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Progress.present();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const { width, height } = useWindowDimensions();
|
const { width, height } = useWindowDimensions();
|
||||||
const isTablet = width > 600;
|
const isTablet = width > 600;
|
||||||
@@ -89,6 +97,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<AuthHeader />
|
||||||
<ForgotPassword />
|
<ForgotPassword />
|
||||||
<Dialog context="two_factor_verify" />
|
<Dialog context="two_factor_verify" />
|
||||||
<View
|
<View
|
||||||
@@ -105,7 +114,6 @@ export const Login = ({ changeMode }) => {
|
|||||||
style={{
|
style={{
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
paddingHorizontal: DefaultAppStyles.GAP,
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
borderBottomWidth: 0.8,
|
borderBottomWidth: 0.8,
|
||||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||||
borderBottomColor: colors.primary.border,
|
borderBottomColor: colors.primary.border,
|
||||||
@@ -165,7 +173,8 @@ export const Login = ({ changeMode }) => {
|
|||||||
: "99.9%",
|
: "99.9%",
|
||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
paddingHorizontal: DefaultAppStyles.GAP
|
paddingHorizontal: DefaultAppStyles.GAP,
|
||||||
|
gap: DefaultAppStyles.GAP_VERTICAL
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
@@ -179,6 +188,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
validationType="email"
|
validationType="email"
|
||||||
|
marginBottom={0}
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
errorMessage={strings.emailInvalid()}
|
errorMessage={strings.emailInvalid()}
|
||||||
@@ -234,11 +244,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View
|
<View>
|
||||||
style={{
|
|
||||||
marginTop: 25
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -246,10 +252,11 @@ export const Login = ({ changeMode }) => {
|
|||||||
login();
|
login();
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: 250
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={!loading ? strings.continue() : null}
|
title={!loading ? strings.continue() : null}
|
||||||
|
fontSize={AppFontSize.md}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{step === LoginSteps.passwordAuth && (
|
{step === LoginSteps.passwordAuth && (
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ import { ToastManager } from "../../services/event-manager";
|
|||||||
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { openLinkInBrowser } from "../../utils/functions";
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
import { AppFontSize, SIZE } from "../../utils/size";
|
import { AppFontSize } from "../../utils/size";
|
||||||
|
import { DefaultAppStyles } from "../../utils/styles";
|
||||||
import { Loading } from "../loading";
|
import { Loading } from "../loading";
|
||||||
import { PaywallComponent } from "../premium/component";
|
import { PaywallComponent } from "../premium/paywall";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
@@ -38,6 +39,7 @@ import Paragraph from "../ui/typography/paragraph";
|
|||||||
import { hideAuth } from "./common";
|
import { hideAuth } from "./common";
|
||||||
import { AuthHeader } from "./header";
|
import { AuthHeader } from "./header";
|
||||||
import { SignupContext } from "./signup-context";
|
import { SignupContext } from "./signup-context";
|
||||||
|
import Navigation from "../../services/navigation";
|
||||||
|
|
||||||
const SignupSteps = {
|
const SignupSteps = {
|
||||||
signup: 0,
|
signup: 0,
|
||||||
@@ -60,7 +62,6 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||||
const { width, height } = useWindowDimensions();
|
const { width, height } = useWindowDimensions();
|
||||||
const isTablet = width > 600;
|
const isTablet = width > 600;
|
||||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
|
||||||
const validateInfo = () => {
|
const validateInfo = () => {
|
||||||
if (!password.current || !email.current || !confirmPassword.current) {
|
if (!password.current || !email.current || !confirmPassword.current) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -89,7 +90,9 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
setLastSynced(await db.lastSynced());
|
setLastSynced(await db.lastSynced());
|
||||||
clearMessage();
|
clearMessage();
|
||||||
setEmailVerifyMessage();
|
setEmailVerifyMessage();
|
||||||
setCurrentStep(SignupSteps.selectPlan);
|
Navigation.navigate("PayWall", {
|
||||||
|
canGoBack: false
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setCurrentStep(SignupSteps.signup);
|
setCurrentStep(SignupSteps.signup);
|
||||||
@@ -138,17 +141,15 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
style={{
|
style={{
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
backgroundColor: colors.secondary.background,
|
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||||
marginBottom: 20,
|
|
||||||
borderBottomWidth: 0.8,
|
borderBottomWidth: 0.8,
|
||||||
borderBottomColor: colors.primary.border,
|
borderBottomColor: colors.primary.border,
|
||||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
alignSelf: isTablet ? "center" : undefined,
|
||||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
borderWidth: isTablet ? 1 : null,
|
||||||
borderColor:
|
borderColor: isTablet ? colors.primary.border : null,
|
||||||
deviceMode !== "mobile" ? colors.primary.border : null,
|
borderRadius: isTablet ? 20 : null,
|
||||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
marginTop: isTablet ? 50 : null,
|
||||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
width: !isTablet ? null : "50%",
|
||||||
width: deviceMode === "mobile" ? null : "50%",
|
|
||||||
minHeight: height * 0.25
|
minHeight: height * 0.25
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -182,7 +183,7 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
marginBottom: 25,
|
marginBottom: 25,
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
size={SIZE.xxl}
|
size={AppFontSize.xxl}
|
||||||
>
|
>
|
||||||
{strings.createAccount()}
|
{strings.createAccount()}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -268,7 +269,7 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
signup();
|
signup();
|
||||||
}}
|
}}
|
||||||
fontSize={SIZE.md}
|
fontSize={AppFontSize.md}
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -285,12 +286,12 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.xs + 1}
|
size={AppFontSize.xs + 1}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{strings.alreadyHaveAccount()}{" "}
|
{strings.alreadyHaveAccount()}{" "}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.xs + 1}
|
size={AppFontSize.xs + 1}
|
||||||
style={{ color: colors.primary.accent }}
|
style={{ color: colors.primary.accent }}
|
||||||
>
|
>
|
||||||
{strings.login()}
|
{strings.login()}
|
||||||
@@ -299,9 +300,15 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: DefaultAppStyles.GAP
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 25
|
marginBottom: 25,
|
||||||
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
size={AppFontSize.xxs}
|
size={AppFontSize.xxs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
@@ -324,7 +331,10 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
size={AppFontSize.xxs}
|
size={AppFontSize.xxs}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
openLinkInBrowser("https://notesnook.com/privacy", colors);
|
openLinkInBrowser(
|
||||||
|
"https://notesnook.com/privacy",
|
||||||
|
colors
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: "underline"
|
textDecorationLine: "underline"
|
||||||
@@ -337,27 +347,16 @@ export const Signup = ({ changeMode, welcome }) => {
|
|||||||
{strings.signupAgreement[4]()}
|
{strings.signupAgreement[4]()}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
</>
|
</>
|
||||||
) : currentStep === SignupSteps.createAccount ? (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Loading
|
<Loading
|
||||||
title={"Setting up your account..."}
|
title={"Setting up your account..."}
|
||||||
description="Your account is almost ready, please wait..."
|
description="Your account is almost ready, please wait..."
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<PaywallComponent
|
|
||||||
close={() => {
|
|
||||||
hideAuth();
|
|
||||||
}}
|
|
||||||
setupAccount={() => {
|
|
||||||
setCurrentStep(SignupSteps.createAccount);
|
|
||||||
}}
|
|
||||||
isModal={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</SignupContext.Provider>
|
</SignupContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { strings } from "@notesnook/intl";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { TextInput } from "react-native";
|
import { TextInput } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -27,7 +28,6 @@ import SettingsService from "../../services/settings";
|
|||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { eCloseSimpleDialog } from "../../utils/events";
|
import { eCloseSimpleDialog } from "../../utils/events";
|
||||||
import TwoFactorVerification from "./two-factor";
|
import TwoFactorVerification from "./two-factor";
|
||||||
import { strings } from "@notesnook/intl";
|
|
||||||
|
|
||||||
export const LoginSteps = {
|
export const LoginSteps = {
|
||||||
emailAuth: 1,
|
emailAuth: 1,
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ import ResultDialog from "../dialogs/result";
|
|||||||
import { VaultDialog } from "../dialogs/vault";
|
import { VaultDialog } from "../dialogs/vault";
|
||||||
import ImagePreview from "../image-preview";
|
import ImagePreview from "../image-preview";
|
||||||
import MergeConflicts from "../merge-conflicts";
|
import MergeConflicts from "../merge-conflicts";
|
||||||
import PremiumDialog from "../premium";
|
|
||||||
import { Expiring } from "../premium/expiring";
|
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
import RateAppSheet from "../sheets/rate-app";
|
import RateAppSheet from "../sheets/rate-app";
|
||||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||||
@@ -46,7 +44,6 @@ const DialogProvider = () => {
|
|||||||
<AppLockPassword />
|
<AppLockPassword />
|
||||||
<LoadingDialog />
|
<LoadingDialog />
|
||||||
<Dialog context="global" />
|
<Dialog context="global" />
|
||||||
<PremiumDialog colors={colors} />
|
|
||||||
<AuthModal colors={colors} />
|
<AuthModal colors={colors} />
|
||||||
<MergeConflicts />
|
<MergeConflicts />
|
||||||
<RecoveryKeySheet colors={colors} />
|
<RecoveryKeySheet colors={colors} />
|
||||||
@@ -56,7 +53,6 @@ const DialogProvider = () => {
|
|||||||
<VaultDialog colors={colors} />
|
<VaultDialog colors={colors} />
|
||||||
<RateAppSheet />
|
<RateAppSheet />
|
||||||
<ImagePreview />
|
<ImagePreview />
|
||||||
<Expiring />
|
|
||||||
<AnnouncementDialog />
|
<AnnouncementDialog />
|
||||||
<SessionExpired />
|
<SessionExpired />
|
||||||
<PDFPreview />
|
<PDFPreview />
|
||||||
|
|||||||
@@ -115,23 +115,28 @@ const Intro = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: colors.primary.background
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
testID="notesnook.splashscreen"
|
testID="notesnook.splashscreen"
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
flex: 1
|
||||||
backgroundColor: colors.primary.background
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.primary.border,
|
borderBottomColor: colors.primary.border,
|
||||||
paddingTop: insets.top + 10,
|
paddingTop: insets.top + 10,
|
||||||
paddingBottom: insets.top + 10,
|
paddingBottom: insets.top + 10,
|
||||||
minHeight: height * 0.7 - (insets.top + insets.bottom)
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
isTablet && {
|
isTablet && {
|
||||||
width: width / 2,
|
width: width / 2,
|
||||||
@@ -162,18 +167,23 @@ const Intro = () => {
|
|||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
minHeight: height * 0.3
|
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||||
|
paddingHorizontal: DefaultAppStyles.GAP,
|
||||||
|
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
|
||||||
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
width={250}
|
style={{
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SettingsService.set({ introCompleted: true });
|
// SettingsService.set({ introCompleted: true });
|
||||||
Navigation.push("Auth", {
|
Navigation.push("Auth", {
|
||||||
mode: AuthMode.welcomeSignup
|
mode: AuthMode.welcomeSignup
|
||||||
});
|
});
|
||||||
@@ -184,17 +194,23 @@ const Intro = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
width="100%"
|
style={{
|
||||||
title={"I already have an account"}
|
width: "100%"
|
||||||
|
}}
|
||||||
|
title={strings.iAlreadyHaveAnAccount()}
|
||||||
type="secondary"
|
type="secondary"
|
||||||
|
fontSize={AppFontSize.md}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
SettingsService.set({
|
// SettingsService.set({
|
||||||
introCompleted: true
|
// introCompleted: true
|
||||||
|
// });
|
||||||
|
Navigation.push("Auth", {
|
||||||
|
mode: AuthMode.login
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { FeatureBlock } from "./feature";
|
|
||||||
import { ScrollView } from "react-native-actions-sheet";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const CompactFeatures = ({
|
|
||||||
vertical,
|
|
||||||
features = [],
|
|
||||||
maxHeight = 600,
|
|
||||||
scrollRef
|
|
||||||
}) => {
|
|
||||||
let data = vertical
|
|
||||||
? features
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
highlight: "Everything",
|
|
||||||
content: "in basic",
|
|
||||||
icon: "emoticon-wink"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Unlimited",
|
|
||||||
content: "notebooks",
|
|
||||||
icon: "notebook"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "File & image",
|
|
||||||
content: "attachments",
|
|
||||||
icon: "attachment"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Instant",
|
|
||||||
content: "syncing",
|
|
||||||
icon: "sync"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Private",
|
|
||||||
content: "vault",
|
|
||||||
icon: "shield"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Daily, weekly & monthly",
|
|
||||||
content: "recurring reminders",
|
|
||||||
icon: "bell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Rich text",
|
|
||||||
content: "editing",
|
|
||||||
icon: "square-edit-outline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "PDF & markdown",
|
|
||||||
content: "exports",
|
|
||||||
icon: "file"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Encrypted",
|
|
||||||
content: "backups",
|
|
||||||
icon: "backup-restore"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
horizontal={!vertical}
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
maxHeight: maxHeight,
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.map((item) => (
|
|
||||||
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React, { useEffect, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { usePricing } from "../../hooks/use-pricing";
|
|
||||||
import {
|
|
||||||
eSendEvent,
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent
|
|
||||||
} from "../../services/event-manager";
|
|
||||||
import PremiumService from "../../services/premium";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import {
|
|
||||||
eOpenPremiumDialog,
|
|
||||||
eOpenResultDialog,
|
|
||||||
eOpenTrialEndingDialog
|
|
||||||
} from "../../utils/events";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import { sleep } from "../../utils/time";
|
|
||||||
import BaseDialog from "../dialog/base-dialog";
|
|
||||||
import DialogContainer from "../dialog/dialog-container";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import Seperator from "../ui/seperator";
|
|
||||||
import Heading from "../ui/typography/heading";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { CompactFeatures } from "./compact-features";
|
|
||||||
import { Offer } from "./offer";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const Expiring = () => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [status, setStatus] = useState({
|
|
||||||
title: "Your trial is ending soon",
|
|
||||||
offer: "Get 30% off",
|
|
||||||
extend: true
|
|
||||||
});
|
|
||||||
const pricing = usePricing("yearly");
|
|
||||||
const promo =
|
|
||||||
status.offer && pricing?.info
|
|
||||||
? {
|
|
||||||
promoCode:
|
|
||||||
pricing?.info?.discount > 30
|
|
||||||
? pricing.info.sku
|
|
||||||
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
|
||||||
text: `GET ${
|
|
||||||
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
|
||||||
}% OFF on yearly`,
|
|
||||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eSubscribeEvent(eOpenTrialEndingDialog, open);
|
|
||||||
return () => {
|
|
||||||
eUnSubscribeEvent(eOpenTrialEndingDialog, open);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const open = (status) => {
|
|
||||||
setStatus(status);
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
visible && (
|
|
||||||
<BaseDialog
|
|
||||||
onRequestClose={() => {
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContainer>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
textBreakStrategy="balanced"
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
paddingTop: 18
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{status.title}
|
|
||||||
</Heading>
|
|
||||||
<Seperator />
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{status.offer ? (
|
|
||||||
<>
|
|
||||||
<Offer padding={20} off={promo?.discount || 30} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Paragraph
|
|
||||||
textBreakStrategy="balanced"
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
paddingTop: 0,
|
|
||||||
paddingBottom: 20
|
|
||||||
}}
|
|
||||||
size={AppFontSize.md + 2}
|
|
||||||
>
|
|
||||||
Upgrade now to continue using all the pro features after
|
|
||||||
your trial ends
|
|
||||||
</Paragraph>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CompactFeatures />
|
|
||||||
|
|
||||||
<Paragraph
|
|
||||||
onPress={async () => {
|
|
||||||
setVisible(false);
|
|
||||||
await sleep(300);
|
|
||||||
eSendEvent(eOpenPremiumDialog, promo);
|
|
||||||
}}
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline",
|
|
||||||
color: colors.secondary.paragraph,
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{"See what's included in Basic & Pro plans"}
|
|
||||||
</Paragraph>
|
|
||||||
|
|
||||||
<Seperator />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
width: "100%",
|
|
||||||
borderBottomRightRadius: 10,
|
|
||||||
borderBottomLeftRadius: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="transparent"
|
|
||||||
title="Subscribe now"
|
|
||||||
onPress={async () => {
|
|
||||||
setVisible(false);
|
|
||||||
await sleep(300);
|
|
||||||
PremiumService.sheet(
|
|
||||||
null,
|
|
||||||
promo?.discount > 30 ? null : promo
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
fontSize={AppFontSize.md + 2}
|
|
||||||
style={{
|
|
||||||
marginBottom: status.extend ? 0 : 10,
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP * 2
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{status.extend && (
|
|
||||||
<Button
|
|
||||||
type="plain"
|
|
||||||
title="Not sure yet? Extend trial for 7 days"
|
|
||||||
textStyle={{
|
|
||||||
textDecorationLine: "underline"
|
|
||||||
}}
|
|
||||||
onPress={async () => {
|
|
||||||
setVisible(false);
|
|
||||||
await sleep(300);
|
|
||||||
eSendEvent(eOpenResultDialog, {
|
|
||||||
title: "Your trial has been extended",
|
|
||||||
paragraph:
|
|
||||||
"Try out all features of Notesnook free for 7 more days. No limitations. No commitments.",
|
|
||||||
button: "Continue"
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
fontSize={AppFontSize.xs}
|
|
||||||
height={30}
|
|
||||||
style={{
|
|
||||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</DialogContainer>
|
|
||||||
</BaseDialog>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { Text, View } from "react-native";
|
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { ProTag } from "./pro-tag";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const FeatureBlock = ({
|
|
||||||
vertical,
|
|
||||||
highlight,
|
|
||||||
content,
|
|
||||||
icon,
|
|
||||||
pro,
|
|
||||||
proTagBg
|
|
||||||
}) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
|
|
||||||
return vertical ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
borderRadius: 10,
|
|
||||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
flexWrap: "wrap",
|
|
||||||
marginLeft: 5,
|
|
||||||
flexShrink: 1
|
|
||||||
}}
|
|
||||||
size={AppFontSize.sm}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 100,
|
|
||||||
justifyContent: "center",
|
|
||||||
padding: DefaultAppStyles.GAP_SMALL,
|
|
||||||
marginRight: 10,
|
|
||||||
borderRadius: defaultBorderRadius,
|
|
||||||
minWidth: 100
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon color={colors.primary.icon} name={icon} size={AppFontSize.xl} />
|
|
||||||
<Paragraph size={AppFontSize.md}>
|
|
||||||
<Text style={{ color: colors.primary.accent }}>{highlight}</Text>
|
|
||||||
{content ? "\n" + content : null}
|
|
||||||
</Paragraph>
|
|
||||||
|
|
||||||
{pro ? (
|
|
||||||
<>
|
|
||||||
<View style={{ height: 5 }} />
|
|
||||||
<ProTag width={50} size={AppFontSize.xs} background={proTagBg} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: 30,
|
|
||||||
height: 3,
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
borderRadius: 100,
|
|
||||||
backgroundColor: colors.primary.accent
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
/*
|
|
||||||
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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const features = [
|
|
||||||
{
|
|
||||||
title: "Focused on privacy",
|
|
||||||
detail:
|
|
||||||
"Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Zero ads",
|
|
||||||
content: "& zero trackers",
|
|
||||||
icon: "billboard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "On device",
|
|
||||||
content: "encryption",
|
|
||||||
icon: "cellphone"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Secure app",
|
|
||||||
content: "lock for all",
|
|
||||||
icon: "cellphone-lock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "100% end-to-end ",
|
|
||||||
content: "encrypted",
|
|
||||||
icon: "lock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Password protected",
|
|
||||||
content: "notes sharing",
|
|
||||||
icon: "file-lock"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "No limit on notes or devices",
|
|
||||||
detail:
|
|
||||||
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Attach files & images",
|
|
||||||
detail:
|
|
||||||
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
|
|
||||||
pro: true,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Bullet proof",
|
|
||||||
content: "encryption",
|
|
||||||
icon: "lock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "High quality",
|
|
||||||
content: "4k images",
|
|
||||||
icon: "image-multiple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "No monthly",
|
|
||||||
content: "storage limit",
|
|
||||||
icon: "harddisk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Generous 500 MB",
|
|
||||||
content: "max file size",
|
|
||||||
icon: "file-cabinet"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "No restriction",
|
|
||||||
content: "on file type",
|
|
||||||
icon: "file"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Cross platform Reminders",
|
|
||||||
detail: "Stay updated on all your upcoming tasks with reminders.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "One-time",
|
|
||||||
content: "reminders",
|
|
||||||
icon: "bell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Daily, weekly & monthly",
|
|
||||||
content: "reminders",
|
|
||||||
icon: "refresh",
|
|
||||||
pro: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Two-factor authentication",
|
|
||||||
detail:
|
|
||||||
"Improve account security & prevent intruders from accessing your notes",
|
|
||||||
info: "* 2FA via email is enabled by default for all users.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Email *",
|
|
||||||
icon: "bell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Authentication",
|
|
||||||
content: "app",
|
|
||||||
icon: "refresh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "SMS",
|
|
||||||
icon: "refresh",
|
|
||||||
pro: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Keep secrets always locked with private vault",
|
|
||||||
detail:
|
|
||||||
"An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Organize yourself in the best way",
|
|
||||||
detail:
|
|
||||||
"We offer multiple ways to keep you organized. The only limit is your imagination.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Unlimited",
|
|
||||||
content: "notebooks & tags*",
|
|
||||||
icon: "emoticon",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Organize",
|
|
||||||
content: "with colors",
|
|
||||||
icon: "palette",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Side menu",
|
|
||||||
content: "shortcuts",
|
|
||||||
icon: "link-variant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Pin note in",
|
|
||||||
content: "notifications",
|
|
||||||
icon: "pin",
|
|
||||||
platform: "android"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
info: "* Free users are limited to keeping 3 notebooks and 5 tags."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: "Instant sync",
|
|
||||||
detail:
|
|
||||||
"Seamlessly work from anywhere on any device. Every change is synced instantly to all your devices.",
|
|
||||||
info: "* Disable sync completely, turn off auto sync or disable editor realtime sync.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Sync to unlimited",
|
|
||||||
content: "devices",
|
|
||||||
icon: "cellphone"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Realtime",
|
|
||||||
content: "editor sync",
|
|
||||||
icon: "sync"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Granular sync",
|
|
||||||
content: "controls *",
|
|
||||||
icon: "sync-off"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Rich tools for rich editing",
|
|
||||||
detail:
|
|
||||||
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Basic formatting",
|
|
||||||
content: "and lists",
|
|
||||||
icon: "format-bold"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Checklists",
|
|
||||||
content: "& tables",
|
|
||||||
icon: "table",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Markdown",
|
|
||||||
content: "support",
|
|
||||||
icon: "language-markdown",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Personalized",
|
|
||||||
content: "editor toolbar",
|
|
||||||
icon: "gesture-tap-button",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Write notes from",
|
|
||||||
content: "notifications",
|
|
||||||
icon: "bell",
|
|
||||||
platform: "android"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Safe publishing to the Internet",
|
|
||||||
detail:
|
|
||||||
"Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Password protected",
|
|
||||||
content: "sharing",
|
|
||||||
icon: "send-lock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Self destruct",
|
|
||||||
content: "monographs",
|
|
||||||
icon: "bomb"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Export and take your notes anywhere",
|
|
||||||
pro: true,
|
|
||||||
detail:
|
|
||||||
"You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.",
|
|
||||||
info: "* Free users can export notes in well formatted plain text.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Export as ",
|
|
||||||
content: "Markdown",
|
|
||||||
icon: "language-markdown",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Export as",
|
|
||||||
content: "PDF",
|
|
||||||
icon: "file-pdf-box",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Export as",
|
|
||||||
content: "HTML",
|
|
||||||
icon: "language-html5",
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Export as",
|
|
||||||
content: "text",
|
|
||||||
icon: "clipboard-text-outline"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Backup & keep your notes safe",
|
|
||||||
detail:
|
|
||||||
"Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.",
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Backup",
|
|
||||||
content: "encryption",
|
|
||||||
icon: "backup-restore"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pro: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Personalize & make Notesnook your own",
|
|
||||||
detail:
|
|
||||||
"Change app themes to match your style. Custom themes are coming soon.",
|
|
||||||
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
highlight: "Automatic",
|
|
||||||
content: "dark mode",
|
|
||||||
icon: "theme-light-dark",
|
|
||||||
pro: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
highlight: "Change accent",
|
|
||||||
content: "color",
|
|
||||||
icon: "invert-colors",
|
|
||||||
pro: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
{
|
|
||||||
highlight: 'Private vault',
|
|
||||||
content: 'for notes',
|
|
||||||
icon: 'shield-lock'
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { ScrollView, View } from "react-native";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import Heading from "../ui/typography/heading";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { FeatureBlock } from "./feature";
|
|
||||||
import { ProTag } from "./pro-tag";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const Group = ({ item, index }) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
backgroundColor:
|
|
||||||
index % 2 !== 0
|
|
||||||
? colors.primary.background
|
|
||||||
: colors.secondary.background,
|
|
||||||
paddingVertical: 40
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item?.pro ? (
|
|
||||||
<ProTag
|
|
||||||
size={AppFontSize.sm}
|
|
||||||
background={
|
|
||||||
index % 2 === 0
|
|
||||||
? colors.primary.background
|
|
||||||
: colors.secondary.background
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Heading>{item.title}</Heading>
|
|
||||||
<Paragraph size={AppFontSize.md}>{item.detail}</Paragraph>
|
|
||||||
|
|
||||||
{item.features && (
|
|
||||||
<ScrollView
|
|
||||||
style={{
|
|
||||||
marginTop: DefaultAppStyles.GAP
|
|
||||||
}}
|
|
||||||
horizontal
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
>
|
|
||||||
{item.features?.map((item) => (
|
|
||||||
<FeatureBlock
|
|
||||||
key={item.detail}
|
|
||||||
{...item}
|
|
||||||
detail={item.detail}
|
|
||||||
pro={item.pro}
|
|
||||||
proTagBg={
|
|
||||||
index % 2 === 0
|
|
||||||
? colors.primary.background
|
|
||||||
: colors.secondary.background
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
)}
|
|
||||||
{item.info ? (
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
|
||||||
}}
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{item.info}
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React, { createRef } from "react";
|
|
||||||
import {
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent
|
|
||||||
} from "../../services/event-manager";
|
|
||||||
import { eClosePremiumDialog, eOpenPremiumDialog } from "../../utils/events";
|
|
||||||
import BaseDialog from "../dialog/base-dialog";
|
|
||||||
import { PaywallComponent } from "./component";
|
|
||||||
import { IconButton } from "../ui/icon-button";
|
|
||||||
|
|
||||||
class PremiumDialog extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
visible: false,
|
|
||||||
promo: null
|
|
||||||
};
|
|
||||||
this.actionSheetRef = createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
eSubscribeEvent(eOpenPremiumDialog, this.open);
|
|
||||||
eSubscribeEvent(eClosePremiumDialog, this.close);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
eUnSubscribeEvent(eOpenPremiumDialog, this.open);
|
|
||||||
eUnSubscribeEvent(eClosePremiumDialog, this.close);
|
|
||||||
}
|
|
||||||
|
|
||||||
open = (promoInfo) => {
|
|
||||||
this.setState({
|
|
||||||
visible: true,
|
|
||||||
promo: promoInfo
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
close = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: false,
|
|
||||||
promo: null
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onClose = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return !this.state.visible ? null : (
|
|
||||||
<BaseDialog
|
|
||||||
animation="slide"
|
|
||||||
bounce={false}
|
|
||||||
background={this.props.colors.primary.background}
|
|
||||||
onRequestClose={this.onClose}
|
|
||||||
>
|
|
||||||
<PaywallComponent
|
|
||||||
getRef={() => this.actionSheetRef}
|
|
||||||
promo={this.state.promo}
|
|
||||||
close={this.close}
|
|
||||||
/>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PremiumDialog;
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { Text } from "react-native";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
|
|
||||||
export const Offer = ({
|
|
||||||
off = "30",
|
|
||||||
text = "on yearly plan, offer ends soon",
|
|
||||||
padding = 0
|
|
||||||
}) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
paddingVertical: padding
|
|
||||||
}}
|
|
||||||
size={AppFontSize.xxxl}
|
|
||||||
>
|
|
||||||
GET {off}
|
|
||||||
<Text style={{ color: colors.primary.accent }}>%</Text> OFF!{"\n"}
|
|
||||||
<Paragraph color={colors.secondary.paragraph}>{text}</Paragraph>
|
|
||||||
</Paragraph>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -18,37 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
BackHandler,
|
||||||
Image,
|
Image,
|
||||||
|
NativeEventSubscription,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import * as RNIap from "react-native-iap";
|
import * as RNIap from "react-native-iap";
|
||||||
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import ToggleSwitch from "toggle-switch-react-native";
|
import ToggleSwitch from "toggle-switch-react-native";
|
||||||
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import usePricingPlans, { PricingPlan } from "../../hooks/use-pricing-plans";
|
import usePricingPlans, { PricingPlan } from "../../hooks/use-pricing-plans";
|
||||||
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { getElevationStyle } from "../../utils/elevation";
|
import { getElevationStyle } from "../../utils/elevation";
|
||||||
import { openLinkInBrowser } from "../../utils/functions";
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
import { SIZE } from "../../utils/size";
|
import { AppFontSize } from "../../utils/size";
|
||||||
import SheetProvider from "../sheet-provider";
|
import { DefaultAppStyles } from "../../utils/styles";
|
||||||
|
import { Header } from "../header";
|
||||||
import { BuyPlan } from "../sheets/buy-plan";
|
import { BuyPlan } from "../sheets/buy-plan";
|
||||||
import { Toast } from "../toast";
|
import { Toast } from "../toast";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { FeaturesList } from "./features-list";
|
import { FeaturesList } from "./features-list";
|
||||||
import { IconButton } from "../ui/icon-button";
|
|
||||||
type PaywallComponentProps = {
|
|
||||||
isModal?: boolean;
|
|
||||||
close?: () => void;
|
|
||||||
promo?: {
|
|
||||||
promoCode: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Steps = {
|
const Steps = {
|
||||||
select: 1,
|
select: 1,
|
||||||
@@ -56,20 +54,61 @@ const Steps = {
|
|||||||
finish: 3
|
finish: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PaywallComponent = ({
|
const PayWall = (props: NavigationProps<"PayWall">) => {
|
||||||
close,
|
const routeParams = props.route.params;
|
||||||
promo,
|
|
||||||
isModal
|
|
||||||
}: PaywallComponentProps) => {
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const pricingPlans = usePricingPlans({
|
const pricingPlans = usePricingPlans();
|
||||||
promoOffer: promo
|
|
||||||
});
|
|
||||||
const [annualBilling, setAnnualBilling] = useState(true);
|
const [annualBilling, setAnnualBilling] = useState(true);
|
||||||
const [step, setStep] = useState(Steps.select);
|
const [step, setStep] = useState(Steps.select);
|
||||||
|
const isFocused = useNavigationFocus(props.navigation, {
|
||||||
|
onBlur: () => true,
|
||||||
|
onFocus: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let listener: NativeEventSubscription;
|
||||||
|
if (isFocused) {
|
||||||
|
listener = BackHandler.addEventListener("hardwareBackPress", () => {
|
||||||
|
if (routeParams.context === "signup" && step === Steps.select)
|
||||||
|
return true;
|
||||||
|
if (step === Steps.buy) {
|
||||||
|
setStep(Steps.select);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
listener.remove();
|
||||||
|
};
|
||||||
|
}, [isFocused, step]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SafeAreaView
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.primary.background,
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{routeParams.context === "signup" && step === Steps.select ? null : (
|
||||||
|
<Header
|
||||||
|
canGoBack={true}
|
||||||
|
onLeftMenuButtonPress={() => {
|
||||||
|
if (step === Steps.buy) {
|
||||||
|
setStep(Steps.select);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.navigation.goBack();
|
||||||
|
}}
|
||||||
|
title={
|
||||||
|
step === Steps.buy
|
||||||
|
? pricingPlans.userCanRequestTrial
|
||||||
|
? `Try ${pricingPlans.currentPlan?.name} plan for free`
|
||||||
|
: `${pricingPlans.currentPlan?.name} plan`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{step === Steps.select ? (
|
{step === Steps.select ? (
|
||||||
<>
|
<>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -77,40 +116,20 @@ export const PaywallComponent = ({
|
|||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
gap: 12,
|
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||||
paddingBottom: 80
|
paddingBottom: 80
|
||||||
}}
|
}}
|
||||||
keyboardDismissMode="none"
|
keyboardDismissMode="none"
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
stickyHeaderIndices={[0]}
|
|
||||||
>
|
>
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
width: "100%",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
marginBottom: -12,
|
|
||||||
backgroundColor: colors.primary.background
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
style={{
|
|
||||||
alignSelf: "flex-start"
|
|
||||||
}}
|
|
||||||
onPress={() => close?.()}
|
|
||||||
name="chevron-left"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingTop: 100,
|
paddingTop: 100,
|
||||||
borderBottomColor: colors.primary.border,
|
borderBottomColor: colors.primary.border,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: DefaultAppStyles.GAP,
|
||||||
paddingBottom: 25,
|
paddingBottom: 25,
|
||||||
gap: 12
|
gap: DefaultAppStyles.GAP_VERTICAL
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -123,27 +142,27 @@ export const PaywallComponent = ({
|
|||||||
<Icon name="crown" size={30} color={colors.static.orange} />
|
<Icon name="crown" size={30} color={colors.static.orange} />
|
||||||
<Heading
|
<Heading
|
||||||
key="heading"
|
key="heading"
|
||||||
size={SIZE.xl}
|
size={AppFontSize.xl}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: "center"
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Notesnook{" "}
|
Notesnook{" "}
|
||||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
<Heading size={AppFontSize.xl} color={colors.primary.accent}>
|
||||||
Pro
|
Plans
|
||||||
</Heading>
|
</Heading>
|
||||||
</Heading>
|
</Heading>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Paragraph key="description" size={SIZE.md}>
|
<Paragraph key="description" size={AppFontSize.md}>
|
||||||
Ready to take the next step on your private note taking journey?
|
Ready to take the next step on your private note taking journey?
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
gap: 16,
|
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||||
paddingHorizontal: 16
|
paddingHorizontal: DefaultAppStyles.GAP
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -231,7 +250,7 @@ export const PaywallComponent = ({
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
size={SIZE.lg}
|
size={AppFontSize.lg}
|
||||||
>
|
>
|
||||||
Open Source
|
Open Source
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -269,7 +288,7 @@ export const PaywallComponent = ({
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
size={SIZE.lg}
|
size={AppFontSize.lg}
|
||||||
>
|
>
|
||||||
8.7K stars
|
8.7K stars
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -307,7 +326,7 @@ export const PaywallComponent = ({
|
|||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
size={SIZE.lg}
|
size={AppFontSize.lg}
|
||||||
>
|
>
|
||||||
Recommended by Privacy Guides
|
Recommended by Privacy Guides
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -436,6 +455,7 @@ After trying all the privacy security oriented note taking apps, for the price a
|
|||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: colors.primary.border,
|
borderTopColor: colors.primary.border,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
paddingBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -451,7 +471,11 @@ After trying all the privacy security oriented note taking apps, for the price a
|
|||||||
}
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (pricingPlans.currentPlan?.id === "free") {
|
if (pricingPlans.currentPlan?.id === "free") {
|
||||||
close?.();
|
if (routeParams.context === "signup") {
|
||||||
|
Navigation.replace("FluidPanelsView", {});
|
||||||
|
} else {
|
||||||
|
Navigation.goBack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!pricingPlans.currentPlan?.id ||
|
!pricingPlans.currentPlan?.id ||
|
||||||
@@ -484,7 +508,7 @@ After trying all the privacy security oriented note taking apps, for the price a
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Toast context="local" />
|
<Toast context="local" />
|
||||||
</>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -517,17 +541,19 @@ const FAQItem = (props: { question: string; answer: string }) => {
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
size={SIZE.md}
|
size={AppFontSize.md}
|
||||||
>
|
>
|
||||||
{props.question}
|
{props.question}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Icon
|
<Icon
|
||||||
name={expanded ? "chevron-up" : "chevron-down"}
|
name={expanded ? "chevron-up" : "chevron-down"}
|
||||||
color={colors.secondary.icon}
|
color={colors.secondary.icon}
|
||||||
size={SIZE.xxl}
|
size={AppFontSize.xxl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{expanded ? <Paragraph size={SIZE.md}>{props.answer}</Paragraph> : null}
|
{expanded ? (
|
||||||
|
<Paragraph size={AppFontSize.md}>{props.answer}</Paragraph>
|
||||||
|
) : null}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -575,17 +601,17 @@ const ComparePlans = React.memo(
|
|||||||
{item === true ? (
|
{item === true ? (
|
||||||
<Icon
|
<Icon
|
||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
size={SIZE.sm}
|
size={AppFontSize.sm}
|
||||||
name="check"
|
name="check"
|
||||||
/>
|
/>
|
||||||
) : item === false ? (
|
) : item === false ? (
|
||||||
<Icon
|
<Icon
|
||||||
size={SIZE.sm}
|
size={AppFontSize.sm}
|
||||||
color={colors.static.red}
|
color={colors.static.red}
|
||||||
name="close"
|
name="close"
|
||||||
/>
|
/>
|
||||||
) : keyIndex === 0 ? (
|
) : keyIndex === 0 ? (
|
||||||
<Heading size={SIZE.sm}>{item}</Heading>
|
<Heading size={AppFontSize.sm}>{item}</Heading>
|
||||||
) : (
|
) : (
|
||||||
<Paragraph>{item}</Paragraph>
|
<Paragraph>{item}</Paragraph>
|
||||||
)}
|
)}
|
||||||
@@ -626,7 +652,7 @@ const ReviewItem = (props: {
|
|||||||
style={{
|
style={{
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
size={SIZE.md}
|
size={AppFontSize.md}
|
||||||
>
|
>
|
||||||
{props.review}
|
{props.review}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -655,7 +681,7 @@ const ReviewItem = (props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Paragraph size={SIZE.sm}>{props.user}</Paragraph>
|
<Paragraph size={AppFontSize.sm}>{props.user}</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -724,14 +750,14 @@ const PricingPlanCard = ({
|
|||||||
alignSelf: "flex-start"
|
alignSelf: "flex-start"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading color={colors.static.white} size={SIZE.xs}>
|
<Heading color={colors.static.white} size={AppFontSize.xs}>
|
||||||
{discountPercentage}% Off
|
{discountPercentage}% Off
|
||||||
</Heading>
|
</Heading>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<View>
|
<View>
|
||||||
<Heading size={SIZE.md}>
|
<Heading size={AppFontSize.md}>
|
||||||
{plan.name}{" "}
|
{plan.name}{" "}
|
||||||
{plan.recommended ? (
|
{plan.recommended ? (
|
||||||
<Text
|
<Text
|
||||||
@@ -751,12 +777,12 @@ const PricingPlanCard = ({
|
|||||||
<ActivityIndicator size="small" color={colors.primary.accent} />
|
<ActivityIndicator size="small" color={colors.primary.accent} />
|
||||||
) : (
|
) : (
|
||||||
<View>
|
<View>
|
||||||
<Paragraph size={SIZE.lg}>
|
<Paragraph size={AppFontSize.lg}>
|
||||||
{isFreePlan ? "0.00" : price} <Paragraph>/month</Paragraph>
|
{isFreePlan ? "0.00" : price} <Paragraph>/month</Paragraph>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{isFreePlan ? null : (
|
{isFreePlan ? null : (
|
||||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
|
||||||
billed {annualBilling ? "annually" : "monthly"} at{" "}
|
billed {annualBilling ? "annually" : "monthly"} at{" "}
|
||||||
{pricingPlans?.getStandardPrice(product as RNIap.Subscription)}{" "}
|
{pricingPlans?.getStandardPrice(product as RNIap.Subscription)}{" "}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -766,3 +792,5 @@ const PricingPlanCard = ({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default PayWall;
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React, { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
|
||||||
import useKeyboard from "../../hooks/use-keyboard";
|
|
||||||
import { DDS } from "../../services/device-detection";
|
|
||||||
import {
|
|
||||||
eSendEvent,
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent
|
|
||||||
} from "../../services/event-manager";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { getElevationStyle } from "../../utils/elevation";
|
|
||||||
import {
|
|
||||||
eCloseActionSheet,
|
|
||||||
eCloseSheet,
|
|
||||||
eOpenPremiumDialog,
|
|
||||||
eShowGetPremium
|
|
||||||
} from "../../utils/events";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import { sleep } from "../../utils/time";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import Heading from "../ui/typography/heading";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
const [msg, setMsg] = useState(null);
|
|
||||||
const timer = useRef();
|
|
||||||
const keyboard = useKeyboard();
|
|
||||||
|
|
||||||
const open = useCallback(
|
|
||||||
(event) => {
|
|
||||||
if (!event) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer.current = null;
|
|
||||||
setMsg(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.context === context && msg?.desc !== event.desc) {
|
|
||||||
if (timer.current !== null) {
|
|
||||||
clearTimeout(timer.current);
|
|
||||||
timer.current = null;
|
|
||||||
}
|
|
||||||
setMsg(event);
|
|
||||||
timer.current = setTimeout(async () => {
|
|
||||||
setMsg(null);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[context, msg?.desc]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eSubscribeEvent(eShowGetPremium, open);
|
|
||||||
return () => {
|
|
||||||
eUnSubscribeEvent(eShowGetPremium, open);
|
|
||||||
};
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
const onPress = async () => {
|
|
||||||
open(null);
|
|
||||||
eSendEvent(eCloseActionSheet);
|
|
||||||
eSendEvent(eCloseSheet);
|
|
||||||
await sleep(300);
|
|
||||||
eSendEvent(eOpenPremiumDialog);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
!!msg && (
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeInUp}
|
|
||||||
exiting={FadeOutUp}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
zIndex: 999,
|
|
||||||
...getElevationStyle(20),
|
|
||||||
padding: DefaultAppStyles.GAP,
|
|
||||||
borderRadius: 10,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignSelf: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
top: offset + keyboard.keyboardHeight,
|
|
||||||
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
setMsg(null);
|
|
||||||
clearTimeout(timer.current);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexShrink: 1,
|
|
||||||
flexGrow: 1,
|
|
||||||
paddingRight: 6
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
style={{
|
|
||||||
flexWrap: "wrap"
|
|
||||||
}}
|
|
||||||
color={colors.primary.accent}
|
|
||||||
size={AppFontSize.md}
|
|
||||||
>
|
|
||||||
{msg.title}
|
|
||||||
</Heading>
|
|
||||||
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
flexWrap: "wrap"
|
|
||||||
}}
|
|
||||||
size={AppFontSize.sm}
|
|
||||||
color={colors.primary.paragraph}
|
|
||||||
>
|
|
||||||
{msg.desc}
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Button onPress={onPress} title="Get Now" type="accent" />
|
|
||||||
</Animated.View>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { Platform, View } from "react-native";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import { Pressable } from "../ui/pressable";
|
|
||||||
import Heading from "../ui/typography/heading";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import RNIap from "react-native-iap";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
export const PricingItem = ({
|
|
||||||
product,
|
|
||||||
onPress,
|
|
||||||
compact,
|
|
||||||
strikethrough
|
|
||||||
}: {
|
|
||||||
product: {
|
|
||||||
type: "yearly" | "monthly";
|
|
||||||
data?: RNIap.Subscription;
|
|
||||||
info: string;
|
|
||||||
offerType?: "yearly" | "monthly";
|
|
||||||
};
|
|
||||||
strikethrough?: boolean;
|
|
||||||
onPress?: () => void;
|
|
||||||
compact?: boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
onPress={onPress}
|
|
||||||
type="secondary"
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
paddingVertical: compact ? 15 : 10,
|
|
||||||
width: compact ? null : "100%",
|
|
||||||
minWidth: 150,
|
|
||||||
opacity: strikethrough ? 0.7 : 1
|
|
||||||
}}
|
|
||||||
disabled={strikethrough}
|
|
||||||
>
|
|
||||||
{!compact && (
|
|
||||||
<View>
|
|
||||||
<Heading size={AppFontSize.lg - 2}>
|
|
||||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
|
||||||
? "Yearly"
|
|
||||||
: "Monthly"}
|
|
||||||
</Heading>
|
|
||||||
{product?.info && (
|
|
||||||
<Paragraph size={AppFontSize.xs}>{product.info}</Paragraph>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View>
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
|
||||||
}}
|
|
||||||
size={AppFontSize.sm}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
style={{
|
|
||||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
|
||||||
}}
|
|
||||||
size={AppFontSize.lg - 2}
|
|
||||||
>
|
|
||||||
{Platform.OS === "android"
|
|
||||||
? (product.data as RNIap.SubscriptionAndroid | undefined)
|
|
||||||
?.subscriptionOfferDetails?.[0]?.pricingPhases
|
|
||||||
.pricingPhaseList?.[0]?.formattedPrice
|
|
||||||
: (product.data as RNIap.SubscriptionIOS | undefined)
|
|
||||||
?.localizedPrice}
|
|
||||||
</Heading>
|
|
||||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
|
||||||
? "/year"
|
|
||||||
: "/month"}
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,431 +0,0 @@
|
|||||||
/*
|
|
||||||
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 { useThemeColors } from "@notesnook/theme";
|
|
||||||
import React from "react";
|
|
||||||
import { ActivityIndicator, Platform, Text, View } from "react-native";
|
|
||||||
import usePricingPlans from "../../hooks/use-pricing-plans";
|
|
||||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
|
||||||
import {
|
|
||||||
eClosePremiumDialog,
|
|
||||||
eCloseSheet,
|
|
||||||
eCloseSimpleDialog,
|
|
||||||
eOpenLoginDialog
|
|
||||||
} from "../../utils/events";
|
|
||||||
import { openLinkInBrowser } from "../../utils/functions";
|
|
||||||
import { AppFontSize } from "../../utils/size";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
import { Dialog } from "../dialog";
|
|
||||||
import BaseDialog from "../dialog/base-dialog";
|
|
||||||
import { presentDialog } from "../dialog/functions";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import Heading from "../ui/typography/heading";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { PricingItem } from "./pricing-item";
|
|
||||||
|
|
||||||
const UUID_PREFIX = "0bdaea";
|
|
||||||
const UUID_VERSION = "4";
|
|
||||||
const UUID_VARIANT = "a";
|
|
||||||
|
|
||||||
function toUUID(str: string) {
|
|
||||||
return [
|
|
||||||
UUID_PREFIX + str.substring(0, 2), // 6 digit prefix + first 2 oid digits
|
|
||||||
str.substring(2, 6), // # next 4 oid digits
|
|
||||||
UUID_VERSION + str.substring(6, 9), // # 1 digit version(0x4) + next 3 oid digits
|
|
||||||
UUID_VARIANT + str.substring(9, 12), // # 1 digit variant(0b101) + 1 zero bit + next 3 oid digits
|
|
||||||
str.substring(12)
|
|
||||||
].join("-");
|
|
||||||
}
|
|
||||||
|
|
||||||
const promoCyclesMonthly = {
|
|
||||||
1: "first month",
|
|
||||||
2: "first 2 months",
|
|
||||||
3: "first 3 months",
|
|
||||||
4: "first 4 months",
|
|
||||||
5: "first 5 months",
|
|
||||||
6: "first 3 months"
|
|
||||||
};
|
|
||||||
|
|
||||||
const promoCyclesYearly = {
|
|
||||||
1: "first year",
|
|
||||||
2: "first 2 years",
|
|
||||||
3: "first 3 years"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PricingPlans = ({
|
|
||||||
promo,
|
|
||||||
marginTop,
|
|
||||||
heading = true,
|
|
||||||
compact = false
|
|
||||||
}: {
|
|
||||||
promo?: {
|
|
||||||
promoCode: string;
|
|
||||||
};
|
|
||||||
marginTop?: any;
|
|
||||||
heading?: boolean;
|
|
||||||
compact?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
const {
|
|
||||||
buySubscription,
|
|
||||||
buying,
|
|
||||||
getStandardPrice,
|
|
||||||
product,
|
|
||||||
setBuying,
|
|
||||||
loading,
|
|
||||||
user,
|
|
||||||
setProduct,
|
|
||||||
monthlyPlan,
|
|
||||||
yearlyPlan,
|
|
||||||
getPromo
|
|
||||||
} = usePricingPlans({
|
|
||||||
promoOffer: promo
|
|
||||||
});
|
|
||||||
|
|
||||||
return loading ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: 100
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActivityIndicator color={colors.primary.accent} size={25} />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: DefaultAppStyles.GAP
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{buying ? (
|
|
||||||
<BaseDialog visible statusBarTranslucent centered>
|
|
||||||
<ActivityIndicator size={50} color="white" />
|
|
||||||
</BaseDialog>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{!user && !product ? (
|
|
||||||
<>
|
|
||||||
{heading || (monthlyPlan?.info?.discount || 0) > 0 ? (
|
|
||||||
<>
|
|
||||||
{monthlyPlan && (monthlyPlan?.info?.discount || 0) > 0 ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
marginTop: marginTop || 20,
|
|
||||||
marginBottom: 20
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
style={{
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
color={colors.primary.accent}
|
|
||||||
>
|
|
||||||
Get {monthlyPlan?.info?.discount}% off in{" "}
|
|
||||||
{monthlyPlan?.info?.country}
|
|
||||||
</Heading>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Heading
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
marginTop: marginTop || 20,
|
|
||||||
marginBottom: 20
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Choose a plan
|
|
||||||
</Heading>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: !compact ? "column" : "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
justifyContent: "space-around"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PricingItem
|
|
||||||
onPress={() => {
|
|
||||||
if (!monthlyPlan?.product) return;
|
|
||||||
buySubscription(monthlyPlan?.product);
|
|
||||||
}}
|
|
||||||
compact={compact}
|
|
||||||
product={{
|
|
||||||
type: "monthly",
|
|
||||||
data: monthlyPlan?.product,
|
|
||||||
info: "Pay once a month, cancel anytime."
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!compact && (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 1,
|
|
||||||
marginVertical: 5
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<PricingItem
|
|
||||||
onPress={() => {
|
|
||||||
if (!yearlyPlan?.product) return;
|
|
||||||
buySubscription(yearlyPlan?.product);
|
|
||||||
}}
|
|
||||||
compact={compact}
|
|
||||||
product={{
|
|
||||||
type: "yearly",
|
|
||||||
data: yearlyPlan?.product,
|
|
||||||
info: "Pay once a year, cancel anytime."
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{Platform.OS !== "ios" ? (
|
|
||||||
<Button
|
|
||||||
height={35}
|
|
||||||
style={{
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
presentDialog({
|
|
||||||
context: "local",
|
|
||||||
input: true,
|
|
||||||
inputPlaceholder: "Enter code",
|
|
||||||
positiveText: "Apply",
|
|
||||||
positivePress: async (value) => {
|
|
||||||
if (!value) return;
|
|
||||||
eSendEvent(eCloseSimpleDialog);
|
|
||||||
setBuying(true);
|
|
||||||
try {
|
|
||||||
if (!(await getPromo(value as string)))
|
|
||||||
throw new Error("Error applying promo code");
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Discount applied!",
|
|
||||||
type: "success",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
setBuying(false);
|
|
||||||
} catch (e) {
|
|
||||||
setBuying(false);
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Promo code invalid or expired",
|
|
||||||
message: (e as Error).message,
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: "Have a promo code?",
|
|
||||||
paragraph: "Enter your promo code to get a special discount."
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
title="I have a promo code"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 15
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<View>
|
|
||||||
{!user ? (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onPress={() => {
|
|
||||||
eSendEvent(eClosePremiumDialog);
|
|
||||||
eSendEvent(eCloseSheet);
|
|
||||||
setTimeout(() => {
|
|
||||||
eSendEvent(eOpenLoginDialog, 1);
|
|
||||||
}, 400);
|
|
||||||
}}
|
|
||||||
title={"Sign up for free"}
|
|
||||||
type="accent"
|
|
||||||
width={250}
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
marginTop: product?.type === "promo" ? 0 : 30,
|
|
||||||
marginBottom: 10
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{Platform.OS !== "ios" &&
|
|
||||||
promo &&
|
|
||||||
!promo.promoCode.startsWith("com.streetwriters.notesnook") ? (
|
|
||||||
<Paragraph
|
|
||||||
size={AppFontSize.md}
|
|
||||||
textBreakStrategy="balanced"
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Use promo code{" "}
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontFamily: "OpenSans-SemiBold"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{promo.promoCode}
|
|
||||||
</Text>{" "}
|
|
||||||
at checkout
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
onPress={() => {
|
|
||||||
if (!product?.data) return;
|
|
||||||
buySubscription(product.data);
|
|
||||||
}}
|
|
||||||
height={40}
|
|
||||||
width="50%"
|
|
||||||
type="accent"
|
|
||||||
title="Subscribe now"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={() => {
|
|
||||||
setProduct(undefined);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
marginTop: 5
|
|
||||||
}}
|
|
||||||
height={30}
|
|
||||||
fontSize={13}
|
|
||||||
type="errorShade"
|
|
||||||
title="Cancel promo code"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!user ? (
|
|
||||||
<Paragraph
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
textAlign: "center",
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
maxWidth: "80%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{user
|
|
||||||
? 'On clicking "Try free for 14 days", your free trial will be activated.'
|
|
||||||
: "After sign up you will be asked to activate your free trial."}{" "}
|
|
||||||
<Paragraph size={AppFontSize.xs} style={{ fontWeight: "bold" }}>
|
|
||||||
No credit card is required.
|
|
||||||
</Paragraph>
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{user ? (
|
|
||||||
<>
|
|
||||||
{Platform.OS === "ios" ? (
|
|
||||||
<Paragraph
|
|
||||||
textBreakStrategy="balanced"
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
By subscribing, you will be charged to your iTunes Account for the
|
|
||||||
selected plan. Subscriptions will automatically renew unless
|
|
||||||
cancelled within 24-hours before the end of the current period.
|
|
||||||
</Paragraph>
|
|
||||||
) : (
|
|
||||||
<Paragraph
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
By subscribing, you will be charged on your Google Account, and
|
|
||||||
your subscription will automatically renew until you cancel prior
|
|
||||||
to the end of the then current period.
|
|
||||||
</Paragraph>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
style={{
|
|
||||||
maxWidth: "100%",
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
By subscribing, you agree to our{" "}
|
|
||||||
<Paragraph
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
onPress={() => {
|
|
||||||
openLinkInBrowser("https://notesnook.com/tos")
|
|
||||||
.catch(() => {})
|
|
||||||
.then(() => {});
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline"
|
|
||||||
}}
|
|
||||||
color={colors.primary.accent}
|
|
||||||
>
|
|
||||||
Terms of Service{" "}
|
|
||||||
</Paragraph>
|
|
||||||
and{" "}
|
|
||||||
<Paragraph
|
|
||||||
size={AppFontSize.xs}
|
|
||||||
onPress={() => {
|
|
||||||
openLinkInBrowser("https://notesnook.com/privacy")
|
|
||||||
.catch(() => {})
|
|
||||||
.then(() => {});
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
textDecorationLine: "underline"
|
|
||||||
}}
|
|
||||||
color={colors.primary.accent}
|
|
||||||
>
|
|
||||||
Privacy Policy.
|
|
||||||
</Paragraph>
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Dialog context="local" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
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 React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
|
||||||
import { DefaultAppStyles } from "../../utils/styles";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {any} param0
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const ProTag = ({ width, size, background }) => {
|
|
||||||
const { colors } = useThemeColors();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: background || colors.primary.background,
|
|
||||||
borderRadius: 100,
|
|
||||||
width: width || 60,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2,
|
|
||||||
flexDirection: "row"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
style={{
|
|
||||||
marginRight: 3
|
|
||||||
}}
|
|
||||||
size={size}
|
|
||||||
color={colors.static.orange}
|
|
||||||
name="crown"
|
|
||||||
/>
|
|
||||||
<Paragraph size={size - 1.5} color={colors.primary.accent}>
|
|
||||||
PRO
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -16,6 +16,8 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
Platform,
|
Platform,
|
||||||
@@ -24,19 +26,17 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import usePricingPlans from "../../../hooks/use-pricing-plans";
|
|
||||||
import { presentSheet } from "../../../services/event-manager";
|
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
|
||||||
import Heading from "../../ui/typography/heading";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import * as RNIap from "react-native-iap";
|
import * as RNIap from "react-native-iap";
|
||||||
import { Button } from "../../ui/button";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import { openLinkInBrowser } from "../../../utils/functions";
|
|
||||||
import { IconButton } from "../../ui/icon-button";
|
|
||||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||||
|
import usePricingPlans from "../../../hooks/use-pricing-plans";
|
||||||
|
import { openLinkInBrowser } from "../../../utils/functions";
|
||||||
|
import { AppFontSize } from "../../../utils/size";
|
||||||
|
import { Button } from "../../ui/button";
|
||||||
|
import { IconButton } from "../../ui/icon-button";
|
||||||
|
import Heading from "../../ui/typography/heading";
|
||||||
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
import { DefaultAppStyles } from "../../../utils/styles";
|
||||||
|
|
||||||
export const BuyPlan = (props: {
|
export const BuyPlan = (props: {
|
||||||
planId: string;
|
planId: string;
|
||||||
@@ -67,9 +67,8 @@ export const BuyPlan = (props: {
|
|||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
gap: 16,
|
|
||||||
paddingBottom: 80,
|
paddingBottom: 80,
|
||||||
paddingTop: Platform.OS === "android" ? insets.top : 0
|
marginTop: DefaultAppStyles.GAP
|
||||||
}}
|
}}
|
||||||
keyboardDismissMode="none"
|
keyboardDismissMode="none"
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
@@ -77,40 +76,9 @@ export const BuyPlan = (props: {
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
paddingHorizontal: DefaultAppStyles.GAP
|
||||||
alignItems: "center",
|
|
||||||
width: "100%",
|
|
||||||
backgroundColor: colors.primary.background
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
|
||||||
name="chevron-left"
|
|
||||||
onPress={() => {
|
|
||||||
props.goBack();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
alignSelf: "flex-start"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
gap: 16
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
style={{
|
|
||||||
alignSelf: "center",
|
|
||||||
marginBottom: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.canActivateTrial
|
|
||||||
? `Try ${pricingPlans.currentPlan?.name} plan for free`
|
|
||||||
: `${pricingPlans.currentPlan?.name} plan`}{" "}
|
|
||||||
</Heading>
|
|
||||||
|
|
||||||
{props.canActivateTrial ? (
|
{props.canActivateTrial ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -138,7 +106,7 @@ export const BuyPlan = (props: {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
size={SIZE.lg}
|
size={AppFontSize.lg}
|
||||||
name="check"
|
name="check"
|
||||||
/>
|
/>
|
||||||
<Paragraph>{item}</Paragraph>
|
<Paragraph>{item}</Paragraph>
|
||||||
@@ -164,11 +132,10 @@ export const BuyPlan = (props: {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between"
|
||||||
marginTop: 20
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading color={colors.primary.paragraph} size={SIZE.sm}>
|
<Heading color={colors.primary.paragraph} size={AppFontSize.sm}>
|
||||||
Due today{" "}
|
Due today{" "}
|
||||||
{pricingPlans.userCanRequestTrial ? (
|
{pricingPlans.userCanRequestTrial ? (
|
||||||
<Text
|
<Text
|
||||||
@@ -220,6 +187,10 @@ export const BuyPlan = (props: {
|
|||||||
? "Subscribe and start free trial"
|
? "Subscribe and start free trial"
|
||||||
: "Subscribe"
|
: "Subscribe"
|
||||||
}
|
}
|
||||||
|
style={{
|
||||||
|
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||||
|
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||||
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const offerToken = pricingPlans.getOfferTokenAndroid(
|
const offerToken = pricingPlans.getOfferTokenAndroid(
|
||||||
pricingPlans.selectedProduct as RNIap.SubscriptionAndroid,
|
pricingPlans.selectedProduct as RNIap.SubscriptionAndroid,
|
||||||
@@ -232,13 +203,12 @@ export const BuyPlan = (props: {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View>
|
|
||||||
<Heading
|
<Heading
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
size={SIZE.xs}
|
size={AppFontSize.xs}
|
||||||
>
|
>
|
||||||
{is5YearPlanSelected
|
{is5YearPlanSelected
|
||||||
? `This is a one time purchase, no subscription.`
|
? `This is a one time purchase, no subscription.`
|
||||||
@@ -249,7 +219,7 @@ export const BuyPlan = (props: {
|
|||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
size={SIZE.xs}
|
size={AppFontSize.xs}
|
||||||
>
|
>
|
||||||
By joining you agree to our{" "}
|
By joining you agree to our{" "}
|
||||||
<Text
|
<Text
|
||||||
@@ -276,7 +246,6 @@ export const BuyPlan = (props: {
|
|||||||
.
|
.
|
||||||
</Heading>
|
</Heading>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -307,20 +276,20 @@ const ProductItem = (props: {
|
|||||||
<Icon
|
<Icon
|
||||||
name={isSelected ? "radiobox-marked" : "radiobox-blank"}
|
name={isSelected ? "radiobox-marked" : "radiobox-blank"}
|
||||||
color={isSelected ? colors.primary.accent : colors.secondary.icon}
|
color={isSelected ? colors.primary.accent : colors.secondary.icon}
|
||||||
size={SIZE.lg}
|
size={AppFontSize.lg}
|
||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
gap: 10
|
gap: DefaultAppStyles.GAP_VERTICAL
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 10
|
gap: DefaultAppStyles.GAP_VERTICAL
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.md}>
|
<Heading size={AppFontSize.md}>
|
||||||
{isAnnual
|
{isAnnual
|
||||||
? "Yearly"
|
? "Yearly"
|
||||||
: product?.productId.includes("5year")
|
: product?.productId.includes("5year")
|
||||||
@@ -338,7 +307,7 @@ const ProductItem = (props: {
|
|||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading color={colors.static.white} size={SIZE.xs}>
|
<Heading color={colors.static.white} size={AppFontSize.xs}>
|
||||||
Best value -{" "}
|
Best value -{" "}
|
||||||
{props.pricingPlans.compareProductPrice(
|
{props.pricingPlans.compareProductPrice(
|
||||||
props.pricingPlans.currentPlan?.id as string,
|
props.pricingPlans.currentPlan?.id as string,
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import { DefaultAppStyles } from "../../../utils/styles";
|
|||||||
import { sleep } from "../../../utils/time";
|
import { sleep } from "../../../utils/time";
|
||||||
import { Dialog } from "../../dialog";
|
import { Dialog } from "../../dialog";
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { ProTag } from "../../premium/pro-tag";
|
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { Pressable } from "../../ui/pressable";
|
import { Pressable } from "../../ui/pressable";
|
||||||
@@ -227,7 +226,6 @@ const ExportNotesSheet = ({
|
|||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!item.pro ? <ProTag size={12} /> : null}
|
|
||||||
<Heading style={{ marginLeft: 10 }} size={AppFontSize.md}>
|
<Heading style={{ marginLeft: 10 }} size={AppFontSize.md}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|||||||
@@ -24,19 +24,17 @@ import {
|
|||||||
ColorValue,
|
ColorValue,
|
||||||
DimensionValue,
|
DimensionValue,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
View,
|
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
useWindowDimensions
|
useWindowDimensions
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
|
||||||
|
import { DefaultAppStyles } from "../../../utils/styles";
|
||||||
import NativeTooltip from "../../../utils/tooltip";
|
import NativeTooltip from "../../../utils/tooltip";
|
||||||
import { ProTag } from "../../premium/pro-tag";
|
|
||||||
import { Pressable, PressableProps, useButton } from "../pressable";
|
import { Pressable, PressableProps, useButton } from "../pressable";
|
||||||
import Heading from "../typography/heading";
|
import Heading from "../typography/heading";
|
||||||
import Paragraph from "../typography/paragraph";
|
import Paragraph from "../typography/paragraph";
|
||||||
import { DefaultAppStyles } from "../../../utils/styles";
|
|
||||||
export interface ButtonProps extends PressableProps {
|
export interface ButtonProps extends PressableProps {
|
||||||
height?: number;
|
height?: number;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@@ -167,15 +165,6 @@ export const Button = ({
|
|||||||
{title}
|
{title}
|
||||||
</Component>
|
</Component>
|
||||||
)}
|
)}
|
||||||
{proTag && !premium ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
marginLeft: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ProTag size={10} width={40} background={colors.primary.shade} />
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{icon && !loading && iconPosition === "right" ? (
|
{icon && !loading && iconPosition === "right" ? (
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets"
|
|||||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
import { getContainerBorder } from "../../../utils/colors";
|
import { getContainerBorder } from "../../../utils/colors";
|
||||||
import { PremiumToast } from "../../premium/premium-toast";
|
|
||||||
import { Toast } from "../../toast";
|
|
||||||
import { NotesnookModule } from "../../../utils/notesnook-module";
|
import { NotesnookModule } from "../../../utils/notesnook-module";
|
||||||
|
import { Toast } from "../../toast";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} param0
|
* @param {any} param0
|
||||||
@@ -155,11 +155,6 @@ const SheetWrapper = ({
|
|||||||
ExtraOverlayComponent={
|
ExtraOverlayComponent={
|
||||||
<>
|
<>
|
||||||
{overlay}
|
{overlay}
|
||||||
<PremiumToast
|
|
||||||
context="sheet"
|
|
||||||
close={() => fwdRef?.current?.hide()}
|
|
||||||
offset={50}
|
|
||||||
/>
|
|
||||||
<Toast context="local" />
|
<Toast context="local" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,7 +329,6 @@ const doAppLoadActions = async () => {
|
|||||||
if (NewFeature.present()) return;
|
if (NewFeature.present()) return;
|
||||||
if (await checkAppUpdateAvailable()) return;
|
if (await checkAppUpdateAvailable()) return;
|
||||||
if (await checkForRateAppRequest()) return;
|
if (await checkForRateAppRequest()) return;
|
||||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
|
||||||
if (SettingsService.get().introCompleted) {
|
if (SettingsService.get().introCompleted) {
|
||||||
useMessageStore.subscribe((state) => {
|
useMessageStore.subscribe((state) => {
|
||||||
const dialogs = state.dialogs;
|
const dialogs = state.dialogs;
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ let MoveNotes: any = null;
|
|||||||
let Settings: any = null;
|
let Settings: any = null;
|
||||||
let ManageTags: any = null;
|
let ManageTags: any = null;
|
||||||
let AddReminder: any = null;
|
let AddReminder: any = null;
|
||||||
|
let PayWall: any = null;
|
||||||
export const RootNavigation = () => {
|
export const RootNavigation = () => {
|
||||||
const introCompleted = useSettingStore(
|
const introCompleted = useSettingStore(
|
||||||
(state) => state.settings.introCompleted
|
(state) => state.settings.introCompleted
|
||||||
@@ -353,6 +353,14 @@ export const RootNavigation = () => {
|
|||||||
return AddReminder;
|
return AddReminder;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<RootStack.Screen
|
||||||
|
name="PayWall"
|
||||||
|
getComponent={() => {
|
||||||
|
PayWall =
|
||||||
|
PayWall || require("../components/premium/paywall").default;
|
||||||
|
return PayWall;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</RootStack.Navigator>
|
</RootStack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
View
|
View
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import Editor from ".";
|
import Editor from ".";
|
||||||
import { PremiumToast } from "../../components/premium/premium-toast";
|
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||||
import useKeyboard from "../../hooks/use-keyboard";
|
import useKeyboard from "../../hooks/use-keyboard";
|
||||||
@@ -112,7 +111,6 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
|
|||||||
enabled={!floating}
|
enabled={!floating}
|
||||||
keyboardVerticalOffset={0}
|
keyboardVerticalOffset={0}
|
||||||
>
|
>
|
||||||
<PremiumToast key="toast" context="editor" offset={50 + insets.top} />
|
|
||||||
<TextInput
|
<TextInput
|
||||||
key="input"
|
key="input"
|
||||||
ref={textInput}
|
ref={textInput}
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ const routeNames = {
|
|||||||
MoveNotes: "MoveNotes",
|
MoveNotes: "MoveNotes",
|
||||||
Archive: "Archive",
|
Archive: "Archive",
|
||||||
ManageTags: "ManageTags",
|
ManageTags: "ManageTags",
|
||||||
AddReminder: "AddReminder"
|
AddReminder: "AddReminder",
|
||||||
|
PayWall: "PayWall"
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<
|
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<
|
||||||
|
|||||||
@@ -18,27 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CHECK_IDS } from "@notesnook/core";
|
import { CHECK_IDS } from "@notesnook/core";
|
||||||
import React from "react";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import Config from "react-native-config";
|
import Config from "react-native-config";
|
||||||
import * as RNIap from "react-native-iap";
|
import * as RNIap from "react-native-iap";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { MMKV } from "../common/database/mmkv";
|
import { MMKV } from "../common/database/mmkv";
|
||||||
import DialogHeader from "../components/dialog/dialog-header";
|
|
||||||
import { CompactFeatures } from "../components/premium/compact-features";
|
|
||||||
import { PricingPlans } from "../components/premium/pricing-plans";
|
|
||||||
import Seperator from "../components/ui/seperator";
|
|
||||||
import { useUserStore } from "../stores/use-user-store";
|
import { useUserStore } from "../stores/use-user-store";
|
||||||
import { itemSkus, SUBSCRIPTION_STATUS } from "../utils/constants";
|
import { itemSkus, SUBSCRIPTION_STATUS } from "../utils/constants";
|
||||||
import {
|
import { eOpenPremiumDialog, eShowGetPremium } from "../utils/events";
|
||||||
eOpenPremiumDialog,
|
|
||||||
eOpenTrialEndingDialog,
|
|
||||||
eShowGetPremium
|
|
||||||
} from "../utils/events";
|
|
||||||
import { eSendEvent, presentSheet, ToastManager } from "./event-manager";
|
import { eSendEvent, presentSheet, ToastManager } from "./event-manager";
|
||||||
|
|
||||||
import SettingsService from "./settings";
|
|
||||||
import { strings } from "@notesnook/intl";
|
import { strings } from "@notesnook/intl";
|
||||||
|
import SettingsService from "./settings";
|
||||||
let premiumStatus = 0;
|
let premiumStatus = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,10 +91,12 @@ function getMontlySub() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadProductsAndSubs() {
|
async function loadProductsAndSubs() {
|
||||||
|
try {
|
||||||
if (!subs || subs.length === 0) {
|
if (!subs || subs.length === 0) {
|
||||||
subs = await RNIap.getSubscriptions({
|
subs = await RNIap.getSubscriptions({
|
||||||
skus: itemSkus
|
skus: itemSkus
|
||||||
});
|
});
|
||||||
|
console.log("SUBS", subs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!products || products.length === 0) {
|
if (!products || products.length === 0) {
|
||||||
@@ -116,6 +109,13 @@ async function loadProductsAndSubs() {
|
|||||||
subs,
|
subs,
|
||||||
products
|
products
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load products and subscriptions", e);
|
||||||
|
return {
|
||||||
|
subs: [],
|
||||||
|
products: []
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get() {
|
function get() {
|
||||||
@@ -355,87 +355,7 @@ const subscriptions = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getRemainingTrialDaysStatus() {
|
const sheet = (context, promo, trial) => {};
|
||||||
let user = await db.user.getUser();
|
|
||||||
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
|
|
||||||
|
|
||||||
let premium = user.subscription.type !== SUBSCRIPTION_STATUS.BASIC;
|
|
||||||
let isTrial = user.subscription.type === SUBSCRIPTION_STATUS.TRIAL;
|
|
||||||
let total = user.subscription.expiry - user.subscription.start;
|
|
||||||
let current = Date.now() - user.subscription.start;
|
|
||||||
current = (current / total) * 100;
|
|
||||||
|
|
||||||
let lastTrialDialogShownAt = MMKV.getString("lastTrialDialogShownAt");
|
|
||||||
|
|
||||||
if (current > 75 && isTrial && lastTrialDialogShownAt !== "ending") {
|
|
||||||
eSendEvent(eOpenTrialEndingDialog, {
|
|
||||||
title: strings.trialEndingSoon(),
|
|
||||||
offer: null,
|
|
||||||
extend: false
|
|
||||||
});
|
|
||||||
MMKV.setItem("lastTrialDialogShownAt", "ending");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!premium && lastTrialDialogShownAt !== "expired") {
|
|
||||||
eSendEvent(eOpenTrialEndingDialog, {
|
|
||||||
title: strings.trialExpired(),
|
|
||||||
offer: 30,
|
|
||||||
extend: false
|
|
||||||
});
|
|
||||||
MMKV.setItem("lastTrialDialogShownAt", "expired");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const features_list = [
|
|
||||||
{
|
|
||||||
content: "Unlock unlimited notebooks, tags, colors. Organize like a pro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "Attach files upto 500MB, upload 4K images with unlimited storage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "Instantly sync to unlimited devices"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "A private vault to keep everything important always locked"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content:
|
|
||||||
"Rich note editing experience with markdown, tables, checklists and more"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "Export your notes in PDF, markdown and html formats"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const sheet = (context, promo, trial) => {
|
|
||||||
presentSheet({
|
|
||||||
context: context,
|
|
||||||
component: (ref) => (
|
|
||||||
<>
|
|
||||||
<DialogHeader
|
|
||||||
centered
|
|
||||||
title="Upgrade to Notesnook"
|
|
||||||
titlePart="Pro"
|
|
||||||
paragraph="Manage your work on another level, enjoy seamless sync and keep all notes in one place."
|
|
||||||
padding={12}
|
|
||||||
/>
|
|
||||||
<Seperator />
|
|
||||||
<CompactFeatures
|
|
||||||
scrollRef={ref}
|
|
||||||
maxHeight={400}
|
|
||||||
features={features_list}
|
|
||||||
vertical
|
|
||||||
/>
|
|
||||||
<Seperator half />
|
|
||||||
<PricingPlans trial={trial} compact heading={false} promo={promo} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const PremiumService = {
|
const PremiumService = {
|
||||||
verify,
|
verify,
|
||||||
@@ -447,7 +367,6 @@ const PremiumService = {
|
|||||||
getUser,
|
getUser,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
getMontlySub,
|
getMontlySub,
|
||||||
getRemainingTrialDaysStatus,
|
|
||||||
sheet
|
sheet
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ export interface RouteParams extends ParamListBase {
|
|||||||
reference?: Note;
|
reference?: Note;
|
||||||
};
|
};
|
||||||
Intro: GenericRouteParam;
|
Intro: GenericRouteParam;
|
||||||
|
PayWall: {
|
||||||
|
canGoBack?: boolean;
|
||||||
|
context: "signup" | "logged-in" | "logged-out";
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type RouteName = keyof RouteParams;
|
export type RouteName = keyof RouteParams;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -22,7 +22,7 @@ import {
|
|||||||
import Config from "react-native-config";
|
import Config from "react-native-config";
|
||||||
|
|
||||||
i18n.load({
|
i18n.load({
|
||||||
en: __DEV__ && Config.isTesting !== "true" ? $pseudo : $en
|
en: !__DEV__ && Config.isTesting !== "true" ? $pseudo : $en
|
||||||
});
|
});
|
||||||
setI18nGlobal(i18n);
|
setI18nGlobal(i18n);
|
||||||
i18n.activate("en");
|
i18n.activate("en");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
const isGithubRelease = false;
|
const isGithubRelease = false;
|
||||||
const config = {
|
const config = {
|
||||||
commands: require('@callstack/repack/commands/rspack'),
|
// commands: require('@callstack/repack/commands/rspack'),
|
||||||
project: {
|
project: {
|
||||||
android: {
|
android: {
|
||||||
sourceDir: './android'
|
sourceDir: './android'
|
||||||
|
|||||||
9
apps/mobile/package-lock.json
generated
9
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@notesnook/mobile",
|
"name": "@notesnook/mobile",
|
||||||
"version": "3.2.10",
|
"version": "3.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@notesnook/mobile",
|
"name": "@notesnook/mobile",
|
||||||
"version": "3.2.10",
|
"version": "3.2.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -44449,11 +44449,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-format-currency/-/react-native-format-currency-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-format-currency/-/react-native-format-currency-0.0.5.tgz",
|
||||||
"integrity": "sha512-iarFkCig987Zw8GFPv0+ltEdAsksf0gfZGWL3+tC+60VHtnfbtFBFWP4523ltuXwplDxKu4Veem1ThEr+pZlvQ=="
|
"integrity": "sha512-iarFkCig987Zw8GFPv0+ltEdAsksf0gfZGWL3+tC+60VHtnfbtFBFWP4523ltuXwplDxKu4Veem1ThEr+pZlvQ=="
|
||||||
},
|
},
|
||||||
"node_modules/react-native-format-currency": {
|
|
||||||
"version": "0.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-native-format-currency/-/react-native-format-currency-0.0.5.tgz",
|
|
||||||
"integrity": "sha512-iarFkCig987Zw8GFPv0+ltEdAsksf0gfZGWL3+tC+60VHtnfbtFBFWP4523ltuXwplDxKu4Veem1ThEr+pZlvQ=="
|
|
||||||
},
|
|
||||||
"node_modules/react-native-gesture-handler": {
|
"node_modules/react-native-gesture-handler": {
|
||||||
"version": "2.25.0",
|
"version": "2.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz",
|
||||||
|
|||||||
11
apps/monograph/package-lock.json
generated
11
apps/monograph/package-lock.json
generated
@@ -70,6 +70,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@leeoniya/ufuzzy": "^1.0.10",
|
"@leeoniya/ufuzzy": "^1.0.10",
|
||||||
"@microsoft/signalr": "^8.0.0",
|
"@microsoft/signalr": "^8.0.0",
|
||||||
|
"@notesnook/intl": "file:../intl",
|
||||||
"@notesnook/logger": "file:../logger",
|
"@notesnook/logger": "file:../logger",
|
||||||
"@readme/data-urls": "^3.0.0",
|
"@readme/data-urls": "^3.0.0",
|
||||||
"@streetwriters/kysely": "^0.27.4",
|
"@streetwriters/kysely": "^0.27.4",
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
"entities": "5.0.0",
|
"entities": "5.0.0",
|
||||||
"fuzzyjs": "^5.0.1",
|
"fuzzyjs": "^5.0.1",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^10.0.0",
|
||||||
"katex": "0.16.11",
|
"katex": "0.16.11",
|
||||||
"linkedom": "^0.14.17",
|
"linkedom": "^0.14.17",
|
||||||
"liqe": "^1.13.0",
|
"liqe": "^1.13.0",
|
||||||
@@ -91,8 +92,7 @@
|
|||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"qclone": "^1.2.0",
|
"qclone": "^1.2.0",
|
||||||
"rfdc": "^1.3.0",
|
"rfdc": "^1.3.0",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2"
|
||||||
"sqlite-better-trigram": "0.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@notesnook/crypto": "file:../crypto",
|
"@notesnook/crypto": "file:../crypto",
|
||||||
@@ -118,6 +118,9 @@
|
|||||||
"nanoid": "5.0.7",
|
"nanoid": "5.0.7",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"refractor": "^4.8.1",
|
"refractor": "^4.8.1",
|
||||||
|
"sqlite-better-trigram": "^0.0.3",
|
||||||
|
"sqlite-regex": "^0.2.4-alpha.1",
|
||||||
|
"sqlite3-fts5-html": "^0.0.4",
|
||||||
"vitest": "2.1.8",
|
"vitest": "2.1.8",
|
||||||
"vitest-fetch-mock": "^0.2.2",
|
"vitest-fetch-mock": "^0.2.2",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
@@ -138,7 +141,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@notesnook-importer/core": "^2.1.1",
|
"@notesnook-importer/core": "^2.2.2",
|
||||||
"@notesnook/common": "file:../common",
|
"@notesnook/common": "file:../common",
|
||||||
"@notesnook/intl": "file:../intl",
|
"@notesnook/intl": "file:../intl",
|
||||||
"@notesnook/theme": "file:../theme",
|
"@notesnook/theme": "file:../theme",
|
||||||
|
|||||||
11
apps/theme-builder/package-lock.json
generated
11
apps/theme-builder/package-lock.json
generated
@@ -987,7 +987,7 @@
|
|||||||
},
|
},
|
||||||
"../web": {
|
"../web": {
|
||||||
"name": "@notesnook/web",
|
"name": "@notesnook/web",
|
||||||
"version": "3.0.30",
|
"version": "3.2.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1002,7 +1002,7 @@
|
|||||||
"@lingui/react": "5.1.2",
|
"@lingui/react": "5.1.2",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/react": "1.6.1",
|
"@mdi/react": "1.6.1",
|
||||||
"@notesnook-importer/core": "^2.1.1",
|
"@notesnook-importer/core": "^2.2.2",
|
||||||
"@notesnook/common": "file:../../packages/common",
|
"@notesnook/common": "file:../../packages/common",
|
||||||
"@notesnook/core": "file:../../packages/core",
|
"@notesnook/core": "file:../../packages/core",
|
||||||
"@notesnook/crypto": "file:../../packages/crypto",
|
"@notesnook/crypto": "file:../../packages/crypto",
|
||||||
@@ -1015,6 +1015,7 @@
|
|||||||
"@notesnook/themes-server": "file:../../servers/themes",
|
"@notesnook/themes-server": "file:../../servers/themes",
|
||||||
"@notesnook/ui": "file:../../packages/ui",
|
"@notesnook/ui": "file:../../packages/ui",
|
||||||
"@notesnook/web-clipper": "file:../../extensions/web-clipper",
|
"@notesnook/web-clipper": "file:../../extensions/web-clipper",
|
||||||
|
"@paddle/paddle-js": "^1.4.2",
|
||||||
"@react-pdf-viewer/core": "^3.12.0",
|
"@react-pdf-viewer/core": "^3.12.0",
|
||||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||||
"@rehookify/datepicker": "^6.6.7",
|
"@rehookify/datepicker": "^6.6.7",
|
||||||
@@ -1026,7 +1027,7 @@
|
|||||||
"@theme-ui/core": "0.16.1",
|
"@theme-ui/core": "0.16.1",
|
||||||
"@trpc/client": "10.45.2",
|
"@trpc/client": "10.45.2",
|
||||||
"@trpc/react-query": "10.45.2",
|
"@trpc/react-query": "10.45.2",
|
||||||
"@zip.js/zip.js": "^2.7.54",
|
"@zip.js/zip.js": "^2.7.62",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"clipboard-polyfill": "4.1.0",
|
"clipboard-polyfill": "4.1.0",
|
||||||
@@ -1034,7 +1035,7 @@
|
|||||||
"cronosjs": "^1.7.1",
|
"cronosjs": "^1.7.1",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"diffblazer": "^1.0.1",
|
"diffblazer": "^1.0.1",
|
||||||
"electron-trpc": "0.6.1",
|
"electron-trpc": "0.7.1",
|
||||||
"event-source-polyfill": "1.0.31",
|
"event-source-polyfill": "1.0.31",
|
||||||
"fflate": "^0.8.0",
|
"fflate": "^0.8.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
@@ -1058,7 +1059,7 @@
|
|||||||
"react-modal": "3.16.3",
|
"react-modal": "3.16.3",
|
||||||
"react-qrcode-logo": "^2.9.0",
|
"react-qrcode-logo": "^2.9.0",
|
||||||
"react-scroll-sync": "^0.11.2",
|
"react-scroll-sync": "^0.11.2",
|
||||||
"react-virtuoso": "^4.6.2",
|
"react-virtuoso": "^4.12.3",
|
||||||
"snarkdown": "^2.0.0",
|
"snarkdown": "^2.0.0",
|
||||||
"timeago.js": "4.0.2",
|
"timeago.js": "4.0.2",
|
||||||
"w3c-keyname": "^2.2.6",
|
"w3c-keyname": "^2.2.6",
|
||||||
|
|||||||
1369
apps/web/package-lock.json
generated
1369
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3056,6 +3056,10 @@ msgstr "How to fix it?"
|
|||||||
msgid "hr"
|
msgid "hr"
|
||||||
msgstr "hr"
|
msgstr "hr"
|
||||||
|
|
||||||
|
#: src/strings.ts:2489
|
||||||
|
msgid "I already have an account"
|
||||||
|
msgstr "I already have an account"
|
||||||
|
|
||||||
#: src/strings.ts:125
|
#: src/strings.ts:125
|
||||||
msgid "I don't have access to authenticator app"
|
msgid "I don't have access to authenticator app"
|
||||||
msgstr "I don't have access to authenticator app"
|
msgstr "I don't have access to authenticator app"
|
||||||
|
|||||||
@@ -3038,6 +3038,10 @@ msgstr ""
|
|||||||
msgid "hr"
|
msgid "hr"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/strings.ts:2489
|
||||||
|
msgid "I already have an account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/strings.ts:125
|
#: src/strings.ts:125
|
||||||
msgid "I don't have access to authenticator app"
|
msgid "I don't have access to authenticator app"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -2493,5 +2493,6 @@ Use this if changes from other devices are not appearing on this device. This wi
|
|||||||
scrollToBottom: () => t`Scroll to bottom`,
|
scrollToBottom: () => t`Scroll to bottom`,
|
||||||
emailConfirmedDesc: () =>
|
emailConfirmedDesc: () =>
|
||||||
t`Your email has been confirmed. You can now securely sync your encrypted notes across all devices.`,
|
t`Your email has been confirmed. You can now securely sync your encrypted notes across all devices.`,
|
||||||
charactersCount: (count: number) => t`${count} characters`
|
charactersCount: (count: number) => t`${count} characters`,
|
||||||
|
iAlreadyHaveAnAccount: () => t`I already have an account`
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user