/*
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 .
*/
import { getFeaturesTable } from "@notesnook/common";
import {
EV,
EVENTS,
Plan,
SKUResponse,
SubscriptionPlan,
User
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
BackHandler,
Image,
NativeEventSubscription,
ScrollView,
Text,
TouchableOpacity,
useWindowDimensions,
View
} from "react-native";
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import { SafeAreaView } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
//@ts-ignore
import ToggleSwitch from "toggle-switch-react-native";
import {
ANDROID_POLICE_SVG,
APPLE_INSIDER_PNG,
ITS_FOSS_NEWS_PNG,
NESS_LABS_PNG,
PRIVACY_GUIDES_SVG,
TECHLORE_SVG,
XDA_SVG
} from "../../assets/images/assets";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import usePricingPlans, {
PlanOverView,
PricingPlan
} from "../../hooks/use-pricing-plans";
import Navigation, { NavigationProps } from "../../services/navigation";
import PremiumService from "../../services/premium";
import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { AuthMode } from "../auth/common";
import { Header } from "../header";
import { BuyPlan } from "../sheets/buy-plan";
import { Toast } from "../toast";
import AppIcon from "../ui/AppIcon";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
const Steps = {
select: 1,
buy: 2,
finish: 3,
buyWeb: 4
};
const PayWall = (props: NavigationProps<"PayWall">) => {
const isGithubRelease = Config.GITHUB_RELEASE === "true";
const routeParams = props.route.params;
const { width } = useWindowDimensions();
const isTablet = width > 600;
const { colors } = useThemeColors();
const pricingPlans = usePricingPlans({
planId: routeParams.state?.planId,
productId: routeParams.state?.productId,
onBuy: () => {
setStep(Steps.finish);
}
});
const [annualBilling, setAnnualBilling] = useState(
routeParams.state ? routeParams.state.billingType === "annual" : true
);
const [step, setStep] = useState(
routeParams.state ? Steps.buy : Steps.select
);
const isFocused = useNavigationFocus(props.navigation, {
onBlur: () => true,
onFocus: () => true
});
useEffect(() => {
if (routeParams.state) {
if (routeParams.state?.planId) {
pricingPlans.selectPlan(
routeParams.state?.planId,
routeParams.state?.productId
);
}
setStep(Steps.buy);
}
}, [routeParams.state]);
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]);
useEffect(() => {
const sub = EV.subscribe(
EVENTS.userSubscriptionUpdated,
(sub: User["subscription"]) => {
if (sub.plan === SubscriptionPlan.FREE) return;
if (routeParams.context === "signup") {
Navigation.replace("FluidPanelsView", {});
} else {
Navigation.goBack();
}
}
);
return () => {
sub?.unsubscribe();
};
}, []);
const is5YearPlanSelected = (
isGithubRelease
? (pricingPlans.selectedProduct as Plan)?.period
: (pricingPlans.selectedProduct as RNIap.Product)?.productId
)?.includes("5");
return (
{step === Steps.finish ? null : routeParams.context === "signup" &&
step === Steps.select ? (
{
Navigation.replace("FluidPanelsView", {});
}}
/>
) : (
{
if (step === Steps.buy) {
setStep(Steps.select);
return;
}
if (routeParams.context === "signup") {
Navigation.replace("FluidPanelsView", {});
} else {
Navigation.goBack();
}
}}
title={
step === Steps.buy || step === Steps.buyWeb
? pricingPlans.userCanRequestTrial
? strings.tryPlanForFree(
pricingPlans.currentPlan?.name as string
)
: strings.plan(pricingPlans.currentPlan?.name as string)
: ""
}
/>
)}
{step === Steps.select ? (
<>
{pricingPlans.isSubscribed()
? strings.changePlan()
: strings.notesnookPlans[0]() + " "}
{pricingPlans.isSubscribed() ? null : (
{strings.notesnookPlans[1]()}
)}
{strings.readyToTakeNextStep()}
{
setAnnualBilling((state) => !state);
}}
style={{
flexDirection: "row",
alignItems: "center",
gap: 15,
width: "100%",
justifyContent: "center",
paddingVertical: 12
}}
activeOpacity={0.9}
>
{strings.monthly()}
{
setAnnualBilling((state) => !state);
}}
/>
{strings.yearly()}{" "}
({strings.percentOff("15")})
{pricingPlans.pricingPlans.map((plan) =>
plan.id !== "free" ? (
{
if (!pricingPlans.user) {
Navigation.navigate("Auth", {
mode: AuthMode.login,
state: {
planId: pricingPlans.currentPlan?.id,
productId:
(
pricingPlans.selectedProduct as RNIap.Subscription
)?.productId ||
(pricingPlans.selectedProduct as Plan)?.period,
billingType: annualBilling ? "annual" : "monthly"
}
});
return;
}
setStep(step);
}}
pricingPlans={pricingPlans}
annualBilling={annualBilling}
/>
) : null
)}
{
openLinkInBrowser(
"https://github.com/streetwriters/notesnook"
);
}}
activeOpacity={0.9}
style={{
padding: 16,
gap: 12,
alignItems: "center",
flexGrow: 1
}}
>
Open Source
{
openLinkInBrowser(
"https://github.com/streetwriters/notesnook/stargazers"
);
}}
activeOpacity={0.9}
style={{
padding: 16,
gap: 12,
alignItems: "center",
justifyContent: "center",
flexGrow: 1
}}
>
12.5K stars
{
openLinkInBrowser(
"https://www.privacyguides.org/en/notebooks/#notesnook"
);
}}
activeOpacity={0.9}
style={{
justifyContent: "center",
padding: 16,
gap: 12,
alignItems: "center",
flexGrow: 1
}}
>
{strings.recommendedByPrivacyGuides()}
{strings.featuredOn()}
{strings.comparePlans()}
{strings.faqs()}
{strings.checkoutFaqs.map((item) => (
))}
>
) : step === Steps.buy ? (
) : step === Steps.finish ? (
{is5YearPlanSelected
? strings.thankYouForPurchase()
: strings.thankYouForSubscribing()}
{strings.settingUpPlan()}
) : null}
);
};
const FAQItem = (props: { question: string; answer: string }) => {
const [expanded, setExpanded] = useState(false);
const { colors } = useThemeColors();
return (
{
setExpanded(!expanded);
}}
key={props.question}
>
{props.question}
{expanded ? (
{props.answer}
) : null}
);
};
const ComparePlans = React.memo(
(props: {
pricingPlans?: ReturnType;
setStep: (step: number) => void;
}) => {
const { colors } = useThemeColors();
const { width } = useWindowDimensions();
const isTablet = width > 600;
return (
{["Features", "Free", "Essential", "Pro", "Believer"].map(
(plan, index) => (
{plan}
)
)}
{getFeaturesTable().map((item, keyIndex) => {
return (
{item.map((featureItem, index) => (
{typeof featureItem === "string" ? (
{featureItem as string}
) : (
<>
{typeof featureItem.caption === "string" ||
typeof featureItem.caption === "number" ? (
{featureItem.caption === "infinity"
? "∞"
: featureItem.caption}
) : typeof featureItem.caption === "boolean" ? (
<>
{featureItem.caption === true ? (
) : (
)}
>
) : null}
>
)}
))}
);
})}
{["features", "free", "essential", "pro", "believer"].map(
(plan, index) => (
{plan !== "free" && plan !== "features" ? (
)
)}
);
},
() => true
);
ComparePlans.displayName = "ComparePlans";
const ReviewItem = (props: {
review: string;
user: string;
link: string;
userImage?: string;
}) => {
const { colors } = useThemeColors();
return (
{
openLinkInBrowser(props.link);
}}
style={{
textAlign: "center"
}}
size={AppFontSize.md}
>
{props.review}
{props.userImage ? (
) : null}
{props.user}
);
};
const PricingPlanCard = ({
plan,
pricingPlans,
annualBilling,
setStep
}: {
plan: PricingPlan;
pricingPlans?: ReturnType;
annualBilling?: boolean;
setStep: (step: number) => void;
}) => {
const { colors } = useThemeColors();
const [regionalDiscount, setRegionaDiscount] = useState();
const { width } = useWindowDimensions();
const isTablet = width > 600;
const product =
plan.subscriptions?.[
regionalDiscount?.sku ||
`notesnook.${plan.id}.${annualBilling ? "yearly" : "monthly"}`
];
const WebPlan = pricingPlans?.getWebPlan(
plan.id,
annualBilling ? "yearly" : "monthly"
);
const price = pricingPlans?.getPrice(
pricingPlans.isGithubRelease && WebPlan
? WebPlan
: (product as RNIap.Subscription),
pricingPlans.hasTrialOffer(plan.id, product?.productId) ? 1 : 0,
annualBilling
);
useEffect(() => {
if (pricingPlans?.isGithubRelease || !annualBilling) return;
pricingPlans
?.getRegionalDiscount(
plan.id,
pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: `notesnook.${plan.id}.${annualBilling ? "yearly" : "monthly"}`
)
.then((value) => {
setRegionaDiscount(value);
});
}, [annualBilling]);
useEffect(() => {
if (!annualBilling) {
setRegionaDiscount(undefined);
}
}, [annualBilling]);
const isSubscribed =
product?.productId &&
pricingPlans?.user?.subscription?.productId?.includes(plan.id) &&
pricingPlans.isSubscribed();
const isNotReady =
pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross);
return (
{
if (isNotReady) return;
const currentPlanSubscribed =
PremiumService.get() &&
(pricingPlans?.user?.subscription?.productId ===
(product as RNIap.Subscription)?.productId ||
pricingPlans?.user?.subscription?.productId.startsWith(
(product as RNIap.Subscription)?.productId
));
pricingPlans?.selectPlan(
plan.id,
currentPlanSubscribed
? `notesnook.${plan.id}.${
!(product as RNIap.Subscription)?.productId.includes("yearly")
? "yearly"
: "monthly"
}`
: pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: (product?.productId as string)
);
setStep(Steps.buy);
}}
style={{
...getElevationStyle(3),
backgroundColor: colors.primary.background,
borderWidth: 1,
borderColor:
plan.id === "pro" ? colors.primary.accent : colors.primary.border,
borderRadius: 10,
padding: 16,
width: isTablet ? undefined : "100%",
flexShrink: isTablet ? 1 : undefined,
flexDirection: "column",
justifyContent: "space-between",
gap: 6
}}
>
{regionalDiscount?.discount || WebPlan?.discount ? (
{strings.specialOffer()}{" "}
{strings.percentOff(
`${regionalDiscount?.discount || WebPlan?.discount?.amount}`
)}
) : null}
{plan.name}{" "}
{plan.recommended ? (
({strings.recommended()})
) : null}
{plan.description}
{strings.storage()}
{PlanOverView[plan.id as keyof typeof PlanOverView].storage}
{strings.fileSize()}
{PlanOverView[plan.id as keyof typeof PlanOverView].fileSize}
{strings.hdImages()}
{PlanOverView[plan.id as keyof typeof PlanOverView].hdImages
? strings.yes()
: strings.no()}
{pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross) ? (
) : (
{price || `${WebPlan?.price?.currency} ${WebPlan?.price?.gross}`}{" "}
/{strings.month()}
{!product && !WebPlan ? null : (
{annualBilling
? strings.billedAnnually(
pricingPlans?.getStandardPrice(
(product || WebPlan) as any
) as string
)
: strings.billedMonthly(
pricingPlans?.getStandardPrice(
(product || WebPlan) as any
) as string
)}
)}
{isSubscribed ? (
{strings.currentPlan()}
) : null}
)}
);
};
export default PayWall;