mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
mobile: introduce plan limits
This commit is contained in:
committed by
Abdullah Atta
parent
1be518510a
commit
dc08a39173
@@ -16,7 +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 { database } from "@notesnook/common";
|
||||
import { database, getFeatureLimit } from "@notesnook/common";
|
||||
import { logger as dbLogger, ICompressor } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import {
|
||||
@@ -73,6 +73,10 @@ export async function setupDatabase(password?: string) {
|
||||
tempStore: "memory",
|
||||
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL",
|
||||
password: key
|
||||
},
|
||||
maxNoteVersions: async () => {
|
||||
const limit = await getFeatureLimit("maxNoteVersions");
|
||||
return typeof limit.caption === "number" ? limit.caption : undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ export const Login = ({ changeMode }) => {
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
width: 250
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
|
||||
@@ -16,6 +16,9 @@ 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 { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Image,
|
||||
@@ -25,15 +28,13 @@ import {
|
||||
View
|
||||
} from "react-native";
|
||||
import { Image as ImageType } from "react-native-image-crop-picker";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
||||
import { presentSheet, ToastManager } from "../../../services/event-manager";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Notice } from "../../ui/notice";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
export default function AttachImage({
|
||||
response,
|
||||
@@ -44,6 +45,7 @@ export default function AttachImage({
|
||||
onAttach: ({ compress }: { compress: boolean }) => void;
|
||||
close: ((ctx?: string | undefined) => void) | undefined;
|
||||
}) {
|
||||
const fullQualityImagesResult = useIsFeatureAvailable("fullQualityImages");
|
||||
const { colors } = useThemeColors();
|
||||
const [compress, setCompress] = useState(true);
|
||||
|
||||
@@ -104,21 +106,42 @@ export default function AttachImage({
|
||||
alignSelf: "center",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
opacity: fullQualityImagesResult?.isAllowed ? 1 : 0.5
|
||||
}}
|
||||
onPress={() => {
|
||||
if (!fullQualityImagesResult?.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: fullQualityImagesResult?.error,
|
||||
type: "info",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
setCompress(!compress);
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size={AppFontSize.lg}
|
||||
name={compress ? "checkbox-marked" : "checkbox-blank-outline"}
|
||||
color={compress ? colors.primary.accent : colors.primary.icon}
|
||||
color={
|
||||
compress && fullQualityImagesResult?.isAllowed
|
||||
? colors.primary.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25
|
||||
}}
|
||||
onPress={() => {
|
||||
if (!fullQualityImagesResult?.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: fullQualityImagesResult?.error,
|
||||
type: "info",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
setCompress(!compress);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -30,6 +30,10 @@ import { fluidTabsRef } from "../../utils/global-refs";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../sheets/paywall";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
|
||||
interface ReorderableListProps<T extends { id: string }>
|
||||
extends Omit<DraxListProps<T>, "renderItem" | "data" | "renderItemContent"> {
|
||||
@@ -59,6 +63,9 @@ function ReorderableList<T extends { id: string }>({
|
||||
const [hiddenItemsState, setHiddenItems] = useState(hiddenItems);
|
||||
const dragging = useSideBarDraggingStore((state) => state.dragging);
|
||||
const listRef = useRef<FlatList | null>(null);
|
||||
const customizableSidebarFeature = useIsFeatureAvailable(
|
||||
"customizableSidebar"
|
||||
);
|
||||
|
||||
if (dragging) {
|
||||
fluidTabsRef.current?.lock();
|
||||
@@ -125,7 +132,7 @@ function ReorderableList<T extends { id: string }>({
|
||||
);
|
||||
|
||||
function getOrderedItems() {
|
||||
const items: T[] = [];
|
||||
const items: T[] = customizableSidebarFeature?.isAllowed ? data : [];
|
||||
itemOrderState.forEach((id) => {
|
||||
const item = data.find((i) => i.id === id);
|
||||
if (!item) return;
|
||||
@@ -158,15 +165,40 @@ function ReorderableList<T extends { id: string }>({
|
||||
}
|
||||
}}
|
||||
longPressDelay={500}
|
||||
onItemDragStart={() =>
|
||||
onItemDragStart={async () => {
|
||||
if (
|
||||
customizableSidebarFeature &&
|
||||
!customizableSidebarFeature?.isAllowed
|
||||
) {
|
||||
ToastManager.show({
|
||||
message: customizableSidebarFeature?.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => PaywallSheet.present(customizableSidebarFeature)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
useSideBarDraggingStore.setState({
|
||||
dragging: true
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
disableVirtualization
|
||||
itemsDraggable={disableDefaultDrag ? dragging : true}
|
||||
lockItemDragsToMainAxis
|
||||
onItemReorder={({ fromIndex, fromItem, toIndex, toItem }) => {
|
||||
onItemReorder={async ({ fromIndex, fromItem, toIndex, toItem }) => {
|
||||
if (
|
||||
customizableSidebarFeature &&
|
||||
!customizableSidebarFeature?.isAllowed
|
||||
) {
|
||||
ToastManager.show({
|
||||
message: customizableSidebarFeature.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => PaywallSheet.present(customizableSidebarFeature)
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newOrder = getOrderedItems().map((item) => item.id);
|
||||
const element = newOrder.splice(fromIndex, 1)[0];
|
||||
if (toIndex === 0) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { FlashList } from "@shopify/flash-list";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { db } from "../../common/database";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
@@ -38,6 +38,8 @@ import NativeTooltip from "../../utils/tooltip";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../sheets/paywall";
|
||||
|
||||
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -109,6 +111,25 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
[note]
|
||||
);
|
||||
|
||||
const onPress = React.useCallback(async () => {
|
||||
const colorTagsAvailable = await isFeatureAvailable("colors");
|
||||
if (!colorTagsAvailable.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: colorTagsAvailable.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(colorTagsAvailable);
|
||||
ToastManager.hide();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColorPicker
|
||||
@@ -133,10 +154,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
>
|
||||
{!colorNotes || !colorNotes.length ? (
|
||||
<Button
|
||||
onPress={async () => {
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
setVisible(true);
|
||||
}}
|
||||
onPress={onPress}
|
||||
buttonType={{
|
||||
text: colors.primary.accent
|
||||
}}
|
||||
@@ -172,10 +190,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
||||
marginRight: 5
|
||||
}}
|
||||
type="secondary"
|
||||
onPress={() => {
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
setVisible(true);
|
||||
}}
|
||||
onPress={onPress}
|
||||
>
|
||||
<Icon
|
||||
testID="icon-plus"
|
||||
|
||||
@@ -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 { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import {
|
||||
ContentBlock,
|
||||
Note,
|
||||
@@ -33,12 +34,12 @@ import { db } from "../../../common/database";
|
||||
import { useDBItem } from "../../../hooks/use-db-item";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
const ListNoteItem = ({
|
||||
id,
|
||||
@@ -146,6 +147,7 @@ export default function LinkNote(props: {
|
||||
onLinkCreated: () => void;
|
||||
close?: (ctx?: string) => void;
|
||||
}) {
|
||||
const blockLinking = useIsFeatureAvailable("blockLinking");
|
||||
const { colors } = useThemeColors();
|
||||
const query = useRef<string>();
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
@@ -327,7 +329,31 @@ export default function LinkNote(props: {
|
||||
keyboardShouldPersistTaps="handled"
|
||||
windowSize={3}
|
||||
keyExtractor={(item) => item.id}
|
||||
data={nodes}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: DefaultAppStyles.GAP,
|
||||
borderRadius: defaultBorderRadius,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.secondary.border,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
{blockLinking?.error}
|
||||
</Paragraph>
|
||||
<Button
|
||||
title={strings.upgradePlan()}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
type="accent"
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
data={blockLinking?.isAllowed ? nodes : []}
|
||||
/>
|
||||
) : (
|
||||
<FlatList
|
||||
|
||||
@@ -21,7 +21,11 @@ import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { SideMenuItem } from "../../../utils/menu-items";
|
||||
@@ -31,6 +35,8 @@ import { useSideBarDraggingStore } from "../../side-menu/dragging-store";
|
||||
import AppIcon from "../../ui/AppIcon";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../paywall";
|
||||
export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
@@ -44,18 +50,50 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
{[
|
||||
{
|
||||
title: strings.setAsHomepage(),
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const customHomepageFeature = await isFeatureAvailable(
|
||||
"customHomepage"
|
||||
);
|
||||
if (!customHomepageFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: customHomepageFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(customHomepageFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
SettingsService.setProperty("homepageV2", {
|
||||
id: item.id,
|
||||
type: "default"
|
||||
});
|
||||
eSendEvent(eCloseSheet);
|
||||
},
|
||||
icon: "home-outline"
|
||||
icon: "home-outline",
|
||||
type: "switch",
|
||||
state: SettingsService.getProperty("homepageV2")?.id === item.id
|
||||
},
|
||||
{
|
||||
title: strings.reorder(),
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const sidebarReorderFeature = await isFeatureAvailable(
|
||||
"customizableSidebar"
|
||||
);
|
||||
if (!sidebarReorderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: sidebarReorderFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(sidebarReorderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
useSideBarDraggingStore.setState({
|
||||
dragging: true
|
||||
});
|
||||
@@ -79,12 +117,28 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
|
||||
item.onPress();
|
||||
}}
|
||||
>
|
||||
<AppIcon
|
||||
color={colors.secondary.icon}
|
||||
name={item.icon}
|
||||
size={AppFontSize.xl}
|
||||
/>
|
||||
<Paragraph>{item.title}</Paragraph>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
>
|
||||
<AppIcon
|
||||
color={colors.secondary.icon}
|
||||
name={item.icon}
|
||||
size={AppFontSize.xl}
|
||||
/>
|
||||
<Paragraph>{item.title}</Paragraph>
|
||||
</View>
|
||||
{item.type === "switch" && item.state ? (
|
||||
<AppIcon
|
||||
name="check"
|
||||
size={AppFontSize.lg}
|
||||
color={colors.primary.accent}
|
||||
style={{ marginLeft: "auto" }}
|
||||
/>
|
||||
) : null}
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -27,7 +27,8 @@ import NotebookScreen from "../../../screens/notebook";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
presentSheet
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import {
|
||||
createNotebookTreeStores,
|
||||
@@ -44,6 +45,8 @@ import { NotebookItem } from "../../side-menu/notebook-item";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../paywall";
|
||||
|
||||
const {
|
||||
useNotebookExpandedStore,
|
||||
@@ -167,7 +170,21 @@ export const Notebooks = (props: {
|
||||
height: 30
|
||||
}}
|
||||
name="plus"
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
const notebooksFeature = await isFeatureAvailable("notebooks");
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddNotebookSheet.present(undefined, props.rootNotebook, "local");
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { View, Text } from "react-native";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { FeatureId, FeatureResult } from "@notesnook/common";
|
||||
|
||||
export default function PaywallSheet() {
|
||||
export default function PaywallSheet<Tid extends FeatureId>(props: {
|
||||
feature: FeatureResult<Tid>;
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -20,8 +23,8 @@ export default function PaywallSheet() {
|
||||
);
|
||||
}
|
||||
|
||||
PaywallSheet.present = () => {
|
||||
PaywallSheet.present = <Tid extends FeatureId>(feature: FeatureResult<Tid>) => {
|
||||
presentSheet({
|
||||
component: <PaywallSheet />
|
||||
component: <PaywallSheet feature={feature} />
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,6 +56,8 @@ import {
|
||||
import { useSideBarDraggingStore } from "./dragging-store";
|
||||
import { Button } from "../ui/button";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../sheets/paywall";
|
||||
const renderScene = SceneMap({
|
||||
home: SideMenuHome,
|
||||
notebooks: SideMenuNotebooks,
|
||||
@@ -347,10 +349,23 @@ const TabBar = (
|
||||
testID="sidebar-add-button"
|
||||
size={AppFontSize.lg - 2}
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
if (props.navigationState.index === 1) {
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
return;
|
||||
}
|
||||
|
||||
AddNotebookSheet.present();
|
||||
} else {
|
||||
const tagsFeature = await isFeatureAvailable("tags");
|
||||
if (!tagsFeature.isAllowed) {
|
||||
PaywallSheet.present(tagsFeature);
|
||||
return;
|
||||
}
|
||||
presentDialog({
|
||||
title: strings.addTag(),
|
||||
paragraph: strings.addTagDesc(),
|
||||
|
||||
@@ -18,32 +18,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { Fragment } from "react";
|
||||
import React from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { DraxProvider, DraxScrollView } from "react-native-drax";
|
||||
import { db } from "../../common/database";
|
||||
import Navigation from "../../services/navigation";
|
||||
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";
|
||||
import { Button } from "../ui/button";
|
||||
import { ColorSection } from "./color-section";
|
||||
import { MenuItem } from "./menu-item";
|
||||
import { PinnedSection } from "./pinned-section";
|
||||
import { SideMenuHeader } from "./side-menu-header";
|
||||
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { eOpenPremiumDialog } from "../../utils/events";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { Button } from "../ui/button";
|
||||
import { MenuItemProperties } from "../sheets/menu-item-properties";
|
||||
|
||||
const pro = {
|
||||
title: strings.getNotesnookPro(),
|
||||
title: strings.upgradePlan(),
|
||||
icon: "crown",
|
||||
id: "pro",
|
||||
onPress: () => {
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
Navigation.navigate("PayWall", {
|
||||
context: "logged-in"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,15 +142,10 @@ export function SideMenuHome() {
|
||||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
|
||||
<Button
|
||||
title={pro.title}
|
||||
iconColor={colors.static.yellow}
|
||||
textStyle={{
|
||||
color: colors.static.white
|
||||
}}
|
||||
icon={pro.icon}
|
||||
style={{
|
||||
backgroundColor: colors.static.black,
|
||||
width: "100%"
|
||||
}}
|
||||
type="accent"
|
||||
onPress={pro.onPress}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -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/>.
|
||||
*/
|
||||
/* eslint-disable no-inner-declarations */
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import {
|
||||
createInternalLink,
|
||||
Item,
|
||||
@@ -38,11 +39,13 @@ import { presentDialog } from "../components/dialog/functions";
|
||||
import NoteHistory from "../components/note-history";
|
||||
import { AddNotebookSheet } from "../components/sheets/add-notebook";
|
||||
import ExportNotesSheet from "../components/sheets/export-notes";
|
||||
import PaywallSheet from "../components/sheets/paywall";
|
||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||
import { ReferencesList } from "../components/sheets/references";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
||||
import { ButtonProps } from "../components/ui/button";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -66,9 +69,6 @@ import { eUpdateNoteInEditor } from "../utils/events";
|
||||
import { deleteItems } from "../utils/functions";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { sleep } from "../utils/time";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../components/sheets/paywall";
|
||||
|
||||
export type ActionId =
|
||||
| "select"
|
||||
@@ -216,7 +216,7 @@ export const useActions = ({
|
||||
|
||||
async function restoreTrashItem() {
|
||||
close();
|
||||
if ((await db.trash.restore(item.id)) === false) return;
|
||||
await db.trash.restore(item.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
const type = item.type === "trash" ? item.itemType : item.type;
|
||||
ToastManager.show({
|
||||
@@ -248,6 +248,19 @@ export const useActions = ({
|
||||
if (isPinnedToMenu) {
|
||||
await db.shortcuts.remove(item.id);
|
||||
} else {
|
||||
const shortcutsFeature = await isFeatureAvailable("shortcuts");
|
||||
if (!shortcutsFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: shortcutsFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(shortcutsFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
await db.shortcuts.add({
|
||||
itemId: item.id,
|
||||
itemType: item.type
|
||||
@@ -417,7 +430,22 @@ export const useActions = ({
|
||||
id: "reorder",
|
||||
title: strings.reorder(),
|
||||
icon: "sort-ascending",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const sidebarReorderFeature = await isFeatureAvailable(
|
||||
"customizableSidebar"
|
||||
);
|
||||
if (!sidebarReorderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: sidebarReorderFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(sidebarReorderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
useSideBarDraggingStore.setState({
|
||||
dragging: true
|
||||
});
|
||||
@@ -500,6 +528,22 @@ export const useActions = ({
|
||||
await db.settings.setDefaultTag(undefined);
|
||||
setDefaultTag(undefined);
|
||||
} else {
|
||||
const defaultNotebookAndTagFeature = await isFeatureAvailable(
|
||||
"defaultNotebookAndTag"
|
||||
);
|
||||
if (!defaultNotebookAndTagFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: defaultNotebookAndTagFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(defaultNotebookAndTagFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await db.settings.setDefaultTag(item.id);
|
||||
setDefaultTag(item.id);
|
||||
}
|
||||
@@ -517,6 +561,20 @@ export const useActions = ({
|
||||
title: strings.addNotebook(),
|
||||
icon: "plus",
|
||||
onPress: async () => {
|
||||
const notebooksFeature = await isFeatureAvailable("notebooks");
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
close();
|
||||
await sleep(300);
|
||||
AddNotebookSheet.present(undefined, item);
|
||||
@@ -545,6 +603,22 @@ export const useActions = ({
|
||||
await db.settings.setDefaultNotebook(undefined);
|
||||
setDefaultNotebook(undefined);
|
||||
} else {
|
||||
const defaultNotebookAndTagFeature = await isFeatureAvailable(
|
||||
"defaultNotebookAndTag"
|
||||
);
|
||||
if (!defaultNotebookAndTagFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: defaultNotebookAndTagFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(defaultNotebookAndTagFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const notebook = {
|
||||
id: item.id
|
||||
};
|
||||
@@ -604,7 +678,24 @@ export const useActions = ({
|
||||
icon: "home-outline",
|
||||
isToggle: true,
|
||||
checked: isHomepage,
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const customHomepageFeature = await isFeatureAvailable(
|
||||
"customHomepage"
|
||||
);
|
||||
|
||||
if (!customHomepageFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: customHomepageFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(customHomepageFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsService.setProperty(
|
||||
"homepageV2",
|
||||
isHomepage
|
||||
@@ -688,7 +779,14 @@ export const useActions = ({
|
||||
async function pinToNotifications() {
|
||||
const result = await isFeatureAvailable("pinNoteInNotification");
|
||||
if (!result.isAllowed) {
|
||||
PaywallSheet.present();
|
||||
ToastManager.show({
|
||||
message: result.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(result);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -921,13 +1019,40 @@ export const useActions = ({
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
onAdd: () => {
|
||||
onAdd: async () => {
|
||||
const reminderFeature = await isFeatureAvailable(
|
||||
"activeReminders"
|
||||
);
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
}
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
button: {
|
||||
type: "plain",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const reminderFeature = await isFeatureAvailable(
|
||||
"activeReminders"
|
||||
);
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
|
||||
@@ -103,6 +103,8 @@ import { fluidTabsRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../components/sheets/paywall";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -185,6 +187,18 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
if (reminder) AddReminder.present(reminder);
|
||||
}
|
||||
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
|
||||
const reminderFeature = await isFeatureAvailable("activeReminders");
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present();
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import Navigation from "../services/navigation";
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
|
||||
const RootStack = createNativeStackNavigator();
|
||||
const AppStack = createNativeStackNavigator();
|
||||
@@ -47,6 +48,7 @@ let ColoredNotes: any = null;
|
||||
let Archive: any = null;
|
||||
const AppNavigation = React.memo(
|
||||
() => {
|
||||
const customHomepageFeature = useIsFeatureAvailable("customHomepage");
|
||||
const { colors } = useThemeColors();
|
||||
const [home, setHome] = React.useState<{
|
||||
name: string;
|
||||
@@ -58,7 +60,7 @@ const AppNavigation = React.memo(
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (loading) return;
|
||||
if (!homepageV2) {
|
||||
if (!homepageV2 || !customHomepageFeature?.isAllowed) {
|
||||
setHome({
|
||||
name: "Notes",
|
||||
params: undefined
|
||||
|
||||
@@ -47,7 +47,8 @@ import SettingsService from "../../services/settings";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { getFormattedDate, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
const ReminderModes =
|
||||
Platform.OS === "ios"
|
||||
@@ -107,6 +108,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const referencedItem = reference ? (reference as Note) : null;
|
||||
const recurringReminderFeature = useIsFeatureAvailable("recurringReminders");
|
||||
|
||||
const title = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.title : reminder?.title
|
||||
@@ -304,7 +306,14 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
if (mode === "Repeat" && !PremiumService.get()) return;
|
||||
if (
|
||||
recurringReminderFeature &&
|
||||
!recurringReminderFeature?.isAllowed
|
||||
) {
|
||||
PaywallSheet.present(recurringReminderFeature);
|
||||
return;
|
||||
}
|
||||
|
||||
setReminderMode(
|
||||
ReminderModes[
|
||||
mode as keyof typeof ReminderModes
|
||||
|
||||
@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { isImage } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { basename } from "pathe";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
@@ -38,12 +40,10 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
@@ -92,13 +92,13 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
|
||||
|
||||
if ((file.size || 0) > FILE_SIZE_LIMIT) {
|
||||
const featureResult = await isFeatureAvailable("fileSize", file.size || 0);
|
||||
if (!featureResult.isAllowed) {
|
||||
ToastManager.show({
|
||||
heading: strings.fileTooLarge(),
|
||||
message: strings.fileTooLargeDesc(500),
|
||||
message: featureResult.error,
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.copyError) {
|
||||
@@ -290,14 +290,15 @@ const handleImageResponse = async (
|
||||
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
|
||||
}
|
||||
|
||||
if (image.size > IMAGE_SIZE_LIMIT) {
|
||||
const featureResult = await isFeatureAvailable("fileSize", image.size || 0);
|
||||
if (!featureResult.isAllowed) {
|
||||
ToastManager.show({
|
||||
heading: strings.fileTooLarge(),
|
||||
message: strings.fileTooLargeDesc(50),
|
||||
message: featureResult.error,
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||
const uri = decodeURI(image.path);
|
||||
const hash = await Sodium.hashFile({
|
||||
|
||||
@@ -80,7 +80,8 @@ import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorState, openInternalLink } from "./utils";
|
||||
import AddReminder from "../../add-reminder";
|
||||
import { useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../../../components/sheets/paywall";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -445,7 +446,23 @@ export const useEditorEvents = (
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
onAdd: () => AddReminder.present(undefined, note)
|
||||
onAdd: async () => {
|
||||
const reminderFeature = await isFeatureAvailable(
|
||||
"activeReminders"
|
||||
);
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present(undefined, note);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case EditorEvents.newtag:
|
||||
@@ -533,7 +550,9 @@ export const useEditorEvents = (
|
||||
if (editor.state.current?.isFocused) {
|
||||
editor.state.current.isFocused = true;
|
||||
}
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
PaywallSheet.present(
|
||||
await isFeatureAvailable(editorMessage.value.feature)
|
||||
);
|
||||
break;
|
||||
case EditorEvents.monograph:
|
||||
publishNote();
|
||||
|
||||
@@ -44,6 +44,9 @@ import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
|
||||
import { Notice } from "../../components/ui/notice";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
const {
|
||||
useNotebookExpandedStore,
|
||||
@@ -233,7 +236,23 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
|
||||
}}
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -376,7 +395,21 @@ const NotebookItemWrapper = React.memo(
|
||||
disableExpand={disableExpand}
|
||||
focused={false}
|
||||
onPress={onPress}
|
||||
onAddNotebook={() => {
|
||||
onAddNotebook={async () => {
|
||||
const notebooksFeature = await isFeatureAvailable("notebooks");
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
item.notebook,
|
||||
|
||||
@@ -53,6 +53,7 @@ import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Header } from "../../components/header";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
|
||||
async function updateInitialSelectionState(items: string[]) {
|
||||
const relations = await db.relations
|
||||
@@ -152,11 +153,22 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
v.and([v(`title`, "==", tag)])
|
||||
);
|
||||
|
||||
const id = exists
|
||||
? exists?.id
|
||||
: await db.tags.add({
|
||||
title: tag
|
||||
let id = exists?.id;
|
||||
|
||||
if (!id) {
|
||||
const tagsFeature = await isFeatureAvailable("tags");
|
||||
if (!tagsFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: tagsFeature.error,
|
||||
type: "info",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
id = await db.tags.add({
|
||||
title: tag
|
||||
});
|
||||
}
|
||||
|
||||
if (id) {
|
||||
for (const noteId of ids) {
|
||||
|
||||
@@ -50,8 +50,10 @@ import {
|
||||
} from "../../utils/notebooks";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import { eUpdateNotebookRoute } from "../../utils/events";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
const {
|
||||
useNotebookExpandedStore,
|
||||
@@ -239,7 +241,23 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
|
||||
}}
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -387,7 +405,21 @@ const NotebookItemWrapper = React.memo(
|
||||
onItemUpdate={onItemUpdate}
|
||||
focused={false}
|
||||
onPress={onPress}
|
||||
onAddNotebook={() => {
|
||||
onAddNotebook={async () => {
|
||||
const notebooksFeature = await isFeatureAvailable("notebooks");
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
ToastManager.hide();
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
item.notebook,
|
||||
|
||||
@@ -30,6 +30,9 @@ import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useReminders } from "../../stores/use-reminder-store";
|
||||
import AddReminder from "../add-reminder";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
export const Reminders = ({
|
||||
navigation,
|
||||
@@ -67,7 +70,19 @@ export const Reminders = ({
|
||||
});
|
||||
}}
|
||||
id={route.name}
|
||||
onPressDefaultRightButton={() => {
|
||||
onPressDefaultRightButton={async () => {
|
||||
const reminderFeature = await isFeatureAvailable("activeReminders");
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present();
|
||||
}}
|
||||
/>
|
||||
@@ -83,7 +98,21 @@ export const Reminders = ({
|
||||
title: strings.yourReminders(),
|
||||
paragraph: strings.remindersEmpty(),
|
||||
button: strings.setReminder(),
|
||||
action: () => {
|
||||
action: async () => {
|
||||
const reminderFeature = await isFeatureAvailable(
|
||||
"activeReminders"
|
||||
);
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present();
|
||||
},
|
||||
loading: strings.loadingReminders()
|
||||
@@ -91,7 +120,19 @@ export const Reminders = ({
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
const reminderFeature = await isFeatureAvailable("activeReminders");
|
||||
if (!reminderFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: reminderFeature.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(reminderFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present();
|
||||
}}
|
||||
alwaysVisible
|
||||
|
||||
@@ -31,6 +31,9 @@ import { Group } from "./group";
|
||||
import { DragState, useDragState } from "./state";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import PaywallSheet from "../../../components/sheets/paywall";
|
||||
export const ConfigureToolbar = () => {
|
||||
const data = useDragState((state) => state.data);
|
||||
const preset = useDragState((state) => state.preset);
|
||||
@@ -97,10 +100,21 @@ export const ConfigureToolbar = () => {
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
proTag={item.pro}
|
||||
onPress={() => {
|
||||
if (item.id === "custom" && !PremiumService.get()) {
|
||||
PremiumService.sheet("global");
|
||||
return;
|
||||
onPress={async () => {
|
||||
if (item.id === "custom") {
|
||||
const customToolbarPresetFeature = await isFeatureAvailable(
|
||||
"customToolbarPreset"
|
||||
);
|
||||
if (!customToolbarPresetFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: customToolbarPresetFeature.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () =>
|
||||
PaywallSheet.present(customToolbarPresetFeature)
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
useDragState
|
||||
.getState()
|
||||
|
||||
@@ -39,8 +39,8 @@ interface PickerOptions<T> {
|
||||
compareValue: (current: T, item: T) => boolean;
|
||||
getItemKey: (item: T) => string;
|
||||
options: T[];
|
||||
premium?: boolean;
|
||||
onCheckOptionIsPremium?: (item: T) => boolean;
|
||||
isFeatureAvailable: () => Promise<boolean>;
|
||||
isOptionAvailable: (item: T) => Promise<boolean>;
|
||||
requiresVerification?: () => boolean;
|
||||
onVerify?: () => Promise<boolean>;
|
||||
}
|
||||
@@ -52,8 +52,8 @@ export function SettingsPicker<T>({
|
||||
compareValue,
|
||||
options,
|
||||
getItemKey,
|
||||
premium,
|
||||
onCheckOptionIsPremium = () => true,
|
||||
isFeatureAvailable,
|
||||
isOptionAvailable,
|
||||
requiresVerification = () => false,
|
||||
onVerify
|
||||
}: PickerOptions<T>) {
|
||||
@@ -63,19 +63,10 @@ export function SettingsPicker<T>({
|
||||
const [currentValue, setCurrentValue] = useState(getValue());
|
||||
|
||||
const onChange = async (item: T) => {
|
||||
if (premium && onCheckOptionIsPremium?.(item)) {
|
||||
await PremiumService.verify(
|
||||
async () => {
|
||||
menuRef.current?.hide();
|
||||
await updateValue(item);
|
||||
setCurrentValue(item);
|
||||
},
|
||||
async () => {
|
||||
menuRef.current?.hide();
|
||||
await sleep(300);
|
||||
PremiumService.sheet();
|
||||
}
|
||||
);
|
||||
if ((await isFeatureAvailable()) && (await isOptionAvailable(item))) {
|
||||
menuRef.current?.hide();
|
||||
await updateValue(item);
|
||||
setCurrentValue(item);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,7 +106,11 @@ export function SettingsPicker<T>({
|
||||
anchor={
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
if (onVerify && !(await onVerify())) return;
|
||||
if (
|
||||
(onVerify && !(await onVerify())) ||
|
||||
!(await isFeatureAvailable())
|
||||
)
|
||||
return;
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="secondary"
|
||||
|
||||
@@ -29,6 +29,8 @@ import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { MenuItemsList } from "../../../utils/menu-items";
|
||||
import { verifyUserWithApplock } from "../functions";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../../../components/sheets/paywall";
|
||||
|
||||
export const FontPicker = createSettingsPicker({
|
||||
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
|
||||
@@ -42,7 +44,9 @@ export const FontPicker = createSettingsPicker({
|
||||
},
|
||||
getItemKey: (item) => item.id,
|
||||
options: getFonts(),
|
||||
compareValue: (current, item) => current === item.id
|
||||
compareValue: (current, item) => current === item.id,
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const HomePicker = createSettingsPicker({
|
||||
@@ -61,7 +65,8 @@ export const HomePicker = createSettingsPicker({
|
||||
getItemKey: (item) => item.title,
|
||||
options: MenuItemsList.slice(0, MenuItemsList.length - 1),
|
||||
compareValue: (current, item) => current === item.title,
|
||||
premium: true
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const SidebarTabPicker = createSettingsPicker({
|
||||
@@ -80,7 +85,21 @@ export const SidebarTabPicker = createSettingsPicker({
|
||||
getItemKey: (item) => item,
|
||||
options: [0, 1, 2],
|
||||
compareValue: (current, item) => current === item,
|
||||
premium: true
|
||||
isFeatureAvailable: async () => {
|
||||
const result = await isFeatureAvailable("defaultSidebarTab");
|
||||
if (!result.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: result.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.isAllowed;
|
||||
},
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const TrashIntervalPicker = createSettingsPicker({
|
||||
@@ -98,7 +117,21 @@ export const TrashIntervalPicker = createSettingsPicker({
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 1, 7, 30, 365],
|
||||
compareValue: (current, item) => current === item,
|
||||
premium: true
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: async () => {
|
||||
const disableTrashFeature = await isFeatureAvailable("disableTrashCleanup");
|
||||
if (!disableTrashFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: disableTrashFeature.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(disableTrashFeature);
|
||||
}
|
||||
});
|
||||
}
|
||||
return disableTrashFeature.isAllowed;
|
||||
}
|
||||
});
|
||||
|
||||
export const DateFormatPicker = createSettingsPicker({
|
||||
@@ -114,7 +147,9 @@ export const DateFormatPicker = createSettingsPicker({
|
||||
},
|
||||
getItemKey: (item) => item,
|
||||
options: DATE_FORMATS,
|
||||
compareValue: (current, item) => current === item
|
||||
compareValue: (current, item) => current === item,
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
const TimeFormats = {
|
||||
@@ -135,7 +170,9 @@ export const TimeFormatPicker = createSettingsPicker({
|
||||
},
|
||||
getItemKey: (item) => item,
|
||||
options: TIME_FORMATS,
|
||||
compareValue: (current, item) => current === item
|
||||
compareValue: (current, item) => current === item,
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const BackupReminderPicker = createSettingsPicker({
|
||||
@@ -149,16 +186,14 @@ export const BackupReminderPicker = createSettingsPicker({
|
||||
getItemKey: (item) => item,
|
||||
options: ["useroff", "daily", "weekly", "monthly"],
|
||||
compareValue: (current, item) => current === item,
|
||||
premium: true,
|
||||
requiresVerification: () => {
|
||||
return (
|
||||
!useSettingStore.getState().settings.encryptedBackup &&
|
||||
useUserStore.getState().user
|
||||
);
|
||||
},
|
||||
onCheckOptionIsPremium: (item) => {
|
||||
return item !== "useroff";
|
||||
}
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
|
||||
@@ -174,16 +209,14 @@ export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
|
||||
getItemKey: (item) => item,
|
||||
options: ["never", "weekly", "monthly"],
|
||||
compareValue: (current, item) => current === item,
|
||||
premium: true,
|
||||
requiresVerification: () => {
|
||||
return (
|
||||
!useSettingStore.getState().settings.encryptedBackup &&
|
||||
useUserStore.getState().user
|
||||
);
|
||||
},
|
||||
onCheckOptionIsPremium: (item) => {
|
||||
return item !== "never";
|
||||
}
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const ApplockTimerPicker = createSettingsPicker({
|
||||
@@ -205,5 +238,7 @@ export const ApplockTimerPicker = createSettingsPicker({
|
||||
compareValue: (current, item) => current === item,
|
||||
onVerify: () => {
|
||||
return verifyUserWithApplock();
|
||||
}
|
||||
},
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
@@ -17,7 +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 { formatBytes } from "@notesnook/common";
|
||||
import { formatBytes, isFeatureAvailable } from "@notesnook/common";
|
||||
import { User } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee from "@notifee/react-native";
|
||||
@@ -67,6 +67,7 @@ import { verifyUser, verifyUserWithApplock } from "./functions";
|
||||
import { logoutUser } from "./logout";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
import PaywallSheet from "../../components/sheets/paywall";
|
||||
|
||||
export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
@@ -749,7 +750,25 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: strings.mardownShortcuts(),
|
||||
property: "markdownShortcuts",
|
||||
description: strings.mardownShortcutsDesc(),
|
||||
type: "switch"
|
||||
type: "switch",
|
||||
onVerify: async () => {
|
||||
const markdownShortcuts = await isFeatureAvailable(
|
||||
"markdownShortcuts"
|
||||
);
|
||||
|
||||
if (!markdownShortcuts.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: markdownShortcuts.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(markdownShortcuts);
|
||||
}
|
||||
});
|
||||
}
|
||||
return markdownShortcuts.isAllowed;
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -941,6 +960,19 @@ export const settingsGroups: SettingSection[] = [
|
||||
SettingsService.setPrivacyScreen(SettingsService.get());
|
||||
},
|
||||
onVerify: async () => {
|
||||
const appLockFeature = await isFeatureAvailable("appLock");
|
||||
if (!appLockFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: appLockFeature.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(appLockFeature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const verified = await verifyUserWithApplock();
|
||||
if (!verified) return false;
|
||||
|
||||
@@ -1244,11 +1276,23 @@ export const settingsGroups: SettingSection[] = [
|
||||
description: strings.quickNoteNotificationDesc(),
|
||||
property: "notifNotes",
|
||||
icon: "form-textbox",
|
||||
modifer: () => {
|
||||
modifer: async () => {
|
||||
const settings = SettingsService.get();
|
||||
if (settings.notifNotes) {
|
||||
Notifications.unpinQuickNote();
|
||||
} else {
|
||||
const createNoteFromNotificationDrawerFeature =
|
||||
await isFeatureAvailable("createNoteFromNotificationDrawer");
|
||||
if (!createNoteFromNotificationDrawerFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: createNoteFromNotificationDrawerFeature.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(createNoteFromNotificationDrawerFeature);
|
||||
}
|
||||
});
|
||||
}
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
SettingsService.set({
|
||||
|
||||
@@ -115,8 +115,15 @@ const Tiptap = ({
|
||||
outlineList: !!settings.features?.outlineList?.isAllowed,
|
||||
taskList: !!settings.features?.taskList?.isAllowed
|
||||
},
|
||||
onPermissionDenied: () => {
|
||||
post(EditorEvents.pro, undefined, tabRef.current.id, tab.session?.noteId);
|
||||
onPermissionDenied: (claim) => {
|
||||
post(
|
||||
EditorEvents.pro,
|
||||
{
|
||||
feature: claim
|
||||
},
|
||||
tabRef.current.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
|
||||
"POT-Creation-Date: 2025-07-16 12:02+0500\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -6544,10 +6544,18 @@ msgstr "Update available"
|
||||
msgid "Update now"
|
||||
msgstr "Update now"
|
||||
|
||||
#: src/strings.ts:2491
|
||||
msgid "Upgrade"
|
||||
msgstr "Upgrade"
|
||||
|
||||
#: src/strings.ts:1917
|
||||
msgid "Upgrade now"
|
||||
msgstr "Upgrade now"
|
||||
|
||||
#: src/strings.ts:2490
|
||||
msgid "Upgrade plan"
|
||||
msgstr "Upgrade plan"
|
||||
|
||||
#: src/strings.ts:1938
|
||||
msgid "Upgrade to Notesnook Pro to add colors."
|
||||
msgstr "Upgrade to Notesnook Pro to add colors."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
|
||||
"POT-Creation-Date: 2025-07-16 12:02+0500\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -6503,10 +6503,18 @@ msgstr ""
|
||||
msgid "Update now"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2491
|
||||
msgid "Upgrade"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1917
|
||||
msgid "Upgrade now"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2490
|
||||
msgid "Upgrade plan"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1938
|
||||
msgid "Upgrade to Notesnook Pro to add colors."
|
||||
msgstr ""
|
||||
|
||||
@@ -2494,5 +2494,7 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
emailConfirmedDesc: () =>
|
||||
t`Your email has been confirmed. You can now securely sync your encrypted notes across all devices.`,
|
||||
charactersCount: (count: number) => t`${count} characters`,
|
||||
iAlreadyHaveAnAccount: () => t`I already have an account`
|
||||
iAlreadyHaveAnAccount: () => t`I already have an account`,
|
||||
upgradePlan: () => t`Upgrade plan`,
|
||||
upgrade: () => t`Upgrade`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user