mobile: initial pricing changes

This commit is contained in:
Abdullah Atta
2025-09-29 10:01:59 +05:00
parent 24da82313e
commit bcbcd516f6
40 changed files with 404 additions and 3571 deletions

View File

@@ -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 (

View File

@@ -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%"
}} }}
> >

View File

@@ -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>
); );
}; };

View File

@@ -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 && (

View File

@@ -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,65 +300,63 @@ export const Signup = ({ changeMode, welcome }) => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<Paragraph <View
style={{ style={{
marginBottom: 25 paddingHorizontal: DefaultAppStyles.GAP
}} }}
size={AppFontSize.xxs}
color={colors.secondary.paragraph}
> >
{strings.signupAgreement[0]()}
<Paragraph <Paragraph
size={AppFontSize.xxs}
onPress={() => {
openLinkInBrowser("https://notesnook.com/tos", colors);
}}
style={{ style={{
textDecorationLine: "underline" marginBottom: 25,
textAlign: "center"
}} }}
color={colors.primary.accent}
>
{" "}
{strings.signupAgreement[1]()}
</Paragraph>{" "}
{strings.signupAgreement[2]()}
<Paragraph
size={AppFontSize.xxs} size={AppFontSize.xxs}
onPress={() => { color={colors.secondary.paragraph}
openLinkInBrowser("https://notesnook.com/privacy", colors);
}}
style={{
textDecorationLine: "underline"
}}
color={colors.primary.accent}
> >
{" "} {strings.signupAgreement[0]()}
{strings.signupAgreement[3]()} <Paragraph
</Paragraph>{" "} size={AppFontSize.xxs}
{strings.signupAgreement[4]()} onPress={() => {
</Paragraph> openLinkInBrowser("https://notesnook.com/tos", colors);
}}
style={{
textDecorationLine: "underline"
}}
color={colors.primary.accent}
>
{" "}
{strings.signupAgreement[1]()}
</Paragraph>{" "}
{strings.signupAgreement[2]()}
<Paragraph
size={AppFontSize.xxs}
onPress={() => {
openLinkInBrowser(
"https://notesnook.com/privacy",
colors
);
}}
style={{
textDecorationLine: "underline"
}}
color={colors.primary.accent}
>
{" "}
{strings.signupAgreement[3]()}
</Paragraph>{" "}
{strings.signupAgreement[4]()}
</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>
); );

View File

@@ -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,

View File

@@ -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 />

View File

@@ -115,65 +115,75 @@ const Intro = () => {
); );
return ( return (
<ScrollView <View
testID="notesnook.splashscreen"
style={{ style={{
width: "100%", flex: 1,
height: "100%",
backgroundColor: colors.primary.background backgroundColor: colors.primary.background
}} }}
> >
<View <View
style={[ testID="notesnook.splashscreen"
{ style={{
width: "100%", flex: 1
backgroundColor: colors.secondary.background, }}
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
paddingTop: insets.top + 10,
paddingBottom: insets.top + 10,
minHeight: height * 0.7 - (insets.top + insets.bottom)
},
isTablet && {
width: width / 2,
alignSelf: "center",
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: 20,
marginTop: 50
}
]}
> >
<SwiperFlatList <View
autoplay style={[
autoplayDelay={10} {
autoplayLoop={true} width: "100%",
index={0} borderBottomWidth: 1,
useReactNativeGestureHandler={true} borderBottomColor: colors.primary.border,
showPagination paddingTop: insets.top + 10,
data={strings.introData} paddingBottom: insets.top + 10,
paginationActiveColor={colors.primary.accent} flexGrow: 1
paginationStyleItem={{ },
width: 10, isTablet && {
height: 5, width: width / 2,
marginRight: 4, alignSelf: "center",
marginLeft: 4 borderWidth: 1,
}} borderColor: colors.primary.border,
paginationDefaultColor={colors.primary.border} borderRadius: 20,
renderItem={renderItem} marginTop: 50
/> }
]}
>
<SwiperFlatList
autoplay
autoplayDelay={10}
autoplayLoop={true}
index={0}
useReactNativeGestureHandler={true}
showPagination
data={strings.introData}
paginationActiveColor={colors.primary.accent}
paginationStyleItem={{
width: 10,
height: 5,
marginRight: 4,
marginLeft: 4
}}
paginationDefaultColor={colors.primary.border}
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>
); );
}; };

View File

@@ -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>
);
};

View File

@@ -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>
)
);
};

View File

@@ -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>
);
};

View File

@@ -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'
}
*/

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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>
)
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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,50 +203,48 @@ export const BuyPlan = (props: {
}} }}
/> />
<View> <Heading
<Heading style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
size={AppFontSize.xs}
>
{is5YearPlanSelected
? `This is a one time purchase, no subscription.`
: `Cancel anytime, subscription auto-renews.`}
</Heading>
<Heading
style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
size={AppFontSize.xs}
>
By joining you agree to our{" "}
<Text
style={{ style={{
textAlign: "center" textDecorationLine: "underline"
}}
onPress={() => {
openLinkInBrowser("https://notesnook.com/privacy");
}} }}
color={colors.secondary.paragraph}
size={SIZE.xs}
> >
{is5YearPlanSelected privacy policy
? `This is a one time purchase, no subscription.` </Text>{" "}
: `Cancel anytime, subscription auto-renews.`} and{" "}
</Heading> <Text
<Heading
style={{ style={{
textAlign: "center" textDecorationLine: "underline"
}}
onPress={() => {
openLinkInBrowser("https://notesnook.com/tos");
}} }}
color={colors.secondary.paragraph}
size={SIZE.xs}
> >
By joining you agree to our{" "} terms of use
<Text </Text>
style={{ .
textDecorationLine: "underline" </Heading>
}}
onPress={() => {
openLinkInBrowser("https://notesnook.com/privacy");
}}
>
privacy policy
</Text>{" "}
and{" "}
<Text
style={{
textDecorationLine: "underline"
}}
onPress={() => {
openLinkInBrowser("https://notesnook.com/tos");
}}
>
terms of use
</Text>
.
</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,

View File

@@ -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>

View File

@@ -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

View File

@@ -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" />
</> </>
} }

View File

@@ -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;

View File

@@ -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>
); );

View File

@@ -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}

View File

@@ -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<

View File

@@ -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,22 +91,31 @@ function getMontlySub() {
} }
async function loadProductsAndSubs() { async function loadProductsAndSubs() {
if (!subs || subs.length === 0) { try {
subs = await RNIap.getSubscriptions({ if (!subs || subs.length === 0) {
skus: itemSkus subs = await RNIap.getSubscriptions({
}); skus: itemSkus
} });
console.log("SUBS", subs);
}
if (!products || products.length === 0) { if (!products || products.length === 0) {
products = await RNIap.getProducts({ products = await RNIap.getProducts({
skus: ["notesnook.pro.5year", "notesnook.believer.5year"] skus: ["notesnook.pro.5year", "notesnook.believer.5year"]
}); });
} }
return { return {
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
}; };

View File

@@ -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;

View File

@@ -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");

View File

@@ -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'

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -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`
}; };