mobile: pricing fixes

This commit is contained in:
Ammar Ahmed
2025-09-03 10:54:01 +05:00
committed by Abdullah Atta
parent dc1498d672
commit 7dddebef2e
12 changed files with 118 additions and 68 deletions

View File

@@ -132,7 +132,8 @@ function ReorderableList<T extends { id: string }>({
);
function getOrderedItems() {
const items: T[] = customizableSidebarFeature?.isAllowed ? data : [];
if (!customizableSidebarFeature?.isAllowed) return data;
const items: T[] = [];
itemOrderState.forEach((id) => {
const item = data.find((i) => i.id === id);
if (!item) return;
@@ -183,7 +184,6 @@ function ReorderableList<T extends { id: string }>({
dragging: true
});
}}
disableVirtualization
itemsDraggable={disableDefaultDrag ? dragging : true}
lockItemDragsToMainAxis
onItemReorder={async ({ fromIndex, fromItem, toIndex, toItem }) => {

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFeaturesTable } from "@notesnook/common";
import { EV, EVENTS, Plan, SubscriptionPlan, User } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
@@ -37,6 +38,15 @@ import { SafeAreaView } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { WebView } from "react-native-webview";
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, { PricingPlan } from "../../hooks/use-pricing-plans";
import Navigation, { NavigationProps } from "../../services/navigation";
@@ -44,28 +54,16 @@ import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize } 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";
import { db } from "../../common/database";
import { SvgView } from "../ui/svg";
import {
ANDROID_POLICE_SVG,
APPLE_INSIDER_PNG,
FREEDOM_PRESS_SVG,
ITS_FOSS_NEWS_PNG,
NESS_LABS_PNG,
PRIVACY_GUIDES_SVG,
TECHLORE_SVG,
XDA_SVG
} from "../../assets/images/assets";
import { EV, EVENTS, Plan, SubscriptionPlan, User } from "@notesnook/core";
import { AuthMode } from "../auth/common";
const Steps = {
select: 1,
@@ -944,7 +942,7 @@ const PricingPlanCard = ({
const price = pricingPlans?.getPrice(
product as RNIap.Subscription,
1,
pricingPlans.hasTrialOffer(plan.id, product?.productId) ? 1 : 0,
annualBilling
);

View File

@@ -16,6 +16,7 @@ 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 { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
@@ -27,7 +28,6 @@ import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { MenuItemsList } from "../../utils/menu-items";
import { DefaultAppStyles } from "../../utils/styles";
import ReorderableList from "../list/reorderable-list";
import { MenuItemProperties } from "../sheets/menu-item-properties";
@@ -36,6 +36,7 @@ import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { SideMenuHeader } from "./side-menu-header";
import { MenuItemsList } from "../../utils/menu-items";
const pro = {
title: strings.upgradePlan(),
@@ -59,7 +60,7 @@ export function SideMenuHome() {
state.hiddenItems["routes"]
]);
const subscriptionType = useUserStore(
(state) => state.user?.subscription?.type
(state) => state.user?.subscription?.plan
);
const user = useUserStore.getState().user;
@@ -139,8 +140,9 @@ export function SideMenuHome() {
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
{subscriptionType === SUBSCRIPTION_STATUS.TRIAL ||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ||
{((subscriptionType === SUBSCRIPTION_STATUS.TRIAL ||
subscriptionType === SUBSCRIPTION_STATUS.BASIC) &&
subscriptionType === SubscriptionPlan.FREE) ||
!user ? (
<Button
title={pro.title}

View File

@@ -53,12 +53,21 @@ export const Walkthrough = ({
justifyContent: "center",
alignItems: "center",
padding: DefaultAppStyles.GAP,
paddingBottom: 0
paddingBottom: DefaultAppStyles.GAP * 2,
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
{step.walkthroughItem(colors)}
{step.title ? <Heading>{step.title}</Heading> : null}
{step.title ? (
<Heading
style={{
textAlign: "center"
}}
>
Notesnook Free plan activated
</Heading>
) : null}
{step.text ? (
<Paragraph
style={{
@@ -74,8 +83,7 @@ export const Walkthrough = ({
{step.actionButton && (
<Button
style={{
height: 30,
marginTop: DefaultAppStyles.GAP_VERTICAL
height: 30
}}
textStyle={{
textDecorationLine: "underline"
@@ -89,11 +97,6 @@ export const Walkthrough = ({
)}
<Button
style={{
borderRadius: 100,
height: 40,
marginTop: DefaultAppStyles.GAP
}}
onPress={async () => {
switch (step.button?.type) {
case "next":

View File

@@ -40,6 +40,10 @@ import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
import { useUserStore } from "../../stores/use-user-store";
import { planToId, SubscriptionPlan } from "@notesnook/core";
import { planToDisplayName } from "../../utils/constants";
import AppIcon from "../ui/AppIcon";
export type TStep = {
text?: string;
@@ -356,7 +360,6 @@ const Support = () => {
alignItems: "center"
}}
>
<SvgView src={SUPPORT_SVG()} />
<Heading>{strings.prioritySupport()}</Heading>
<Paragraph
style={{
@@ -384,21 +387,6 @@ const Support = () => {
title={strings.joinDiscord()}
/>
<Button
style={{
justifyContent: "flex-start",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
width: "90%"
}}
onPress={() => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
}}
icon="telegram"
type="secondary"
title={strings.joinTelegram()}
/>
<Button
style={{
justifyContent: "flex-start",
@@ -415,7 +403,7 @@ const Support = () => {
marginBottom: DefaultAppStyles.GAP_VERTICAL,
width: "90%"
}}
icon="mail"
icon="email"
type="secondary"
title={strings.emailSupport()}
/>
@@ -427,18 +415,15 @@ const prouser: { id: string; steps: TStep[] } = {
id: "prouser",
steps: [
{
title: strings.welcomeToNotesnookPro(),
title: strings.welcomeToPlan(
planToDisplayName(
useUserStore.getState().user?.subscription?.plan as SubscriptionPlan
)
),
text: strings.thankYouPrivacy(),
walkthroughItem: (colors) => (
<SvgView src={LAUNCH_ROCKET(colors.primary.paragraph)} />
<AppIcon name="check" color={colors.primary.accent} size={50} />
),
button: {
type: "next",
title: strings.next()
}
},
{
walkthroughItem: () => <Support />,
button: {
type: "done",
title: strings.continue()

View File

@@ -22,6 +22,8 @@ import {
EVENTS,
EventManagerSubscription,
SYNC_CHECK_IDS,
SubscriptionPlan,
SubscriptionType,
SyncStatusEvent,
User
} from "@notesnook/core";
@@ -224,7 +226,11 @@ const onUserEmailVerified = async () => {
const onUserSubscriptionStatusChanged = async (
subscription: User["subscription"]
) => {
if (!PremiumService.get() && subscription.type === 5) {
if (
!PremiumService.get() &&
(subscription.type === SubscriptionType.PREMIUM ||
subscription.plan != SubscriptionPlan.FREE)
) {
PremiumService.subscriptions.clear();
Walkthrough.present("prouser", false, true);
}

View File

@@ -86,9 +86,7 @@ const pricingPlans: PricingPlan[] = [
subscriptionSkuList: [
"notesnook.pro.monthly",
"notesnook.pro.yearly",
"notesnook.pro.monthly.tier2",
"notesnook.pro.yearly.tier2",
"notesnook.pro.monthly.tier3",
"notesnook.pro.yearly.tier3",
// no trial
"notesnook.pro.monthly.nt",
@@ -281,8 +279,11 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
: (product as Plan).price.gross
}`;
const pricingPhaseListItem = (product as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails?.[0]?.pricingPhases?.pricingPhaseList?.[1];
const pricingPhaseListItem =
(product as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
?.pricingPhases?.pricingPhaseList?.[1] ||
(product as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
?.pricingPhases?.pricingPhaseList?.[0];
return (
pricingPhaseListItem?.formattedPrice ||
@@ -534,10 +535,18 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
if (!p1 || !p2) return 0;
if (Platform.OS === "android") {
const androidPricingPhase1 = (p1 as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails?.[0].pricingPhases?.pricingPhaseList?.[1];
const androidPricingPhase2 = (p2 as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails?.[0].pricingPhases?.pricingPhaseList?.[1];
const androidPricingPhase1 =
(p1 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[1] ||
(p1 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[0];
const androidPricingPhase2 =
(p2 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[1] ||
(p2 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[0];
if (!androidPricingPhase1 || !androidPricingPhase2) return 0;
return getDiscountValue(
androidPricingPhase1.priceAmountMicros,

View File

@@ -40,6 +40,8 @@ import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { SectionItem } from "./section-item";
import { planToDisplayName } from "../../utils/constants";
import { SubscriptionType } from "@notesnook/core";
export const getTimeLeft = (t2) => {
let daysRemaining = dayjs(t2).diff(dayjs(), "days");
@@ -118,6 +120,8 @@ const onChangePicture = () => {
});
};
const LONG_MAX = 9223372036854776000;
const SettingsUserSection = ({ item }) => {
const { colors } = useThemeColors();
const [user] = useUserStore((state) => [state.user]);
@@ -276,7 +280,10 @@ const SettingsUserSection = ({ item }) => {
{strings.storage()}
</Paragraph>
<Paragraph size={AppFontSize.xxs}>
{formatBytes(used)}/{formatBytes(total)} {strings.used()}
{formatBytes(used)}/
{total === LONG_MAX
? "Unlimited"
: formatBytes(total) + " " + strings.used()}
</Paragraph>
</View>
<View
@@ -317,7 +324,10 @@ const SettingsUserSection = ({ item }) => {
}}
>
<Paragraph size={AppFontSize.sm}>
{strings.freePlan()}
{user.subscription.plan === 0 &&
user.subscription.type === SubscriptionType.PREMIUM
? strings.proPlan()
: planToDisplayName(user.subscription.plan)}
</Paragraph>
<Paragraph
color={colors.secondary.paragraph}

View File

@@ -17,6 +17,8 @@ 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 { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { Platform } from "react-native";
import { getVersion } from "react-native-device-info";
@@ -63,6 +65,23 @@ export const itemSkus = [
"notesnook.believer.5year"
];
export function planToDisplayName(plan: SubscriptionPlan): string {
switch (plan) {
case SubscriptionPlan.FREE:
return strings.freePlan();
case SubscriptionPlan.ESSENTIAL:
return strings.essentialPlan();
case SubscriptionPlan.PRO:
return strings.proPlan();
case SubscriptionPlan.BELIEVER:
return strings.believerPlan();
case SubscriptionPlan.EDUCATION:
return strings.educationPlan();
default:
return strings.freePlan();
}
}
export const SUBSCRIPTION_STATUS = {
BASIC: 0,
TRIAL: 1,

View File

@@ -2372,6 +2372,10 @@ msgstr "Edit your full name"
msgid "Editor"
msgstr "Editor"
#: src/strings.ts:2573
msgid "Education plan"
msgstr "Education plan"
#: src/strings.ts:1480
msgid "Email"
msgstr "Email"
@@ -7055,6 +7059,10 @@ msgstr "Welcome back, {email}"
msgid "Welcome back!"
msgstr "Welcome back!"
#: src/strings.ts:2574
msgid "Welcome to Notesnook {plan} plan"
msgstr "Welcome to Notesnook {plan} plan"
#: src/strings.ts:2072
msgid "Welcome to Notesnook Pro"
msgstr "Welcome to Notesnook Pro"

View File

@@ -2361,6 +2361,10 @@ msgstr ""
msgid "Editor"
msgstr ""
#: src/strings.ts:2573
msgid "Education plan"
msgstr ""
#: src/strings.ts:1480
msgid "Email"
msgstr ""
@@ -7006,6 +7010,10 @@ msgstr ""
msgid "Welcome back!"
msgstr ""
#: src/strings.ts:2574
msgid "Welcome to Notesnook {plan} plan"
msgstr ""
#: src/strings.ts:2072
msgid "Welcome to Notesnook Pro"
msgstr ""

View File

@@ -2577,5 +2577,7 @@ Use this if changes from other devices are not appearing on this device. This wi
bestValue: () => t`Best value`,
planLimits: () => t`Plan limits`,
unlimited: () => t`Unlimited`,
fiveYearPlan: () => t`5 year plan (One time purchase)`
fiveYearPlan: () => t`5 year plan (One time purchase)`,
educationPlan: () => t`Education plan`,
welcomeToPlan: (plan: string) => t`Welcome to Notesnook ${plan}`
};