diff --git a/apps/mobile/.nanoicons.json b/apps/mobile/.nanoicons.json
new file mode 100644
index 000000000..465a6971d
--- /dev/null
+++ b/apps/mobile/.nanoicons.json
@@ -0,0 +1,9 @@
+{
+ "iconSets": [
+ {
+ "inputDir": "../../packages/icons/svgs",
+ "fontFamily": "notesnook-icons",
+ "outputDir": "./fonts"
+ }
+ ]
+}
diff --git a/apps/mobile/app/common/design/font.ts b/apps/mobile/app/common/design/font.ts
index 753a95fbd..221d5d299 100644
--- a/apps/mobile/app/common/design/font.ts
+++ b/apps/mobile/app/common/design/font.ts
@@ -1,3 +1,21 @@
+/*
+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 .
+*/
export const FontSizes = {
XXS: 10,
XS: 12,
@@ -15,10 +33,24 @@ export const FontFamily = {
BOLD: "Inter-Bold" // 700
};
+const LineHeightMultipliers = {
+ "100%": 1,
+ "110%": 1.1,
+ "120%": 1.2,
+ "130%": 1.3,
+ "140%": 1.4,
+ "150%": 1.5
+};
+export type LineHeightVariants =
+ | "100%"
+ | "110%"
+ | "120%"
+ | "130%"
+ | "140%"
+ | "150%";
export const getLineHeight = (
fontSize: keyof typeof FontSizes,
- type: 1 | 2
+ type: LineHeightVariants
) => {
- if (type === 1) return (FontSizes[fontSize] / 100) * 120;
- if (type === 2) return (FontSizes[fontSize] / 100) * 150;
+ return FontSizes[fontSize] * LineHeightMultipliers[type];
};
diff --git a/apps/mobile/app/components/auth/index.tsx b/apps/mobile/app/components/auth/index.tsx
index e873ebd94..eac8b8eb4 100644
--- a/apps/mobile/app/components/auth/index.tsx
+++ b/apps/mobile/app/components/auth/index.tsx
@@ -17,15 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import { SafeAreaView } from "react-native-safe-area-context";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
+import { NavigationProps } from "../../services/navigation";
import { Toast } from "../toast";
import { AuthMode, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
-import { useThemeColors } from "@notesnook/theme";
-import { NavigationProps } from "../../services/navigation";
const Auth = ({ navigation, route }: NavigationProps<"Auth">) => {
const [currentAuthMode, setCurrentAuthMode] = useState(
diff --git a/apps/mobile/app/components/auth/login.tsx b/apps/mobile/app/components/auth/login.tsx
index 4dd390bcf..41b52bf00 100644
--- a/apps/mobile/app/components/auth/login.tsx
+++ b/apps/mobile/app/components/auth/login.tsx
@@ -178,6 +178,7 @@ export const Login = ({
formRef={formRef}
fwdRef={emailInputRef}
testID="input.email"
+ label={strings.email()}
returnKeyLabel="Next"
returnKeyType="next"
autoComplete="email"
@@ -200,6 +201,7 @@ export const Login = ({
name="password"
formRef={formRef}
fwdRef={passwordInputRef}
+ label={strings.password()}
testID="input.password"
returnKeyLabel={strings.done()}
returnKeyType="done"
diff --git a/apps/mobile/app/components/auth/two-factor.tsx b/apps/mobile/app/components/auth/two-factor.tsx
index 1106abf4e..fe52f2b1b 100644
--- a/apps/mobile/app/components/auth/two-factor.tsx
+++ b/apps/mobile/app/components/auth/two-factor.tsx
@@ -22,9 +22,9 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { db } from "../../common/database/index";
+import { Radius, Spacing } from "../../common/design/spacing";
import useTimer from "../../hooks/use-timer";
-import { eSendEvent, ToastManager } from "../../services/event-manager";
-import { eCloseSimpleDialog } from "../../utils/events";
+import { hexToRGBA, RGB_Linear_Shade } from "../../utils/colors";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
@@ -35,7 +35,6 @@ import PinInput from "../ui/pin-input/index";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
-import { Radius, Spacing } from "../../common/design/spacing";
type MFAInfo = {
primaryMethod: string;
@@ -119,12 +118,14 @@ const TwoFactorVerification = ({
{
id: "sms",
title: strings.sendCodeSms(),
- icon: "message-plus-outline"
+ icon: "chat",
+ iconFamily: "notesnook"
},
{
id: "email",
title: strings.sendCodeEmail(),
- icon: "email-outline"
+ icon: "envelope-simple",
+ iconFamily: "notesnook"
},
{
id: "app",
@@ -134,7 +135,8 @@ const TwoFactorVerification = ({
{
id: "recoveryCode",
title: strings.recoveryCode(),
- icon: "key"
+ icon: "lock-simple",
+ iconFamily: "notesnook"
}
];
@@ -182,47 +184,54 @@ const TwoFactorVerification = ({
}, 500);
}}
style={{
- alignItems: "center"
+ alignItems: "center",
+ gap: Spacing.LEVEL_3
}}
>
-
-
- {currentMethod.method ? strings["2fa"]() : strings.select2faMethod()}
-
-
- {currentMethod.method
- ? strings["2faCodeHelpText"][
- currentMethod.method as keyof (typeof strings)["2faCodeHelpText"]
- ]?.() || strings.select2faCodeHelpText()
- : strings.select2faCodeHelpText()}
-
+
+
+ {currentMethod.method ? strings["2fa"]() : strings.select2faMethod()}
+
+
+ {currentMethod.method
+ ? strings["2faCodeHelpText"][
+ currentMethod.method as keyof (typeof strings)["2faCodeHelpText"]
+ ]?.() || strings.select2faCodeHelpText()
+ : strings.select2faCodeHelpText()}
+
+
{currentMethod.method ? (
<>
>
) : (
- <>
+
{getMethods().map((item) => (
- {item.title}
+ {item.title}
))}
- >
+
)}
);
diff --git a/apps/mobile/app/components/dialog/dialog-buttons.tsx b/apps/mobile/app/components/dialog/dialog-buttons.tsx
index 01e8d1efb..44de213af 100644
--- a/apps/mobile/app/components/dialog/dialog-buttons.tsx
+++ b/apps/mobile/app/components/dialog/dialog-buttons.tsx
@@ -20,14 +20,11 @@ along with this program. If not, see .
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
-import { ActivityIndicator, StyleSheet, View } from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
+import { View } from "react-native";
import { notesnook } from "../../../e2e/test.ids";
-import { getColorLinearShade } from "../../utils/colors";
+import { Spacing } from "../../common/design/spacing";
import { AppFontSize } from "../../utils/size";
-import { DefaultAppStyles } from "../../utils/styles";
import { Button, ButtonProps } from "../ui/button";
-import Paragraph from "../ui/typography/paragraph";
const DialogButtons = ({
onPressPositive,
@@ -51,82 +48,39 @@ const DialogButtons = ({
return (
- {doneText ? (
-
-
- {" " + doneText}
-
- ) : loading ? (
-
- ) : (
-
- )}
-
-
+ title={negativeTitle}
+ />
+ {onPressPositive ? (
- {onPressPositive ? (
-
- ) : null}
-
+ ) : null}
);
};
export default DialogButtons;
-
-const styles = StyleSheet.create({
- container: {
- justifyContent: "space-between",
- alignItems: "center",
- flexDirection: "row",
- marginTop: DefaultAppStyles.GAP_VERTICAL
- }
-});
diff --git a/apps/mobile/app/components/dialog/dialog-container.tsx b/apps/mobile/app/components/dialog/dialog-container.tsx
index fcd5fe932..653528dac 100644
--- a/apps/mobile/app/components/dialog/dialog-container.tsx
+++ b/apps/mobile/app/components/dialog/dialog-container.tsx
@@ -23,6 +23,7 @@ import { DDS } from "../../services/device-detection";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
import { getContainerBorder } from "../../utils/colors";
+import { Radius } from "../../common/design/spacing";
const DialogContainer = ({
width,
@@ -44,7 +45,7 @@ const DialogContainer = ({
{
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
- borderRadius: 10,
+ borderRadius: Radius.LG,
backgroundColor: colors.primary.background,
paddingTop: 12
},
diff --git a/apps/mobile/app/components/dialog/dialog-header.tsx b/apps/mobile/app/components/dialog/dialog-header.tsx
index eff4dad14..27262600d 100644
--- a/apps/mobile/app/components/dialog/dialog-header.tsx
+++ b/apps/mobile/app/components/dialog/dialog-header.tsx
@@ -57,7 +57,6 @@ const DialogHeader = ({
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
- minHeight: 50,
paddingHorizontal: padding,
...style
}}
@@ -76,7 +75,7 @@ const DialogHeader = ({
>
{title}{" "}
{titlePart ? (
diff --git a/apps/mobile/app/components/dialog/functions.ts b/apps/mobile/app/components/dialog/functions.ts
index 25d643895..ce967d510 100644
--- a/apps/mobile/app/components/dialog/functions.ts
+++ b/apps/mobile/app/components/dialog/functions.ts
@@ -58,6 +58,7 @@ export type DialogInfo = {
onFormSubmit?: (form: FormRef) => Promise;
};
input: boolean;
+ inputLabel?: string;
inputPlaceholder: string;
defaultValue: string;
// eslint-disable-next-line @typescript-eslint/ban-types
diff --git a/apps/mobile/app/components/dialog/index.tsx b/apps/mobile/app/components/dialog/index.tsx
index c4e6a1080..9c4dc2451 100644
--- a/apps/mobile/app/components/dialog/index.tsx
+++ b/apps/mobile/app/components/dialog/index.tsx
@@ -19,13 +19,14 @@ along with this program. If not, see .
import { useThemeColors } from "@notesnook/theme";
import React, {
+ RefObject,
useCallback,
useEffect,
useRef,
- useState,
- RefObject
+ useState
} from "react";
import { TextInput, View, ViewStyle } from "react-native";
+import { Spacing } from "../../common/design/spacing";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
@@ -42,7 +43,6 @@ import { Button } from "../ui/button";
import Input from "../ui/input";
import { FormInput, type FormRef } from "../ui/input/form-input";
import { Notice } from "../ui/notice";
-import Seperator from "../ui/seperator";
import BaseDialog from "./base-dialog";
import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
@@ -155,7 +155,7 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
maxHeight: 450,
borderRadius: defaultBorderRadius,
backgroundColor: colors.primary.background,
- paddingTop: 12,
+ gap: Spacing.LEVEL_4,
...getContainerBorder(colors.primary.border, 0.5),
overflow: "hidden"
};
@@ -170,9 +170,7 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
bounce={!dialogInfo.input && !dialogInfo.form}
closeOnTouch={!dialogInfo.disableBackdropClosing}
background={dialogInfo.background}
- transparent={
- dialogInfo.transparent === undefined ? false : dialogInfo.transparent
- }
+ transparent={dialogInfo.transparent}
onShow={async () => {
if (dialogInfo.input && !dialogInfo.form) {
inputRef.current?.setNativeProps({
@@ -201,54 +199,61 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
: dialogInfo.component}
{dialogInfo.component ? null : (
-
-
-
+
+
+
- {dialogInfo.form ? (
-
- {dialogInfo.form.items.map((item, index) => (
- }
- validators={item.validators}
- defaultValue={item.defaultValue}
- secureTextEntry={dialogInfo.secureTextEntry}
- onSubmitEditing={() => {
- const nextItem = dialogInfo?.form?.items?.[index + 1];
- if (nextItem) {
- nextItem?.ref.current?.focus();
- } else {
- onPressPositive();
- }
- }}
- />
- ))}
-
- ) : dialogInfo.input ? (
-
+ {dialogInfo.form ? (
+
+ {dialogInfo.form.items.map((item, index) => (
+ }
+ validators={item.validators}
+ defaultValue={item.defaultValue}
+ secureTextEntry={dialogInfo.secureTextEntry}
+ onSubmitEditing={() => {
+ const nextItem = dialogInfo?.form?.items?.[index + 1];
+ if (nextItem) {
+ nextItem?.ref.current?.focus();
+ } else {
+ onPressPositive();
+ }
+ }}
+ />
+ ))}
+
+ ) : dialogInfo.input ? (
{
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
defaultValue={dialogInfo.defaultValue}
+ label={dialogInfo.inputLabel}
onSubmit={() => {
onPressPositive();
}}
@@ -266,48 +272,47 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
keyboardType={dialogInfo.keyboardType || "default"}
placeholder={dialogInfo.inputPlaceholder}
/>
-
- ) : null}
+ ) : null}
- {dialogInfo?.notice ? (
-
-
-
- ) : null}
-
- {dialogInfo.check ? (
- <>
-
+ ) : null}
+ {dialogInfo.check ? (
+ <>
+
.
*/
-import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
+import { notesnook } from "../../../e2e/test.ids";
+import { Radius, Spacing } from "../../common/design/spacing";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
+import Navigation from "../../services/navigation";
import { RouteName } from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
+import { useSettingStore } from "../../stores/use-setting-store";
import { eScrollEvent } from "../../utils/events";
-import { AppFontSize } from "../../utils/size";
+import { fluidTabsRef } from "../../utils/global-refs";
import { DefaultAppStyles } from "../../utils/styles";
-import { IconButtonProps } from "../ui/icon-button";
-import { Pressable } from "../ui/pressable";
+import { IconButton, IconButtonProps } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
-import Paragraph from "../ui/typography/paragraph";
-import { LeftMenus } from "./left-menus";
-import { RightMenus } from "./right-menus";
export const Header = ({
renderedInRoute,
@@ -64,6 +63,9 @@ export const Header = ({
state.selectionMode
]);
+ const deviceMode = useSettingStore((state) => state.deviceMode);
+ const isTablet = deviceMode === "tablet";
+
const onScroll = useCallback(
(data: { x: number; y: number; id?: string; route: string }) => {
if (data.route !== renderedInRoute || data.id !== id) return;
@@ -85,34 +87,58 @@ export const Header = ({
};
}, [borderHidden, onScroll]);
- const HeaderWrapper = hasSearch ? Pressable : View;
+ const _onLeftButtonPress = () => {
+ if (onLeftMenuButtonPress) return onLeftMenuButtonPress();
+
+ if (!canGoBack) {
+ if (fluidTabsRef.current?.isDrawerOpen()) {
+ Navigation.closeDrawer();
+ } else {
+ Navigation.openDrawer();
+ }
+ return;
+ }
+ Navigation.goBack();
+ };
return (
- {
- onSearch?.();
- }}
>
-
+ {isTablet && !canGoBack ? null : (
+ {
+ Navigation.popToTop();
+ }}
+ style={{
+ width: 20,
+ height: 20
+ }}
+ size={20}
+ name={canGoBack ? "arrow-left" : "menu"}
+ iconFamily="notesnook"
+ color={colors.primary.icon}
+ />
+ )}
{!title ? (
- ) : hasSearch ? (
-
- {selectionMode
- ? `${selectedItemsList.length} selected`
- : strings.searchInRoute(title)}
-
) : (
- {title}
+
+ {selectionMode ? `${selectedItemsList.length} selected` : title}
+
)}
-
-
+
+ {rightButton ? (
+
+ ) : null}
+
+ {hasSearch ? (
+ {
+ onSearch?.();
+ }}
+ style={{
+ width: 20,
+ height: 20
+ }}
+ iconFamily="notesnook"
+ name="search"
+ />
+ ) : null}
+
+
);
};
diff --git a/apps/mobile/app/components/header/left-menus.tsx b/apps/mobile/app/components/header/left-menus.tsx
deleted file mode 100644
index c95530cfe..000000000
--- a/apps/mobile/app/components/header/left-menus.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-This file is part of the Notesnook project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-import { useThemeColors } from "@notesnook/theme";
-import React from "react";
-import { notesnook } from "../../../e2e/test.ids";
-import Navigation from "../../services/navigation";
-import { useSettingStore } from "../../stores/use-setting-store";
-import { fluidTabsRef } from "../../utils/global-refs";
-import { IconButton } from "../ui/icon-button";
-
-export const LeftMenus = ({
- canGoBack,
- onLeftButtonPress
-}: {
- canGoBack?: boolean;
- onLeftButtonPress?: () => void;
-}) => {
- const { colors } = useThemeColors();
- const deviceMode = useSettingStore((state) => state.deviceMode);
- const isTablet = deviceMode === "tablet";
-
- const _onLeftButtonPress = () => {
- if (onLeftButtonPress) return onLeftButtonPress();
-
- if (!canGoBack) {
- if (fluidTabsRef.current?.isDrawerOpen()) {
- Navigation.closeDrawer();
- } else {
- Navigation.openDrawer();
- }
- return;
- }
- Navigation.goBack();
- };
-
- return isTablet && !canGoBack ? null : (
- {
- Navigation.popToTop();
- }}
- name={canGoBack ? "arrow-left" : "menu"}
- color={colors.primary.icon}
- />
- );
-};
diff --git a/apps/mobile/app/components/list-items/headers/section-header.tsx b/apps/mobile/app/components/list-items/headers/section-header.tsx
index e52d6e4a9..e828fa2bc 100644
--- a/apps/mobile/app/components/list-items/headers/section-header.tsx
+++ b/apps/mobile/app/components/list-items/headers/section-header.tsx
@@ -22,22 +22,22 @@ import {
GroupingByIdKey,
GroupingKey,
GroupOptions,
- ItemType
+ ItemType,
+ Item,
+ VirtualizedGrouping
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
-import React from "react";
-import { View } from "react-native";
+import React, { RefObject } from "react";
+import { FlatList, View } from "react-native";
+import { Radius, Spacing } from "../../../common/design/spacing";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { RouteName } from "../../../stores/use-navigation-store";
-import { AppFontSize } from "../../../utils/size";
-import { DefaultAppStyles } from "../../../utils/styles";
import Sort from "../../sheets/sort";
import { IconButton } from "../../ui/icon-button";
-import { Pressable } from "../../ui/pressable";
-import Heading from "../../ui/typography/heading";
+import Paragraph from "../../ui/typography/paragraph";
type SectionHeaderProps = {
item: GroupHeader;
@@ -49,8 +49,9 @@ type SectionHeaderProps = {
group: GroupingKey;
groupId?: string;
type?: GroupingByIdKey;
- onOpenJumpToDialog: () => void;
itemCount?: number;
+ data?: VirtualizedGrouping- ;
+ ref?: RefObject;
};
export const SectionHeader = React.memo<
@@ -64,10 +65,11 @@ export const SectionHeader = React.memo<
screen,
groupOptions,
group,
- onOpenJumpToDialog,
itemCount,
groupId,
- type
+ type,
+ data,
+ ref
}: SectionHeaderProps) {
const { colors } = useThemeColors();
const isCompactModeEnabled = useIsCompactModeEnabled(
@@ -78,8 +80,9 @@ export const SectionHeader = React.memo<
- {
- onOpenJumpToDialog();
- }}
- hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
+
-
- {!item.title || item.title === ""
- ? screen === "Search"
- ? strings.results(itemCount || 0)
- : strings.pinned().toUpperCase()
- : item.title.toUpperCase()}
-
-
+ {!item.title || item.title === ""
+ ? screen === "Search"
+ ? strings.results(itemCount || 0)
+ : strings.pinned().toUpperCase()
+ : item.title.toUpperCase()}
+
{index === 0 ? (
<>
{
@@ -155,15 +136,20 @@ export const SectionHeader = React.memo<
hideGroupOptions={
screen === "Reminders" || screen === "Search"
}
+ data={data}
+ ref={ref}
/>
)
});
}}
style={{
- width: 25,
- height: 25
+ width: 30,
+ height: 30,
+ borderWidth: 1,
+ borderRadius: Radius.XS,
+ borderColor: colors.secondary.border
}}
- size={AppFontSize.lg - 2}
+ size={16}
/>
{
SettingsService.set({
[dataType === "notebook"
@@ -192,20 +180,10 @@ export const SectionHeader = React.memo<
: "normal"
});
}}
- size={AppFontSize.lg - 2}
+ size={16}
/>
>
) : null}
-
- {/* */}
diff --git a/apps/mobile/app/components/list-items/note/index.tsx b/apps/mobile/app/components/list-items/note/index.tsx
index 75814a332..5d2a11438 100644
--- a/apps/mobile/app/components/list-items/note/index.tsx
+++ b/apps/mobile/app/components/list-items/note/index.tsx
@@ -28,7 +28,6 @@ import { useThemeColors } from "@notesnook/theme";
import { EntityLevel, decode } from "entities";
import React from "react";
import { View } from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import useNavigationStore, {
RouteParams
@@ -56,6 +55,9 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import dayjs from "dayjs";
import { ExpiryDate } from "../../ui/expiry-date";
+import { Radius, Spacing } from "../../../common/design/spacing";
+import { create } from "zustand";
+import { FontFamily } from "../../../common/design/font";
type NoteItemProps = {
item: Note | BaseTrashItem;
@@ -72,6 +74,24 @@ type NoteItemProps = {
renderedInRoute?: keyof RouteParams;
};
+const useShowMoreStore = create<{
+ showMoreStatus: Record;
+ show: (id: string) => void;
+ hide: (id: string) => void;
+}>((set) => ({
+ showMoreStatus: {},
+ show(id) {
+ set((state) => ({
+ showMoreStatus: { ...state.showMoreStatus, [id]: true }
+ }));
+ },
+ hide(id) {
+ set((state) => ({
+ showMoreStatus: { ...state.showMoreStatus, [id]: false }
+ }));
+ }
+}));
+
const NoteItem = ({
item,
isTrash,
@@ -98,68 +118,54 @@ const NoteItem = ({
const primaryColors = isEditingNote ? colors.selected : colors.primary;
const selectionMode = useSelectionStore((state) => state.selectionMode);
const [selected] = useIsSelected(item);
+ const showMore = useShowMoreStore((state) => state.showMoreStatus[item.id]);
+ const statusIcons = [
+ {
+ condition: item.conflicted,
+ name: "alert-circle",
+ color: colors.error.accent
+ },
+ {
+ condition: item.localOnly,
+ testID: "sync-off",
+ name: "sync-off",
+ color: primaryColors.icon
+ },
+ {
+ condition: item.readonly,
+ testID: "pencil-lock",
+ name: "pencil-lock",
+ color: primaryColors.icon
+ },
+ {
+ condition: item.pinned,
+ testID: "icon-pinned",
+ name: "pin",
+ color: primaryColors.icon
+ },
+ {
+ condition: !!locked,
+ testID: "lock",
+ name: "lock",
+ color: primaryColors.icon
+ },
+ {
+ condition: item.favorite,
+ testID: "star-filled",
+ name: "star-outline",
+ color: "orange"
+ }
+ ];
+
return (
<>
- {compactMode ? null : (
-
- {getFormattedDate(
- date,
- dayjs(date).isBefore(dayjs().subtract(1, "day").hour(23))
- ? "date"
- : "time"
- )}
-
- )}
-
- {compactMode ? (
-
- {item.title}
-
- ) : (
-
- {item.title}
-
- )}
-
- {item.headline && !compactMode ? (
-
- {decode(item.headline, {
- level: EntityLevel.HTML
- })}
-
- ) : null}
-
{compactMode ? null : (
{!isTrash ? (
<>
- {item.conflicted ? (
-
- ) : null}
+ {statusIcons
+ .filter((statusIcon) => statusIcon.condition)
+ .map((statusIcon) => (
+
+
+
+ ))}
- {item.localOnly ? (
-
- ) : null}
-
- {item.readonly ? (
-
- ) : null}
-
- {attachmentsCount > 0 ? (
+ {attachmentsCount !== 0 ? (
-
@@ -223,93 +231,34 @@ const NoteItem = ({
) : null}
- {item.pinned ? (
-
- ) : null}
-
- {locked ? (
-
- ) : null}
-
- {item.favorite ? (
-
- ) : null}
-
- {reminder ? (
-
- ) : null}
-
- {item.expiryDate?.value ? (
-
- ) : null}
-
{notebooks?.items
?.filter(
(item) =>
renderedInRoute !== "Notebook" ||
item.id !== useNavigationStore.getState().focusedRouteId
)
+ .filter((_, index) => showMore || index < 1)
.map((item) => (
{item.title}
@@ -317,31 +266,64 @@ const NoteItem = ({
))}
- {!isTrash && !compactMode && tags
- ? tags.items?.map((item) =>
- item.id ? (
- showMore || index < 1)
+ .map((item) =>
+ item.id ? (
+
+
-
- #{item.title}
-
-
- ) : null
- )
- : null}
+ {item.title}
+
+
+ ) : null
+ )}
+
+ {(() => {
+ const filteredNotebooks = (notebooks?.items || []).filter(
+ (nb) =>
+ renderedInRoute !== "Notebook" ||
+ nb.id !== useNavigationStore.getState().focusedRouteId
+ );
+ const filteredTags = (tags?.items || []).filter((t) => t.id);
+ const totalNotebooks = filteredNotebooks.length;
+ const totalTags = filteredTags.length;
+ const hasMore = totalNotebooks > 1 || totalTags > 1;
+ if (!hasMore) return null;
+ const hiddenCount =
+ (totalNotebooks > 1 ? totalNotebooks - 1 : 0) +
+ (totalTags > 1 ? totalTags - 1 : 0);
+ return (
+ {
+ if (showMore) {
+ useShowMoreStore.getState().hide(item.id);
+ } else {
+ useShowMoreStore.getState().show(item.id);
+ }
+ }}
+ >
+ {showMore
+ ? "Show less"
+ : `+${hiddenCount} ${strings.more()}`}
+
+ );
+ })()}
>
) : (
<>
@@ -372,6 +354,140 @@ const NoteItem = ({
)}
)}
+
+
+ {color ? (
+
+ ) : null}
+
+
+
+ {item.title}
+
+
+ !noOpen && Properties.present(item)}
+ style={{
+ justifyContent: "center",
+ height: undefined,
+ width: undefined,
+ borderRadius: 100,
+ alignItems: "center"
+ }}
+ />
+
+
+
+ {item.headline && !compactMode ? (
+
+ {decode(item.headline, {
+ level: EntityLevel.HTML
+ })}
+
+ ) : null}
+
+
+ {compactMode ? null : (
+
+
+
+ {getFormattedDate(
+ date,
+ dayjs(date).isBefore(dayjs().subtract(1, "day").hour(23))
+ ? "date"
+ : "time"
+ )}
+
+
+ )}
+
+ {item.expiryDate?.value ? (
+
+ ) : null}
+
+ {reminder ? (
+
+ ) : null}
+
{item.conflicted ? (
-
>
- ) : (
- !noOpen && Properties.present(item)}
- style={{
- justifyContent: "center",
- height: 35,
- width: 35,
- borderRadius: 100,
- alignItems: "center"
- }}
- />
- )}
+ ) : null}
>
);
diff --git a/apps/mobile/app/components/list-items/note/wrapper.tsx b/apps/mobile/app/components/list-items/note/wrapper.tsx
index fc649365e..ee0b07460 100644
--- a/apps/mobile/app/components/list-items/note/wrapper.tsx
+++ b/apps/mobile/app/components/list-items/note/wrapper.tsx
@@ -36,7 +36,8 @@ import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { RouteParams } from "../../../stores/use-navigation-store";
import NotePreview from "../../note-history/preview";
-import SelectionWrapper, { selectItem } from "../selection-wrapper";
+import SelectionWrapper from "../selection-wrapper";
+import { selectItem } from "../../../stores/use-selection-store";
export const openNote = async (
item: Note,
@@ -94,6 +95,7 @@ type NoteWrapperProps = {
isRenderedInActionSheet: boolean;
locked?: boolean;
renderedInRoute?: keyof RouteParams;
+ hasGroupHeader?: boolean;
};
export const NoteWrapper = React.memo<
@@ -103,6 +105,7 @@ export const NoteWrapper = React.memo<
item,
index,
isRenderedInActionSheet,
+ hasGroupHeader,
...restProps
}: NoteWrapperProps) {
const isTrash = item.type === "trash";
@@ -113,6 +116,7 @@ export const NoteWrapper = React.memo<
onPress={() => openNote(item as Note, isTrash, isRenderedInActionSheet)}
isSheet={isRenderedInActionSheet}
item={item}
+ hasGroupHeader={hasGroupHeader}
index={index}
color={restProps.color?.colorCode}
>
diff --git a/apps/mobile/app/components/list-items/notebook/wrapper.tsx b/apps/mobile/app/components/list-items/notebook/wrapper.tsx
index 7e3ef6f82..da3142c5d 100644
--- a/apps/mobile/app/components/list-items/notebook/wrapper.tsx
+++ b/apps/mobile/app/components/list-items/notebook/wrapper.tsx
@@ -27,7 +27,8 @@ import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { presentDialog } from "../../dialog/functions";
-import SelectionWrapper, { selectItem } from "../selection-wrapper";
+import SelectionWrapper from "../selection-wrapper";
+import { selectItem } from "../../../stores/use-selection-store";
import { strings } from "@notesnook/intl";
export const openNotebook = (item: Notebook | BaseTrashItem) => {
diff --git a/apps/mobile/app/components/list-items/reminder/index.tsx b/apps/mobile/app/components/list-items/reminder/index.tsx
index d52617ec6..5773b86c9 100644
--- a/apps/mobile/app/components/list-items/reminder/index.tsx
+++ b/apps/mobile/app/components/list-items/reminder/index.tsx
@@ -36,7 +36,8 @@ import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
-import SelectionWrapper, { selectItem } from "../selection-wrapper";
+import SelectionWrapper from "../selection-wrapper";
+import { selectItem } from "../../../stores/use-selection-store";
const ReminderItem = React.memo(
({
diff --git a/apps/mobile/app/components/list-items/selection-wrapper/index.tsx b/apps/mobile/app/components/list-items/selection-wrapper/index.tsx
index 53a02dadd..54bcdbc02 100644
--- a/apps/mobile/app/components/list-items/selection-wrapper/index.tsx
+++ b/apps/mobile/app/components/list-items/selection-wrapper/index.tsx
@@ -23,26 +23,9 @@ import React, { PropsWithChildren, useRef } from "react";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
-import { DefaultAppStyles } from "../../../utils/styles";
import { Pressable } from "../../ui/pressable";
import { View } from "react-native";
-
-export function selectItem(item: Item) {
- if (useSelectionStore.getState().selectionMode === item.type) {
- const { selectionMode, clearSelection, setSelectedItem } =
- useSelectionStore.getState();
-
- if (selectionMode === item.type) {
- setSelectedItem(item.id);
- }
-
- if (useSelectionStore.getState().selectedItemsList.length === 0) {
- clearSelection();
- }
- return true;
- }
- return false;
-}
+import { Spacing } from "../../../common/design/spacing";
type SelectionWrapperProps = PropsWithChildren<{
item: Item;
@@ -51,6 +34,7 @@ type SelectionWrapperProps = PropsWithChildren<{
isSheet?: boolean;
color?: string;
index?: number;
+ hasGroupHeader?: boolean;
}>;
const SelectionWrapper = ({
@@ -60,6 +44,7 @@ const SelectionWrapper = ({
isSheet,
children,
color,
+ hasGroupHeader,
index = 0
}: SelectionWrapperProps) => {
const itemId = useRef(item.id);
@@ -86,47 +71,37 @@ const SelectionWrapper = ({
};
return (
-
- {isEditingNote ? (
-
- ) : null}
- {children}
-
+
+ {children}
+
+
);
};
diff --git a/apps/mobile/app/components/list-items/tag/index.tsx b/apps/mobile/app/components/list-items/tag/index.tsx
index b962b6325..3d8a84e7c 100644
--- a/apps/mobile/app/components/list-items/tag/index.tsx
+++ b/apps/mobile/app/components/list-items/tag/index.tsx
@@ -28,7 +28,8 @@ import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
-import SelectionWrapper, { selectItem } from "../selection-wrapper";
+import SelectionWrapper from "../selection-wrapper";
+import { selectItem } from "../../../stores/use-selection-store";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
diff --git a/apps/mobile/app/components/list/card.tsx b/apps/mobile/app/components/list/card.tsx
index e363a3738..0419aa8bd 100644
--- a/apps/mobile/app/components/list/card.tsx
+++ b/apps/mobile/app/components/list/card.tsx
@@ -21,10 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Dimensions, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
+import { Radius, Spacing } from "../../common/design/spacing";
import { Message, useMessageStore } from "../../stores/use-message-store";
-import { AppFontSize } from "../../utils/size";
-import { DefaultAppStyles } from "../../utils/styles";
+import AppIcon from "../ui/AppIcon";
import { Pressable } from "../ui/pressable";
+import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const Card = ({
@@ -47,15 +48,19 @@ export const Card = ({
@@ -90,25 +96,26 @@ export const Card = ({
-
{messageBoardState.actionText}
-
-
+
+
{messageBoardState.message}
+
+
);
diff --git a/apps/mobile/app/components/list/list-item.wrapper.tsx b/apps/mobile/app/components/list/list-item.wrapper.tsx
index 7ef5f3eca..9ed396228 100644
--- a/apps/mobile/app/components/list/list-item.wrapper.tsx
+++ b/apps/mobile/app/components/list/list-item.wrapper.tsx
@@ -43,9 +43,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { getGroupOptions } from "../../hooks/use-group-options";
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
-import { eSendEvent } from "../../services/event-manager";
import { RouteName } from "../../stores/use-navigation-store";
-import { eOpenJumpToDialog } from "../../utils/events";
import { SectionHeader } from "../list-items/headers/section-header";
import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
@@ -190,12 +188,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
type={props.type}
color={props.customAccentColor}
groupOptions={groupOptions}
- onOpenJumpToDialog={() => {
- eSendEvent(eOpenJumpToDialog, {
- ref: props.scrollRef,
- data: items
- });
- }}
+ ref={props.scrollRef}
+ data={items}
/>
) : null}
@@ -205,6 +199,9 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
color={color.current}
notebooks={notebooks.current}
reminder={reminder.current}
+ hasGroupHeader={
+ groupHeader && previousIndex.current === index && !isSheet
+ }
attachmentsCount={attachmentsCount.current}
date={getDate(item as Note, group)}
isRenderedInActionSheet={isSheet}
@@ -229,12 +226,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
type={props.type}
color={props.customAccentColor}
groupOptions={groupOptions}
- onOpenJumpToDialog={() => {
- eSendEvent(eOpenJumpToDialog, {
- ref: props.scrollRef,
- data: items
- });
- }}
+ ref={props.scrollRef}
+ data={items}
/>
) : null}
{
- eSendEvent(eOpenJumpToDialog, {
- ref: props.scrollRef,
- data: items
- });
- }}
+ ref={props.scrollRef}
+ data={items}
/>
) : null}
{
- eSendEvent(eOpenJumpToDialog, {
- ref: props.scrollRef,
- data: items
- });
- }}
+ ref={props.scrollRef}
+ data={items}
/>
) : null}
{
- eSendEvent(eOpenJumpToDialog, {
- ref: props.scrollRef,
- data: items
- });
- }}
+ ref={props.scrollRef}
+ data={items}
/>
) : null}
diff --git a/apps/mobile/app/components/properties/color-tags.tsx b/apps/mobile/app/components/properties/color-tags.tsx
index 5bcc1ab47..8dd5f6a48 100644
--- a/apps/mobile/app/components/properties/color-tags.tsx
+++ b/apps/mobile/app/components/properties/color-tags.tsx
@@ -43,6 +43,8 @@ import ColorPicker from "../dialogs/color-picker";
import PaywallSheet from "../sheets/paywall";
import { Button } from "../ui/button";
import { Pressable } from "../ui/pressable";
+import { Spacing } from "../../common/design/spacing";
+import AppIcon from "../ui/AppIcon";
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
const { colors } = useThemeColors();
@@ -80,12 +82,11 @@ const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
key={item.id}
onPress={toggleColor}
style={{
- width: 35,
- height: 35,
+ width: 40,
+ height: 40,
borderRadius: 100,
justifyContent: "center",
- alignItems: "center",
- marginRight: 5
+ alignItems: "center"
}}
>
{isLinked ? (
@@ -151,31 +152,10 @@ export const ColorTags = ({ item }: { item: Note }) => {
/>
- {!colorNotes || !colorNotes.length ? (
-
- ) : (
+ {colorNotes?.length ? (
{
bounces={false}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
+ contentContainerStyle={{
+ gap: Spacing.LEVEL_1
+ }}
ListFooterComponent={
{
type="secondary"
onPress={onPress}
>
-
}
/>
- )}
+ ) : null}
>
);
diff --git a/apps/mobile/app/components/properties/date-meta.tsx b/apps/mobile/app/components/properties/date-meta.tsx
index dce327781..f2cd8587a 100644
--- a/apps/mobile/app/components/properties/date-meta.tsx
+++ b/apps/mobile/app/components/properties/date-meta.tsx
@@ -29,6 +29,7 @@ import DateTimePickerModal from "react-native-modal-datetime-picker";
import { db } from "../../common/database";
import { Item, Note } from "@notesnook/core";
import AppIcon from "../ui/AppIcon";
+import { Radius, Spacing } from "../../common/design/spacing";
export const DateMeta = ({ item }: { item: Item }) => {
const { colors, isDark } = useThemeColors();
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
@@ -50,44 +51,52 @@ export const DateMeta = ({ item }: { item: Item }) => {
key={key}
style={{
flexDirection: "row",
+ width: "48.5%",
justifyContent: "space-between",
- paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2
+ backgroundColor: colors.secondary.background,
+ borderRadius: Radius.XS,
+ padding: Spacing.LEVEL_2,
+ gap: Spacing.LEVEL_1,
+ alignItems: "center"
}}
>
-
- {strings.dateDescFromKey(
- key as
- | "dateDeleted"
- | "dateEdited"
- | "dateModified"
- | "dateCreated"
- | "dateUploaded"
- )}
-
- {
- setIsDatePickerVisible(true);
- }
- }
- >
- {getFormattedDate(
- key === "dateCreated"
- ? dateCreated
- : (item[key as keyof Item] as string),
- "date-time"
- )}
- {key === "dateCreated" && item.type === "note" ? (
- <>
- {" "}
-
- >
- ) : null}
-
+
+
+ {strings.dateDescFromKey(
+ key as
+ | "dateDeleted"
+ | "dateEdited"
+ | "dateModified"
+ | "dateCreated"
+ | "dateUploaded"
+ )}
+
+ {
+ setIsDatePickerVisible(true);
+ }
+ }
+ >
+ {getFormattedDate(
+ key === "dateCreated"
+ ? dateCreated
+ : (item[key as keyof Item] as string),
+ "date-time"
+ )}
+
+
+
+ {key === "dateCreated" && item.type === "note" ? (
+ <>
+
+ >
+ ) : null}
);
@@ -119,9 +128,10 @@ export const DateMeta = ({ item }: { item: Item }) => {
{getDateMeta().map(renderItem)}
diff --git a/apps/mobile/app/components/properties/index.jsx b/apps/mobile/app/components/properties/index.jsx
index 009252b4d..d25ac64f3 100644
--- a/apps/mobile/app/components/properties/index.jsx
+++ b/apps/mobile/app/components/properties/index.jsx
@@ -18,14 +18,18 @@ along with this program. If not, see .
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
-import React from "react";
+import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
-import { eSendEvent, presentSheet } from "../../services/event-manager";
-import { eOnLoadNote } from "../../utils/events";
+import {
+ eSendEvent,
+ presentSheet,
+ sendItemUpdateEvent,
+ ToastManager
+} from "../../services/event-manager";
+import { eOnLoadNote, refreshNotesPage } from "../../utils/events";
import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
@@ -37,27 +41,43 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { DateMeta } from "./date-meta";
import { Items } from "./items";
-import Notebooks from "./notebooks";
-import { TagStrip, Tags } from "./tags";
+import { Tags } from "./tags";
import { Dialog } from "../dialog";
-
-const Line = ({ top = 6, bottom = 6 }) => {
- const { colors } = useThemeColors();
- return (
-
- );
-};
+import AppIcon from "../ui/AppIcon";
+import { Spacing } from "../../common/design/spacing";
+import { Button } from "../ui/button";
+import ManageTags from "../../screens/manage-tags";
+import ColorPicker from "../dialogs/color-picker";
+import { useRelationStore } from "../../stores/use-relation-store";
+import { useMenuStore } from "../../stores/use-menu-store";
+import Navigation from "../../services/navigation";
+import { useIsFeatureAvailable } from "@notesnook/common";
+import PaywallSheet from "../sheets/paywall";
+import { useSettingStore } from "../../stores/use-setting-store";
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
const { colors } = useThemeColors();
+ const colorFeature = useIsFeatureAvailable("colors");
+ const [noteNotebooks, setNoteNotebooks] = useState([]);
+ const [tags, setTags] = useState([]);
+ const [visible, setVisible] = useState(false);
+ const colorNotes = useMenuStore((state) => state.colorNotes);
+ useEffect(() => {
+ async function getNotebooks() {
+ let filteredNotebooks = await db.relations.to(item, "notebook").resolve();
+ return filteredNotebooks || [];
+ }
+ if (item.type === "note") {
+ getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
+ db.relations
+ .to(item, "tag")
+ .resolve()
+ .then((tags) => {
+ setTags(tags);
+ });
+ }
+ }, [item]);
+
if (!item || !item.id) {
return (
@@ -74,7 +94,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
backgroundColor: colors.primary.background,
borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1,
borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1,
- maxHeight: "100%"
+ maxHeight: "100%",
+ paddingTop: Spacing.LEVEL_3
}}
nestedScrollEnabled
bounces={false}
@@ -83,111 +104,240 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
renderItem={() => (
+ {item.type === "note" ? (
+ {
+ await db.relations.to(item, "color").unlink();
+ await db.relations.add(color, item);
+ useRelationStore.getState().update();
+ useMenuStore.getState().setColorNotes();
+ Navigation.queueRoutesForUpdate();
+ sendItemUpdateEvent(color.id, "color");
+ eSendEvent(refreshNotesPage);
+ }}
+ />
+ ) : null}
-
+
- {item.type === "color" ? (
- (
+
- ) : item.type === "tag" ? (
-
- ) : null}
+ >
+
+
+ {item.title}
+
+
+ ))}
- {item.title}
+ {tags?.map((item) =>
+ item.id ? (
+
+
+ {item.title}
+
+
+ ) : null
+ )}
- {item.type === "note" ? (
-
+ {
- close();
- eSendEvent(eOnLoadNote, {
- item: item,
- newTab: true
- });
- if (!DDS.isTab) {
- fluidTabsRef.current?.goToPage("editor");
- }
+ >
+ {item.type === "color" ? (
+
+ ) : item.type === "tag" ? (
+
+ ) : null}
+
+ {item.title}
+
+
+ {item.type === "note" ? (
+ {
+ close();
+ eSendEvent(eOnLoadNote, {
+ item: item,
+ newTab: true
+ });
+ if (!DDS.isTab) {
+ fluidTabsRef.current?.goToPage("editor");
+ }
+ }}
+ />
+ ) : null}
+
+
+ {(item.type === "notebook" || item.type === "reminder") &&
+ item.description ? (
+ {item.description}
+ ) : null}
+
+ {item.type === "reminder" ? (
+
) : null}
- {(item.type === "notebook" || item.type === "reminder") &&
- item.description ? (
- {item.description}
+
+
+ {item.type === "note" && colorNotes.length > 0 ? (
+
) : null}
- {item.type === "note" ? (
-
- ) : null}
-
- {item.type === "reminder" ? (
- 0 ? 1 : 0,
+ borderColor: colors.primary.border,
+ paddingVertical: Spacing.LEVEL_2,
+ paddingTop: colorNotes.length > 0 ? Spacing.LEVEL_2 : 0,
+ gap: Spacing.LEVEL_2
+ }}
+ >
+
-
-
-
- {item.type === "note" ? (
- <>
-
-
- >
- ) : null}
- {item.type === "note" ? (
+ {/* {item.type === "note" ? (
- ) : null}
+ ) : null} */}
@@ -161,12 +162,12 @@ export const Items = ({
type={item.checked ? "shade" : "secondary"}
testID={"icon-" + item.id}
style={{
- height: columnItemWidth / 1.5,
- width: columnItemWidth - 8,
+ width: columnItemWidth - 10,
+ paddingVertical: Spacing.LEVEL_2,
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
- marginBottom: DDS.isTab ? 7 : 3.5
+ marginBottom: 6
}}
>
{item.title}
@@ -321,8 +329,12 @@ export const Items = ({
[
colors.error.icon,
colors.primary.accent,
- colors.primary.border,
+ colors.primary.icon,
+ colors.primary.paragraph,
+ colors.primary.shade,
+ colors.secondary.background,
colors.secondary.icon,
+ colors.secondary.paragraph,
colors.static.orange,
columnItemWidth,
topBarSorting
@@ -352,16 +364,18 @@ export const Items = ({
autoplay={false}
showPagination
paginationStyleItemActive={{
- borderRadius: 2,
- backgroundColor: colors.selected.background,
- height: 6,
- marginHorizontal: 2
+ borderRadius: 6,
+ backgroundColor: colors.selected.accent,
+ height: 5,
+ width: 20,
+ marginHorizontal: 3
}}
paginationStyleItemInactive={{
- borderRadius: 2,
+ borderRadius: 6,
backgroundColor: colors.secondary.background,
- height: 6,
- marginHorizontal: 2
+ height: 5,
+ width: 14,
+ marginHorizontal: 3
}}
paginationStyle={{
position: "relative",
@@ -380,7 +394,7 @@ export const Items = ({
style={{
flexDirection: "row",
paddingHorizontal: DefaultAppStyles.GAP,
- gap: 5,
+ gap: Spacing.LEVEL_2,
width: width
}}
>
@@ -394,7 +408,7 @@ export const Items = ({
style={{
flexDirection: "row",
flexWrap: "wrap",
- gap: 5,
+ gap: Spacing.LEVEL_1,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
diff --git a/apps/mobile/app/components/properties/tags.jsx b/apps/mobile/app/components/properties/tags.jsx
index 8e14ccfd3..cdfa2f8b4 100644
--- a/apps/mobile/app/components/properties/tags.jsx
+++ b/apps/mobile/app/components/properties/tags.jsx
@@ -17,18 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
-import ManageTags from "../../screens/manage-tags";
import { TaggedNotes } from "../../screens/notes/tagged";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
+import { Spacing } from "../../common/design/spacing";
export const Tags = ({ item, close }) => {
const { colors } = useThemeColors();
@@ -37,32 +36,12 @@ export const Tags = ({ item, close }) => {
- {
- ManageTags.present([item.id]);
- close();
- }}
- buttonType={{
- text: colors.primary.accent
- }}
- title={strings.addTag()}
- type="secondary"
- icon="plus"
- iconPosition="right"
- fontSize={AppFontSize.xs}
- style={{
- paddingHorizontal: DefaultAppStyles.GAP_SMALL,
- paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
- }}
- />
) : null;
diff --git a/apps/mobile/app/components/sheets/add-notebook/index.tsx b/apps/mobile/app/components/sheets/add-notebook/index.tsx
index 3bcaa170d..8fd9d3d84 100644
--- a/apps/mobile/app/components/sheets/add-notebook/index.tsx
+++ b/apps/mobile/app/components/sheets/add-notebook/index.tsx
@@ -40,6 +40,7 @@ import { getElevationStyle } from "../../../utils/elevation";
import { defaultBorderRadius } from "../../../utils/size";
import { useThemeColors } from "@notesnook/theme";
import { getContainerBorder } from "../../../utils/colors";
+import { Radius, Spacing } from "../../../common/design/spacing";
export const AddNotebookSheet = ({
notebook,
@@ -127,53 +128,63 @@ export const AddNotebookSheet = ({
...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
- borderRadius: defaultBorderRadius,
+ borderRadius: Radius.LG,
backgroundColor: colors.primary.background,
- paddingTop: 12,
+ gap: Spacing.LEVEL_4,
+ paddingVertical: Spacing.LEVEL_4,
...getContainerBorder(colors.primary.border, 0.5),
overflow: "hidden"
}}
>
- {
- title.current = value;
+ {
- setTimeout(() => {
- titleInput?.current?.focus();
- }, 300);
- }}
- placeholder={strings.enterNotebookTitle()}
- onSubmit={() => {
- descriptionInput.current?.focus();
- }}
- returnKeyLabel="Next"
- returnKeyType="next"
- defaultValue={notebook ? notebook.title : title.current}
- />
+ >
+ {
+ title.current = value;
+ }}
+ onLayout={() => {
+ setTimeout(() => {
+ titleInput?.current?.focus();
+ }, 300);
+ }}
+ placeholder={"eg. My Notebook"}
+ onSubmit={() => {
+ descriptionInput.current?.focus();
+ }}
+ label={strings.enterNotebookTitle()}
+ returnKeyLabel="Next"
+ returnKeyType="next"
+ defaultValue={notebook ? notebook.title : title.current}
+ />
- {
- description.current = value;
- }}
- placeholder={strings.enterNotebookDescription()}
- returnKeyLabel={strings.next()}
- returnKeyType="next"
- defaultValue={notebook ? notebook.description : ""}
- />
+ {
+ description.current = value;
+ }}
+ label={strings.enterNotebookDescription()}
+ placeholder={"eg. This is My Notebook"}
+ returnKeyLabel={strings.next()}
+ returnKeyType="next"
+ defaultValue={notebook ? notebook.description : ""}
+ />
+
{
diff --git a/apps/mobile/app/components/sheets/sort/index.tsx b/apps/mobile/app/components/sheets/sort/index.tsx
index 5d2b88597..bb655f349 100644
--- a/apps/mobile/app/components/sheets/sort/index.tsx
+++ b/apps/mobile/app/components/sheets/sort/index.tsx
@@ -19,19 +19,22 @@ along with this program. If not, see .
import {
GroupingByIdKey,
+ GroupHeader,
GroupingKey,
GroupOptions,
+ Item,
ItemType,
- SortOptions
+ SortOptions,
+ VirtualizedGrouping
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
-import React, { useState } from "react";
-import { View } from "react-native";
import {
getGroupOptions,
setGroupOptionsById
} from "../../../hooks/use-group-options";
+import React, { RefObject, useEffect, useRef, useState } from "react";
+import { FlatList, View } from "react-native";
import { eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { RouteName } from "../../../stores/use-navigation-store";
@@ -46,13 +49,20 @@ import { Button } from "../../ui/button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
+import { Radius, Spacing } from "../../../common/design/spacing";
+import { getElevationStyle } from "../../../utils/elevation";
+import { useMessageStore } from "../../../stores/use-message-store";
+
const Sort = ({
dataType,
screen,
hideGroupOptions,
group: groupType,
groupId,
- type
+ type,
+ hideJumpToSection,
+ ref,
+ data
}: {
dataType: ItemType;
type?: GroupingByIdKey;
@@ -60,29 +70,42 @@ const Sort = ({
group: GroupingKey;
hideGroupOptions?: boolean;
groupId?: string;
+ hideJumpToSection?: boolean;
+ data?: VirtualizedGrouping- ;
+ ref?: RefObject;
}) => {
const { colors } = useThemeColors();
+ const [groups, setGroups] = useState<
+ {
+ index: number;
+ group: GroupHeader;
+ }[]
+ >();
+ const offsets = useRef([]);
+ const scrollRef = useRef>(undefined);
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const currentScrollPosition = useRef(0);
const [groupOptions, setGroupOptions] = useState(
getGroupOptions(groupType, groupId, type)
);
- const getSortButtonTitle = () => {
- const { sortDirection, groupBy, sortBy } = groupOptions || {};
+ const getSortButtonTitle = (type: "asc" | "desc") => {
+ const { groupBy, sortBy } = groupOptions || {};
const isAlphabetical = groupBy === "abc" || sortBy === "title";
const isDueDate = sortBy === "dueDate";
const isRelevance = sortBy === "relevance";
- if (sortDirection === "asc") {
+ if (type === "asc") {
if (isAlphabetical) return strings.aToZ();
if (isDueDate) return strings.earliestFirst();
if (isRelevance) return strings.leastRelevantFirst();
- return strings.oldNew();
+ return strings.oldestFirst();
} else {
if (isAlphabetical) return strings.zToA();
if (isDueDate) return strings.latestFirst();
if (isRelevance) return strings.mostRelevantFirst();
- return strings.newOld();
+ return strings.newestFirst();
}
};
@@ -111,13 +134,49 @@ const Sort = ({
await updateGroupOptions(_groupOptions);
};
+ useEffect(() => {
+ data?.groups?.().then((groups) => {
+ setGroups(groups);
+ offsets.current = [];
+ groups.map((item, index) => {
+ let offset = 35 * index;
+ let groupIndex = item.index;
+ const messageState = useMessageStore.getState().message;
+ const msgOffset = messageState?.visible ? 60 : 10;
+
+ groupIndex = groupIndex + 1;
+ groupIndex = groupIndex - (index + 1);
+ offset = offset + groupIndex * 100 + msgOffset;
+ offsets.current.push(offset);
+ });
+
+ const index = offsets.current?.findIndex((o, i) => {
+ return (
+ o <= currentScrollPosition.current + 100 &&
+ offsets.current[i + 1] - 100 > currentScrollPosition.current
+ );
+ });
+
+ setCurrentIndex(index < 0 ? 0 : index);
+ });
+ }, [data]);
+
+ const onPress = (item: { index: number; group: GroupHeader }) => {
+ scrollRef.current?.current?.scrollToIndex({
+ index: item.index,
+ animated: true
+ });
+ close();
+ };
+
return (
- {strings.sortBy()}
+ {strings.sortBy()}
-
+ /> */}
{Object.keys(SORT).map((item) => {
@@ -178,40 +239,92 @@ const Sort = ({
}
return (
- {
- const _groupOptions: GroupOptions = {
- ...groupOptions,
- sortBy: item as SortOptions["sortBy"]
- };
- await updateGroupOptions(_groupOptions);
+ gap: Spacing.LEVEL_2
}}
>
-
- {strings.sortByStrings[
- item as keyof typeof strings.sortByStrings
- ]()}
-
+ {
+ const _groupOptions: GroupOptions = {
+ ...groupOptions,
+ sortBy: item as SortOptions["sortBy"]
+ };
+ await updateGroupOptions(_groupOptions);
+ }}
+ >
+
+ {strings.sortByStrings[
+ item as keyof typeof strings.sortByStrings
+ ]()}
+
- {groupOptions?.sortBy === item ? (
-
+ {groupOptions?.sortBy === item ? (
+
+ ) : null}
+
+
+ {groupOptions.sortBy === item ? (
+
+
+
+
+
) : null}
-
+
);
})}
@@ -223,18 +336,19 @@ const Sort = ({
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
- paddingHorizontal: DefaultAppStyles.GAP,
- paddingVertical: DefaultAppStyles.GAP_VERTICAL
+ paddingHorizontal: Spacing.LEVEL_3,
+ paddingBottom: Spacing.LEVEL_3
}}
>
- {strings.groupBy()}
+ {strings.groupBy()}
{Object.keys(GROUP).map((item) => (
@@ -243,16 +357,14 @@ const Sort = ({
type={
groupOptions?.groupBy === GROUP[item as keyof typeof GROUP]
? "selected"
- : "plain"
+ : "plain-outline"
}
- noborder
style={{
- width: "100%",
- justifyContent: "space-between",
flexDirection: "row",
- borderRadius: 0,
- paddingHorizontal: DefaultAppStyles.GAP,
- paddingVertical: DefaultAppStyles.GAP_VERTICAL
+ width: "auto",
+ borderRadius: 100,
+ paddingHorizontal: Spacing.LEVEL_3,
+ paddingVertical: Spacing.LEVEL_1
}}
onPress={async () => {
const _groupOptions: GroupOptions = {
@@ -262,24 +374,82 @@ const Sort = ({
await updateGroupOptions(_groupOptions);
}}
>
-
+
{strings.groupByStrings[
item as keyof typeof strings.groupByStrings
]()}
-
- {groupOptions.groupBy === item ? (
-
- ) : null}
))}
>
) : null}
+
+ {!hideJumpToSection && groups ? (
+ <>
+
+ {strings.jumpToGroup()}
+
+
+
+ {groups?.map((item, index) => {
+ return (
+ onPress(item)}
+ type={currentIndex === index ? "selected" : "plain-outline"}
+ style={{
+ minWidth: "20%",
+ width: null,
+ borderRadius: 100,
+ paddingHorizontal: Spacing.LEVEL_3,
+ paddingVertical: Spacing.LEVEL_1
+ }}
+ >
+
+ {item.group.title}
+
+
+ );
+ })}
+
+ >
+ ) : null}
);
};
diff --git a/apps/mobile/app/components/side-menu/index.tsx b/apps/mobile/app/components/side-menu/index.tsx
index d92881ceb..6943ea5ea 100644
--- a/apps/mobile/app/components/side-menu/index.tsx
+++ b/apps/mobile/app/components/side-menu/index.tsx
@@ -21,7 +21,6 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useGroupOptions } from "../../hooks/use-group-options";
import { presentSheet, ToastManager } from "../../services/event-manager";
@@ -34,9 +33,7 @@ import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
import { AddNotebookSheet } from "../sheets/add-notebook";
import Sort from "../sheets/sort";
-import { IconButton } from "../ui/icon-button";
-import { Pressable } from "../ui/pressable";
-import Paragraph from "../ui/typography/paragraph";
+
import { SideMenuHome } from "./side-menu-home";
import { SideMenuNotebooks } from "./side-menu-notebooks";
import { SideMenuTags } from "./side-menu-tags";
@@ -44,12 +41,14 @@ import {
useSideMenuNotebookSelectionStore,
useSideMenuTagsSelectionStore
} from "./stores";
+import { TabBarButton } from "./tab-bar-button";
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";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
+import { Spacing } from "../../common/design/spacing";
/**
* Simple Tab View Implementation for the Side bar
@@ -77,7 +76,7 @@ type SimpleTabViewProps = {
};
const createSceneMap = (
- scenes: Record>
+ scenes: Record>
): ((props: { route: SimpleRoute }) => React.ReactNode) => {
// eslint-disable-next-line react/display-name
return ({ route }: { route: SimpleRoute }) => {
@@ -216,55 +215,61 @@ const TabBar = (props: SimpleTabBarProps) => {
const getIcon = (key: string) => {
switch (key) {
case "home":
- return "home-outline";
+ return "home";
case "notebooks":
- return "book-outline";
+ return "bookmark";
case "tags":
- return "pound";
+ return "shopping-mode";
default:
- return "home-outline";
+ return "home";
}
};
return (
- {isSelectionEnabled ? (
- <>
- {[
- {
- title: "Select all",
- icon: "check-all"
- },
- {
- title: "Delete",
- icon: "delete"
- },
- {
- title: "Move",
- icon: "arrow-right-bold-box-outline",
- hidden:
- !notebookSelectionEnabled || props.navigationState.index !== 1
- },
- {
- title: "Close",
- icon: "close"
- }
- ].map((item) =>
- item.hidden ? null : (
- <>
-
+ {isSelectionEnabled ? (
+ <>
+ {[
+ {
+ title: "Select all",
+ icon: "checks"
+ },
+ {
+ title: "Delete",
+ icon: "trash"
+ },
+ {
+ title: "Move",
+ icon: "drive-file-move",
+ hidden:
+ !notebookSelectionEnabled || props.navigationState.index !== 1
+ },
+ {
+ title: "Close",
+ icon: "close"
+ }
+ ].map((item) =>
+ item.hidden ? null : (
+ {
switch (item.title) {
case "Select all": {
@@ -325,221 +330,177 @@ const TabBar = (props: SimpleTabBarProps) => {
}
}
}}
+ />
+ )
+ )}
+ >
+ ) : (
+ <>
+ {dragging ? (
+ {
+ useSideBarDraggingStore.setState({
+ dragging: false
+ });
+ }}
+ style={{
+ width: "100%"
+ }}
+ type="accent"
+ testID="check"
+ title={strings.done()}
+ icon={"check"}
+ iconSize={AppFontSize.lg - 2}
+ />
+ ) : (
+ <>
+
-
-
- {item.title}
-
-
- >
- )
- )}
- >
- ) : (
- <>
- {dragging ? (
- {
- useSideBarDraggingStore.setState({
- dragging: false
- });
- }}
- style={{
- width: "100%"
- }}
- type="accent"
- testID="check"
- title={strings.done()}
- icon={"check"}
- iconSize={AppFontSize.lg - 2}
- />
- ) : (
- <>
-
- {props.navigationState.routes.map((route, index) => {
- const isFocused = props.navigationState.index === index;
+ {props.navigationState.routes.map((route, index) => {
+ const isFocused = props.navigationState.index === index;
- return (
- {
- props.jumpTo(route.key);
- switch (route.key) {
- case "notebooks":
- Navigation.routeNeedsUpdate(
- "Notebooks",
- Navigation.routeUpdateFunctions.Notebooks
- );
- break;
- case "tags":
- Navigation.routeNeedsUpdate(
- "Tags",
- Navigation.routeUpdateFunctions.Tags
- );
- break;
- default:
- break;
- }
- }}
- style={{
- borderRadius: 10,
- paddingVertical: 2,
- width: 40,
- height: 40
- }}
- type={isFocused ? "selected" : "plain"}
- >
- {
+ props.jumpTo(route.key);
+ switch (route.key) {
+ case "notebooks":
+ Navigation.routeNeedsUpdate(
+ "Notebooks",
+ Navigation.routeUpdateFunctions.Notebooks
+ );
+ break;
+ case "tags":
+ Navigation.routeNeedsUpdate(
+ "Tags",
+ Navigation.routeUpdateFunctions.Tags
+ );
+ break;
+ default:
+ break;
+ }
+ }}
/>
-
- );
- })}
-
+ );
+ })}
+
-
- {props.navigationState.index > 0 ? (
- <>
- {
- 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(),
- input: true,
- positiveText: "Add",
- positivePress: async (tag) => {
- if (tag) {
- await db.tags.add({
- title: tag
- });
- useTagStore.getState().refresh();
- return true;
- }
- ToastManager.show({
- context: "local",
- type: "error",
- message: strings.allFieldsRequired()
- });
- return false;
- }
- });
+
+ {props.navigationState.index > 0 ? (
+ <>
+
+ onPress={async () => {
+ if (props.navigationState.index === 1) {
+ const notebooksFeature =
+ await isFeatureAvailable("notebooks");
+ if (!notebooksFeature.isAllowed) {
+ PaywallSheet.present(notebooksFeature);
+ return;
+ }
- {
- presentSheet({
- component: (
- {
+ if (tag) {
+ await db.tags.add({
+ title: tag
+ });
+ useTagStore.getState().refresh();
+ return true;
+ }
+ ToastManager.show({
+ context: "local",
+ type: "error",
+ message: strings.allFieldsRequired()
+ });
+ return false;
}
- group={
- props.navigationState.index === 1
- ? "notebooks"
- : "tags"
- }
- hideGroupOptions
- />
- )
- });
- }}
- style={{
- width: 35,
- height: 35
- }}
- size={AppFontSize.lg - 2}
- />
- >
- ) : null}
+ });
+ }
+ }}
+ />
- {props.navigationState.index === 0 ? (
- <>
- {
- useThemeStore.getState().setColorScheme();
- }}
- style={{
- width: 28,
- height: 28
- }}
- top={10}
- testID="sidebar-theme-button"
- color={colors.primary.icon}
- name={isDark ? "weather-night" : "weather-sunny"}
- size={AppFontSize.lg - 2}
- />
- >
- ) : null}
-
- >
- )}
- >
- )}
+ {
+ presentSheet({
+ component: (
+
+ )
+ });
+ }}
+ />
+ >
+ ) : null}
+
+ {props.navigationState.index === 0 ? (
+ <>
+ {
+ useThemeStore.getState().setColorScheme();
+ }}
+ />
+ >
+ ) : null}
+
+ >
+ )}
+ >
+ )}
+
);
};
diff --git a/apps/mobile/app/components/side-menu/menu-item.tsx b/apps/mobile/app/components/side-menu/menu-item.tsx
index ff30aa8dd..86474e39a 100644
--- a/apps/mobile/app/components/side-menu/menu-item.tsx
+++ b/apps/mobile/app/components/side-menu/menu-item.tsx
@@ -20,10 +20,10 @@ along with this program. If not, see .
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useTotalNotes } from "../../hooks/use-db-item";
import { db } from "../../common/database";
+import { Radius, Spacing } from "../../common/design/spacing";
import {
eSubscribeEvent,
subscribeToItemUpdate
@@ -32,14 +32,14 @@ import Navigation from "../../services/navigation";
import useNavigationStore, {
RouteParams
} from "../../stores/use-navigation-store";
+import { useRelationStore } from "../../stores/use-relation-store";
import { eAfterSync, eMenuItemUpdate } from "../../utils/events";
import { SideMenuItem } from "../../utils/menu-items";
-import { AppFontSize, defaultBorderRadius } from "../../utils/size";
-import { DefaultAppStyles } from "../../utils/styles";
+import { AppFontSize } from "../../utils/size";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { useSideBarDraggingStore } from "./dragging-store";
-import { useRelationStore } from "../../stores/use-relation-store";
+import AppIcon from "../ui/AppIcon";
export function MenuItem({
item,
@@ -144,30 +144,32 @@ export function MenuItem({
style={{
width: "100%",
alignSelf: "center",
- borderRadius: defaultBorderRadius,
+ borderRadius: Radius.XS,
flexDirection: "row",
- paddingHorizontal: DefaultAppStyles.GAP_SMALL,
+ paddingHorizontal: Spacing.LEVEL_1,
justifyContent: "space-between",
alignItems: "center",
- paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
+ paddingVertical: Spacing.LEVEL_1,
+ marginBottom: Spacing.LEVEL_0
}}
>
{renderIcon ? (
renderIcon(item, AppFontSize.md)
) : (
-
{item.title}
{menuItemCount}
diff --git a/apps/mobile/app/components/side-menu/notebook-item.tsx b/apps/mobile/app/components/side-menu/notebook-item.tsx
index 1f95b0f6f..b960e61a4 100644
--- a/apps/mobile/app/components/side-menu/notebook-item.tsx
+++ b/apps/mobile/app/components/side-menu/notebook-item.tsx
@@ -24,19 +24,20 @@ import { StoreApi, UseBoundStore } from "zustand";
import { useTotalNotes } from "../../hooks/use-db-item";
import {
eSubscribeEvent,
- eUnSubscribeEvent,
- ToastManager
+ eUnSubscribeEvent
} from "../../services/event-manager";
import { TreeItem } from "../../stores/create-notebook-tree-stores";
import { SelectionStore } from "../../stores/item-selection-store";
import { eOnNotebookUpdated } from "../../utils/events";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
-import { DefaultAppStyles } from "../../utils/styles";
import AppIcon from "../ui/AppIcon";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { useRelationStore } from "../../stores/use-relation-store";
+import { Radius, Spacing } from "../../common/design/spacing";
+import Heading from "../ui/typography/heading";
+import { AddNotebookSheet } from "../sheets/add-notebook";
export const NotebookItem = ({
index,
@@ -94,20 +95,31 @@ export const NotebookItem = ({
};
}, [item.notebook.id, notebook.id, onItemUpdate]);
+ const itemPadding =
+ item.depth === 0 ? undefined : item.depth < 6 ? 15 * item.depth : 15 * 5;
+
return (
+ {item.depth > 0 ? (
+
+ ) : null}
0 ? 1 : undefined,
+ // borderLeftColor: colors.primary.border
}}
>
- {
- if (item.hasChildren && !disableExpand) {
- onToggleExpanded?.();
- } else {
- onPress?.();
+ {item.depth === 0 ? (
+
+ testID={item.hasChildren ? `expand-notebook-${index}` : ""}
+ style={{
+ borderRadius: defaultBorderRadius
+ }}
+ iconFamily="notesnook"
+ name={"bookmark"}
+ />
+ ) : null}
{selectionEnabled ? (
) : (
<>
-
+
{totalNotes?.(notebook?.id) || 0}
>
@@ -263,8 +263,81 @@ export const NotebookItem = ({
}}
/>
) : null}
+
+ {item.hasChildren ? (
+ {
+ if (item.hasChildren && !disableExpand) {
+ onToggleExpanded?.();
+ }
+ }}
+ top={0}
+ left={20}
+ bottom={0}
+ right={20}
+ style={{
+ borderRadius: defaultBorderRadius,
+ width: undefined,
+ height: undefined
+ }}
+ iconFamily="notesnook"
+ name={expanded ? "chevron-up" : "chevron-down"}
+ />
+ ) : null}
+ {expanded && item.hasChildren && !selectionEnabled ? (
+
+
+ {
+ AddNotebookSheet.present(undefined, item.notebook);
+ }}
+ >
+
+ Create sub-notebook
+ {" "}
+
+ ) : null}
);
};
diff --git a/apps/mobile/app/components/side-menu/pinned-section.tsx b/apps/mobile/app/components/side-menu/pinned-section.tsx
index 128a626a5..ae976cff9 100644
--- a/apps/mobile/app/components/side-menu/pinned-section.tsx
+++ b/apps/mobile/app/components/side-menu/pinned-section.tsx
@@ -70,7 +70,7 @@ export const PinnedSection = React.memo(
menuPins.map((item) => ({
id: item.id,
title: item.title,
- icon: item.type === "notebook" ? "notebook-outline" : "pound",
+ icon: item.type === "notebook" ? "bookmark" : "shopping-mode",
dataType: item.type,
data: item,
onPress: onPress,
diff --git a/apps/mobile/app/components/side-menu/side-menu-header.tsx b/apps/mobile/app/components/side-menu/side-menu-header.tsx
index 1486b7973..8ef3f31e1 100644
--- a/apps/mobile/app/components/side-menu/side-menu-header.tsx
+++ b/apps/mobile/app/components/side-menu/side-menu-header.tsx
@@ -30,6 +30,7 @@ import { Pressable } from "../ui/pressable";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import { useSideBarDraggingStore } from "./dragging-store";
+import { Radius, Spacing } from "../../common/design/spacing";
const SettingsIcon = () => {
const { colors } = useThemeColors();
@@ -77,31 +78,31 @@ export const SideMenuHeader = (props: { rightButtons?: IconButtonProps[] }) => {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
- borderBottomWidth: 1,
- borderBottomColor: colors.primary.border,
- paddingBottom: DefaultAppStyles.GAP,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
-
+
- Notesnook
+
+ Notesnook
+
@@ -129,7 +129,8 @@ export function SideMenuHome() {
>
)}
style={{
- paddingHorizontal: DefaultAppStyles.GAP
+ paddingHorizontal: DefaultAppStyles.GAP,
+ marginTop: Spacing.LEVEL_3
}}
nestedScrollEnabled={false}
/>
diff --git a/apps/mobile/app/components/side-menu/side-menu-list-empty.tsx b/apps/mobile/app/components/side-menu/side-menu-list-empty.tsx
index 8c65519a6..e92578600 100644
--- a/apps/mobile/app/components/side-menu/side-menu-list-empty.tsx
+++ b/apps/mobile/app/components/side-menu/side-menu-list-empty.tsx
@@ -23,9 +23,15 @@ import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import Paragraph from "../ui/typography/paragraph";
import { SideMenuHeader } from "./side-menu-header";
+import Heading from "../ui/typography/heading";
+import { Spacing } from "../../common/design/spacing";
+import { Button } from "../ui/button";
type SideMenuListEmptyProps = {
- placeholder: string;
+ placeholderTitle: string;
+ placeholderBody: string;
+ placeholderButtonTitle: string;
+ onPressPlaceholderButton: () => void;
isLoading?: boolean;
};
@@ -76,9 +82,39 @@ export const SideMenuListEmpty = (props: SideMenuListEmptyProps) => {
))}
) : (
-
- {props.placeholder}
-
+
+
+ {props.placeholderTitle}
+
+
+ {props.placeholderBody}
+
+
+
+
)}
diff --git a/apps/mobile/app/components/side-menu/side-menu-notebooks.tsx b/apps/mobile/app/components/side-menu/side-menu-notebooks.tsx
index 350018605..0b73b59e3 100644
--- a/apps/mobile/app/components/side-menu/side-menu-notebooks.tsx
+++ b/apps/mobile/app/components/side-menu/side-menu-notebooks.tsx
@@ -41,6 +41,9 @@ import {
} from "./stores";
import { LegendList } from "@legendapp/list";
import { useRelationStore } from "../../stores/use-relation-store";
+import { AddNotebookSheet } from "../sheets/add-notebook";
+import { Spacing } from "../../common/design/spacing";
+import AppIcon from "../ui/AppIcon";
useSideMenuNotebookSelectionStore.setState({
multiSelect: true
});
@@ -53,7 +56,7 @@ export const SideMenuNotebooks = () => {
const [filteredNotebooks, setFilteredNotebooks] = React.useState(notebooks);
const searchTimer = React.useRef(undefined);
const lastQuery = React.useRef(undefined);
- const updater = useRelationStore(state => state.updater);
+ const updater = useRelationStore((state) => state.updater);
const loadRootNotebooks = React.useCallback(async () => {
if (!filteredNotebooks) return;
const _notebooks: Notebook[] = [];
@@ -81,7 +84,7 @@ export const SideMenuNotebooks = () => {
useEffect(() => {
updateNotebooks();
- }, [updateNotebooks,updater]);
+ }, [updateNotebooks, updater]);
useEffect(() => {
(async () => {
@@ -136,7 +139,12 @@ export const SideMenuNotebooks = () => {
>
{!notebooks || notebooks.placeholders.length === 0 ? (
{
+ AddNotebookSheet.present();
+ }}
isLoading={isLoading}
/>
) : (
@@ -151,7 +159,8 @@ export const SideMenuNotebooks = () => {
@@ -159,34 +168,44 @@ export const SideMenuNotebooks = () => {
}
renderItem={renderItem}
/>
+
- {
- searchTimer.current && clearTimeout(searchTimer.current);
- searchTimer.current = setTimeout(async () => {
- lastQuery.current = value;
- updateNotebooks();
- }, 500);
- }}
- placeholderTextColor={colors.primary.placeholder}
- />
+ >
+ {
+ searchTimer.current && clearTimeout(searchTimer.current);
+ searchTimer.current = setTimeout(async () => {
+ lastQuery.current = value;
+ updateNotebooks();
+ }, 500);
+ }}
+ placeholderTextColor={colors.primary.placeholder}
+ />
+
+
+
>
)}
@@ -235,8 +254,7 @@ const NotebookItemWrapper = React.memo(
return (
;
@@ -67,11 +70,8 @@ const TagItem = (props: {
return (
{item ? (
@@ -118,31 +118,24 @@ const TagItem = (props: {
width: "100%",
alignItems: "center",
flexDirection: "row",
- borderRadius: defaultBorderRadius,
- paddingRight: DefaultAppStyles.GAP_SMALL
+ borderRadius: Radius.XS,
+ padding: Spacing.LEVEL_1
}}
>
-
-
-
+
) : (
<>
- {item?.id && totalNotes.totalNotes?.(item?.id) ? (
+ {item?.id && totalNotes.totalNotes?.(item?.id) !== undefined ? (
{
setLoading(false);
}, [tags]);
+ const onPressAddTag = React.useCallback(() => {
+ presentDialog({
+ title: strings.addTag(),
+ // paragraph: strings.addTagDesc(),
+ input: true,
+ inputLabel: "Enter title",
+ inputPlaceholder: "eg. journal",
+ positiveText: strings.add(),
+ positivePress: async (tag) => {
+ if (tag) {
+ await db.tags.add({
+ title: tag
+ });
+ useTagStore.getState().refresh();
+ return true;
+ }
+ ToastManager.show({
+ context: "local",
+ type: "error",
+ message: strings.allFieldsRequired()
+ });
+ return false;
+ }
+ });
+ }, []);
+
useEffect(() => {
if (!isLoading) {
updateTags();
@@ -262,7 +281,10 @@ export const SideMenuTags = () => {
>
{!tags || tags?.placeholders.length === 0 ? (
) : (
@@ -278,7 +300,8 @@ export const SideMenuTags = () => {
@@ -288,36 +311,45 @@ export const SideMenuTags = () => {
/>
- {
- searchTimer.current && clearTimeout(searchTimer.current);
- searchTimer.current = setTimeout(async () => {
- try {
- lastQuery.current = value;
- updateTags();
- } catch (e) {
- DatabaseLogger.error(e);
- }
- }, 100);
- }}
- placeholderTextColor={colors.primary.placeholder}
- />
+ >
+ {
+ searchTimer.current && clearTimeout(searchTimer.current);
+ searchTimer.current = setTimeout(async () => {
+ try {
+ lastQuery.current = value;
+ updateTags();
+ } catch (e) {
+ DatabaseLogger.error(e);
+ }
+ }, 100);
+ }}
+ placeholderTextColor={colors.primary.placeholder}
+ />
+
+
+
>
)}
diff --git a/apps/mobile/app/components/side-menu/tab-bar-button.tsx b/apps/mobile/app/components/side-menu/tab-bar-button.tsx
new file mode 100644
index 000000000..d553df05f
--- /dev/null
+++ b/apps/mobile/app/components/side-menu/tab-bar-button.tsx
@@ -0,0 +1,111 @@
+/*
+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 { useThemeColors } from "@notesnook/theme";
+import React from "react";
+import { Pressable } from "../ui/pressable";
+import Paragraph from "../ui/typography/paragraph";
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withSpring
+} from "react-native-reanimated";
+import { Radius, Spacing } from "../../common/design/spacing";
+import AppIcon from "../ui/AppIcon";
+
+type TabBarButtonProps = {
+ icon: string;
+ label?: string;
+ onPress: () => void;
+ isActive?: boolean;
+ testID?: string;
+};
+
+export const TabBarButton = ({
+ icon,
+ label,
+ onPress,
+ isActive = false,
+ testID
+}: TabBarButtonProps) => {
+ const { colors } = useThemeColors();
+ const scale = useSharedValue(1);
+
+ const animatedIconStyle = useAnimatedStyle(() => ({
+ transform: [{ scale: scale.value }]
+ }));
+
+ const handlePress = () => {
+ scale.value = withSpring(0.85, {
+ damping: 100,
+ mass: 1,
+ overshootClamping: false
+ });
+
+ setTimeout(() => {
+ scale.value = withSpring(1, {
+ damping: 100,
+ mass: 1,
+ overshootClamping: false
+ });
+ }, 100);
+
+ onPress();
+ };
+
+ return (
+
+
+
+
+
+ {label && (
+
+ {label}
+
+ )}
+
+ );
+};
diff --git a/apps/mobile/app/components/ui/AppIcon/index.tsx b/apps/mobile/app/components/ui/AppIcon/index.tsx
index 3d2a1a32c..7cdd050cd 100644
--- a/apps/mobile/app/components/ui/AppIcon/index.tsx
+++ b/apps/mobile/app/components/ui/AppIcon/index.tsx
@@ -22,6 +22,10 @@ import { ColorValue, TextProps } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import EvilIcon from "react-native-vector-icons/EvilIcons";
import { AppFontSize } from "../../../utils/size";
+import { createNanoIconSet } from "react-native-nano-icons";
+import glyphMap from "../../../../fonts/notesnook-icons.glyphmap.json";
+
+const NotesnookIcon = createNanoIconSet(glyphMap);
export interface IconProps extends TextProps {
/**
@@ -43,9 +47,9 @@ export interface IconProps extends TextProps {
* Color of the icon
*
*/
- color?: ColorValue | number | undefined;
+ color?: ColorValue | ColorValue[] | number | undefined;
- iconFamily?: "evilicons" | "material";
+ iconFamily?: "evilicons" | "material" | "notesnook";
}
export default function AppIcon({
@@ -59,6 +63,12 @@ export default function AppIcon({
color={colors.primary.icon}
{...(props as any)}
/>
+ ) : iconFamily === "notesnook" ? (
+
) : (
{
@@ -142,8 +144,9 @@ export const Button = ({
) : null}
{icon && !loading && iconPosition === "left" ? (
- {
const { colors } = useThemeColors();
@@ -100,8 +104,10 @@ export const IconButton = ({
...style
}}
>
- .
*/
-import React, { RefObject, useState } from "react";
+import { useThemeColors } from "@notesnook/theme";
+import phone from "phone";
+import React, { RefObject, useRef, useState } from "react";
import {
ColorValue,
+ findNodeHandle,
NativeSyntheticEvent,
TextInput,
TextInputProps,
@@ -28,22 +31,17 @@ import {
View,
ViewStyle
} from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
+import isURL from "validator/lib/isURL";
+import { Spacing } from "../../../common/design/spacing";
import {
- ERRORS_LIST,
validateEmail,
validatePass,
validateUsername
} from "../../../services/validation";
-import { useThemeColors } from "@notesnook/theme";
-import { getElevationStyle } from "../../../utils/elevation";
-import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
+import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
-import phone from "phone";
-import isURL from "validator/lib/isURL";
-import { DefaultAppStyles } from "../../../utils/styles";
-import { Spacing } from "../../../common/design/spacing";
+import { useInputError } from "./input-error-context";
interface InputProps extends TextInputProps {
fwdRef?: RefObject;
@@ -113,6 +111,9 @@ const Input = ({
...restProps
}: InputProps) => {
const { colors, isDark } = useThemeColors();
+ const errorCtx = useInputError();
+ const internalRef = useRef(null);
+ const activeRef = fwdRef ?? internalRef;
const [error, setError] = useState(false);
const [focus, setFocus] = useState(false);
const [secureEntry, setSecureEntry] = useState(true);
@@ -121,6 +122,12 @@ const Input = ({
SHORT_PASS: false
});
type ErrorKey = keyof typeof errorList;
+
+ const reportError = (message: string | null) => {
+ if (!errorCtx) return;
+ const nativeId = findNodeHandle(activeRef.current);
+ if (nativeId !== null) errorCtx.setError(nativeId, message);
+ };
const color = error
? colors.error.border
: focus
@@ -137,34 +144,30 @@ const Input = ({
});
return;
}
- let isError:
- | boolean
- | string
- | { SHORT_PASS?: boolean; isValid?: boolean }
- | undefined = undefined;
+ let isValid: boolean | string | undefined = undefined;
switch (validationType) {
case "password":
- isError = validatePass(value);
+ isValid = validatePass(value);
break;
case "email":
- isError = validateEmail(value);
+ isValid = validateEmail(value);
break;
case "username":
- isError = validateUsername(value);
+ isValid = validateUsername(value);
break;
case "confirmPassword":
- isError = customValidator && value === customValidator();
+ isValid = customValidator && value === customValidator();
break;
case "url":
- isError = isURL(value, { allow_underscores: true });
+ isValid = isURL(value, { allow_underscores: true });
break;
case "phonenumber": {
const result = phone(value, {
strictDetection: true,
validateMobilePrefix: true
});
- isError = result.isValid;
+ isValid = result.isValid;
if (result.isValid) {
onChangeText && onChangeText(result.phoneNumber);
}
@@ -173,23 +176,10 @@ const Input = ({
}
}
- if (validationType === "password") {
- let hasError = false;
-
- const errors = isError as { [name: string]: boolean };
- Object.keys(errors).forEach((e) => {
- //ts-ignore
- if (errors[e] === true) {
- hasError = true;
- }
- });
- setError(hasError);
- onErrorCheck && onErrorCheck(hasError);
- setErrorList(errors as { SHORT_PASS: boolean });
- } else {
- setError(!isError);
- onErrorCheck && onErrorCheck(!isError);
- }
+ const hasError = !isValid;
+ setError(hasError);
+ onErrorCheck && onErrorCheck(hasError);
+ reportError(hasError ? (errorMessage ?? null) : null);
};
const onChange = (value: string) => {
@@ -201,6 +191,7 @@ const Input = ({
setErrorList({
SHORT_PASS: false
});
+ reportError(null);
}
};
@@ -221,7 +212,7 @@ const Input = ({
const style: ViewStyle = {
borderWidth: 1,
borderRadius: defaultBorderRadius,
- borderColor: color,
+ borderColor: error ? colors.static.red : color,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
@@ -277,10 +268,8 @@ const Input = ({
{
- console.log(e.nativeEvent.layout);
- }}
+ ref={activeRef}
+ onLayout={restProps.onLayout}
editable={!loading && restProps.editable}
onChangeText={onChange}
onBlur={onBlur}
@@ -343,86 +332,9 @@ const Input = ({
}}
/>
)}
-
- {error && (
- {
- setShowError(!showError);
- }}
- size={20}
- style={{
- width: 25,
- marginLeft: 5
- }}
- color={colors.error.icon}
- />
- )}
-
- {error && showError && errorMessage ? (
-
-
- {" "}
- {errorMessage}
-
-
- ) : null}
-
- {validationType === "password" &&
- focus &&
- Object.keys(errorList).filter((k) => errorList[k as ErrorKey] === true)
- .length !== 0 ? (
-
- {Object.keys(ERRORS_LIST).map((error) => (
-
-
-
-
- {ERRORS_LIST[error as ErrorKey]}
-
-
- ))}
-
- ) : null}
>
);
};
diff --git a/apps/mobile/app/components/ui/input/input-error-context.tsx b/apps/mobile/app/components/ui/input/input-error-context.tsx
new file mode 100644
index 000000000..b76eab777
--- /dev/null
+++ b/apps/mobile/app/components/ui/input/input-error-context.tsx
@@ -0,0 +1,120 @@
+/*
+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 React, {
+ createContext,
+ RefObject,
+ useCallback,
+ useContext,
+ useMemo,
+ useRef,
+ useState
+} from "react";
+import { findNodeHandle, TextInput, View } from "react-native";
+import Paragraph from "../typography/paragraph";
+import { useThemeColors } from "@notesnook/theme";
+import { Spacing } from "../../../common/design/spacing";
+import AppIcon from "../AppIcon";
+
+interface InputErrorContextType {
+ setError: (nativeId: number, message: string | null) => void;
+ getError: (nativeId: number) => string | null;
+}
+
+const InputErrorContext = createContext(null);
+
+export function InputErrorProvider({
+ children
+}: {
+ children: React.ReactNode;
+}) {
+ const [errors, setErrors] = useState