mobile: add plan preview in settings

This commit is contained in:
Ammar Ahmed
2025-08-01 11:19:22 +05:00
committed by Abdullah Atta
parent 3480b8c5da
commit 0a5bdaaa7f
4 changed files with 152 additions and 81 deletions

View File

@@ -21,7 +21,8 @@ export default function useFeatureManager() {
"defaultNotebookAndTag",
"defaultSidebarTab",
"customToolbarPreset",
"disableTrashCleanup"
"disableTrashCleanup",
"fullOfflineMode"
]);
const user = useUserStore((state) => state.user);
@@ -55,33 +56,42 @@ export default function useFeatureManager() {
useDragState.getState().setPreset("default");
}
if (!features.fullOfflineMode.isAllowed) {
SettingsService.setProperty("offlineMode", false);
}
if (!features.defaultNotebookAndTag.isAllowed) {
db.settings.setDefaultNotebook(undefined);
db.settings.setDefaultTag(undefined);
}
const isAppLocked = useUserStore.getState().appLocked;
let unsub: () => void;
if (isAppLocked) {
unsub = useUserStore.subscribe((state) => {
if (!state.appLocked && !features?.appLock?.isAllowed) {
unsub();
SettingsService.setProperty("appLockEnabled", false);
setTimeout(() => {
presentDialog({
title: "App Lock Disabled",
paragraph: features?.appLock?.error,
positiveText: strings.upgrade(),
negativeText: strings.cancel(),
positivePress: async () => {
eSendEvent(eCloseSimpleDialog);
Navigation.navigate("PayWall", {
context: "logged-in"
});
}
});
}, 1000);
}
});
}
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked && !features?.appLock?.isAllowed) {
unsub();
SettingsService.setProperty("appLockEnabled", false);
setTimeout(() => {
presentDialog({
title: "App Lock Disabled",
paragraph: features?.appLock?.error,
positiveText: strings.upgrade(),
negativeText: strings.cancel(),
positivePress: async () => {
eSendEvent(eCloseSimpleDialog);
Navigation.navigate("PayWall", {
context: "logged-in"
});
}
});
}, 1000);
}
});
return () => {
unsub();
unsub?.();
};
}, [features]);

View File

@@ -81,14 +81,15 @@ export const settingsGroups: SettingSection[] = [
hidden: (current) => !current,
name: (current) => {
const user = (current as User) || useUserStore.getState().user;
if (!user) return strings.subscribeToPro();
if (!user) return strings.upgradePlan();
const isBasic = user.subscription?.type === SUBSCRIPTION_STATUS.BASIC;
const isTrial = user.subscription?.type === SUBSCRIPTION_STATUS.TRIAL;
return isBasic || !user.subscription?.type
? strings.subscribeToPro()
? strings.upgradePlan()
: isTrial
? strings.trialStarted()
: strings.subDetails();
``;
},
type: "component",
component: "subscription",

View File

@@ -21,7 +21,6 @@ import { strings } from "@notesnook/intl";
import React from "react";
import { Linking, Platform, View } from "react-native";
import Config from "react-native-config";
import { Button } from "../../components/ui/button";
import { usePricing } from "../../hooks/use-pricing";
import {
eSendEvent,
@@ -32,8 +31,6 @@ import PremiumService from "../../services/premium";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
export const Subscription = () => {
const user = useUserStore((state) => state.user);
const monthlyPlan = usePricing("monthly");
@@ -95,7 +92,7 @@ export const Subscription = () => {
return (
<View>
{isNotPro ? (
{/* {isNotPro ? (
<Button
height={40}
style={{
@@ -121,9 +118,9 @@ export const Subscription = () => {
: `${strings.getPro()} (${getPrice() || "$4.49"} / mo)`
}
/>
) : null}
) : null} */}
{subscriptionProviderInfo &&
{/* {subscriptionProviderInfo &&
user.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM_EXPIRED &&
user.subscription?.type !== SUBSCRIPTION_STATUS.BASIC ? (
<Button
@@ -143,7 +140,7 @@ export const Subscription = () => {
height={30}
type="secondaryAccented"
/>
) : null}
) : null} */}
</View>
);
};

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/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import dayjs from "dayjs";
@@ -27,16 +28,15 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { presentDialog } from "../../components/dialog/functions";
import AppIcon from "../../components/ui/AppIcon";
import { Button } from "../../components/ui/button";
import { TimeSince } from "../../components/ui/time-since";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import Navigation from "../../services/navigation";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS_STRINGS } from "../../utils/constants";
import { AppFontSize } from "../../utils/size";
import { SectionItem } from "./section-item";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import { SectionItem } from "./section-item";
export const getTimeLeft = (t2) => {
let daysRemaining = dayjs(t2).diff(dayjs(), "days");
@@ -60,8 +60,8 @@ const ProfilePicPlaceholder = (props) => {
style={{
backgroundColor: colors.primary.shade,
borderRadius: 100,
width: 100,
height: 100,
width: 80,
height: 80,
alignItems: "center",
justifyContent: "center"
}}
@@ -123,6 +123,8 @@ const SettingsUserSection = ({ item }) => {
const { isInternetReachable } = useNetInfo();
const isOffline = !isInternetReachable;
const userProfile = useUserStore((state) => state.profile);
const used = 50;
const total = 100;
return (
<>
@@ -131,7 +133,7 @@ const SettingsUserSection = ({ item }) => {
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
paddingTop: 50,
paddingTop: 25,
borderBottomWidth: 1,
paddingBottom: 20,
borderColor: colors.primary.border
@@ -140,23 +142,23 @@ const SettingsUserSection = ({ item }) => {
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
width: "100%"
}}
>
<View
style={{
flexDirection: "column",
alignItems: "center",
width: "100%"
flexDirection: "row",
width: "100%",
gap: DefaultAppStyles.GAP,
alignItems: "center"
}}
>
<View
style={{
borderWidth: 2,
borderRadius: 100,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
borderColor: colors.primary.accent
borderColor: colors.primary.accent,
alignSelf: "flex-start"
}}
>
{userProfile?.profilePicture ? (
@@ -169,9 +171,9 @@ const SettingsUserSection = ({ item }) => {
uri: userProfile?.profilePicture
}}
style={{
width: 100,
height: 100,
borderRadius: 100
width: 80,
height: 80,
borderRadius: 80
}}
/>
</TouchableOpacity>
@@ -180,22 +182,7 @@ const SettingsUserSection = ({ item }) => {
)}
</View>
<View
style={{
alignItems: "center"
}}
>
{premium ? (
<Heading
color={colors.primary.accent}
size={AppFontSize.sm}
>
{SUBSCRIPTION_STATUS_STRINGS[
user.subscription?.type
]?.toUpperCase() || strings.basic()}
</Heading>
) : null}
<View>
<Paragraph
onPress={() => {
presentDialog({
@@ -219,12 +206,12 @@ const SettingsUserSection = ({ item }) => {
});
}}
color={colors.primary.heading}
size={AppFontSize.lg}
size={AppFontSize.md}
>
{userProfile?.fullName
? userProfile.fullName + " "
: strings.setYourName() + " "}
<AppIcon name="pencil" size={AppFontSize.lg} />
<AppIcon name="pencil" size={AppFontSize.md} />
</Paragraph>
<Paragraph
@@ -236,8 +223,7 @@ const SettingsUserSection = ({ item }) => {
<Paragraph
style={{
flexWrap: "wrap",
marginTop: DefaultAppStyles.GAP_VERTICAL_SMALL
flexWrap: "wrap"
}}
size={AppFontSize.xs}
color={colors.secondary.heading}
@@ -255,28 +241,105 @@ const SettingsUserSection = ({ item }) => {
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
/>{" "}
ago
{isOffline ? ` (${strings.offline()})` : ""}
</>
) : (
strings.never()
)}{" "}
<Icon
name="checkbox-blank-circle"
size={11}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
: isOffline
? colors.static.orange
: colors.success.icon
}
/>
)}
</Paragraph>
</View>
</View>
</View>
<View
style={{
paddingVertical: DefaultAppStyles.GAP_SMALL,
gap: DefaultAppStyles.GAP_VERTICAL,
borderRadius: 10
}}
>
<View
style={{
gap: DefaultAppStyles.GAP_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.xxs}>
{strings.storage()}
</Paragraph>
<Paragraph size={AppFontSize.xxs}>
50/100MB {strings.used()}
</Paragraph>
</View>
<View
style={{
backgroundColor: colors.secondary.background,
width: "100%",
height: 5,
borderRadius: 10
}}
>
<View
style={{
backgroundColor: colors.static.black,
height: 5,
width: `${(used / total) * 100}%`,
borderRadius: 10
}}
/>
</View>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
borderRadius: 10
}}
>
<View>
<Paragraph size={AppFontSize.sm}>
{strings.freePlan()}
</Paragraph>
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xxxs}
>
{strings.viewAllLimits()}{" "}
<AppIcon name="information" size={AppFontSize.xxxs} />
</Paragraph>
</View>
<Button
title={strings.upgradePlan()}
onPress={() => {
Navigation.navigate("PayWall", {
context: "logged-in",
canGoBack: true
});
}}
type="accent"
fontSize={AppFontSize.xs}
style={{
paddingHorizontal: DefaultAppStyles.GAP_SMALL,
height: "auto",
paddingVertical: DefaultAppStyles.GAP_SMALL
}}
/>
</View>
</View>
</View>
{item.sections.map((item) => (