mobile: fix reorderable side menu

This commit is contained in:
Ammar Ahmed
2024-02-06 00:00:14 +05:00
committed by Abdullah Atta
parent feba11626d
commit a65d271cbd
11 changed files with 699 additions and 864 deletions

View File

@@ -28,9 +28,8 @@ import {
} from "react-native-drax";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import Paragraph from "../ui/typography/paragraph";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button";
interface ReorderableListProps<T extends { id: string }>
extends Omit<DraxListProps<T>, "renderItem" | "data" | "renderItemContent"> {
@@ -39,7 +38,8 @@ interface ReorderableListProps<T extends { id: string }>
data: T[];
itemOrder: string[];
hiddenItems: string[];
onHiddenItemsChanged: (data: string[]) => void;
onHiddenItemsChanged?: (data: string[]) => void;
canHideItems?: boolean;
}
function ReorderableList<T extends { id: string }>({
@@ -49,6 +49,7 @@ function ReorderableList<T extends { id: string }>({
hiddenItems = [],
itemOrder = [],
onHiddenItemsChanged,
canHideItems = true,
...restProps
}: ReorderableListProps<T>) {
const { colors } = useThemeColors();
@@ -86,7 +87,7 @@ function ReorderableList<T extends { id: string }>({
>
{renderDraggableItem(info, props)}
</View>
{dragging ? (
{dragging && canHideItems ? (
<IconButton
name={!isHidden ? "minus" : "plus"}
color={colors.primary.icon}
@@ -101,7 +102,7 @@ function ReorderableList<T extends { id: string }>({
} else {
_hiddenItems.splice(index, 1);
}
onHiddenItemsChanged(_hiddenItems);
onHiddenItemsChanged?.(_hiddenItems);
setHiddenItems(_hiddenItems);
}}
/>
@@ -114,7 +115,8 @@ function ReorderableList<T extends { id: string }>({
dragging,
hiddenItemsState,
onHiddenItemsChanged,
renderDraggableItem
renderDraggableItem,
canHideItems
]
);
@@ -128,7 +130,6 @@ function ReorderableList<T extends { id: string }>({
items.splice(index, 0, item);
}
});
console.log(items.map((item) => item.id));
return items;
}
@@ -166,7 +167,6 @@ function ReorderableList<T extends { id: string }>({
} else {
newOrder.splice(toIndex, 0, element);
}
console.log(newOrder);
setItemsOrder(newOrder);
onListOrderChanged?.(newOrder);
}}

View File

@@ -21,6 +21,7 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import { FlatList, View } from "react-native";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
@@ -31,16 +32,15 @@ import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { MenuItemsList } from "../../utils/menu-items";
import { SIZE } from "../../utils/size";
import ReorderableList from "../list/reorderable-list";
import { IconButton } from "../ui/icon-button";
import Paragraph from "../ui/typography/paragraph";
import { ColorSection } from "./color-section";
import { useSideBarDraggingStore } from "./dragging-store";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { UserStatus } from "./user-status";
import ReorderableList from "../list/reorderable-list";
import { db } from "../../common/database";
import { useSideBarDraggingStore } from "./dragging-store";
import Paragraph from "../ui/typography/paragraph";
import { IconButton } from "../ui/icon-button";
import { SIZE } from "../../utils/size";
export const SideMenu = React.memo(
function SideMenu() {
@@ -55,7 +55,6 @@ export const SideMenu = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const noTextMode = false;
const BottomItemsList = [
{
name: isDark ? "Day" : "Night",
@@ -89,13 +88,13 @@ export const SideMenu = React.memo(
<>
<ReorderableList
onListOrderChanged={(data) => {
db.settings.setSideBarOrder("menu", data);
db.settings.setSideBarOrder("routes", data);
}}
onHiddenItemsChanged={(data) => {
db.settings.setSideBarHiddenItems("menu", data);
db.settings.setSideBarHiddenItems("routes", data);
}}
itemOrder={db.settings.getSideBarOrder("menu")}
hiddenItems={db.settings.getSideBarHiddenItems("menu")}
itemOrder={db.settings.getSideBarOrder("routes")}
hiddenItems={db.settings.getSideBarHiddenItems("routes")}
alwaysBounceVertical={false}
data={MenuItemsList}
style={{
@@ -113,11 +112,11 @@ export const SideMenu = React.memo(
);
}}
/>
<ColorSection noTextMode={noTextMode} />
<ColorSection />
<PinnedSection />
</>
),
[noTextMode]
[]
);
return !isAppLoading && introCompleted ? (
@@ -144,12 +143,14 @@ export const SideMenu = React.memo(
marginBottom: 12,
justifyContent: "space-between",
alignItems: "center",
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
paddingHorizontal: 12
paddingHorizontal: 12,
backgroundColor: colors.secondary.background,
marginHorizontal: 12,
marginTop: 5,
paddingVertical: 6
}}
>
<Paragraph size={SIZE.xs + 1}>REORDER SIDEBAR</Paragraph>
<Paragraph size={SIZE.sm}>REORDERING</Paragraph>
<IconButton
name="close"
@@ -184,13 +185,7 @@ export const SideMenu = React.memo(
>
{subscriptionType === SUBSCRIPTION_STATUS.TRIAL ||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
<MenuItem
testID={pro.name}
key={pro.name}
item={pro}
index={0}
ignore={true}
/>
<MenuItem testID={pro.name} key={pro.name} item={pro} index={0} />
) : null}
{BottomItemsList.slice(DDS.isLargeTablet() ? 0 : 1, 3).map(
@@ -204,10 +199,9 @@ export const SideMenu = React.memo(
key={item.name}
item={item}
index={index}
ignore={true}
rightBtn={
DDS.isLargeTablet() || item.name === "Notesnook Pro"
? null
? undefined
: BottomItemsList[0]
}
/>
@@ -221,7 +215,7 @@ export const SideMenu = React.memo(
paddingHorizontal: 0
}}
>
<UserStatus noTextMode={noTextMode} />
<UserStatus />
</View>
</View>
</View>

View File

@@ -1,157 +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 <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { SIZE, normalize } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const MenuItem = React.memo(
function MenuItem({ item, index, testID, rightBtn }) {
const { colors } = useThemeColors();
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.name
);
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
if (item.func) {
item.func();
} else {
if (useNavigationStore.getState().currentRoute !== item.name) {
Navigation.navigate(item.name, {
canGoBack: false,
beta: item.isBeta
});
}
}
if (item.close) {
setImmediate(() => {
Navigation.closeDrawer();
});
}
};
return (
<PressableButton
testID={testID}
key={item.name + index}
onPress={_onPress}
type={isFocused ? "selected" : "gray"}
customStyle={{
width: "100%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 8,
justifyContent: "space-between",
alignItems: "center",
height: normalize(50),
marginBottom: 5
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<Icon
style={{
width: 30,
textAlignVertical: "center",
textAlign: "left"
}}
allowFontScaling
name={item.icon}
color={
item.icon === "crown"
? colors.static.yellow
: isFocused
? colors.selected.icon
: colors.secondary.icon
}
size={SIZE.lg - 2}
/>
{isFocused ? (
<Heading color={colors.selected.heading} size={SIZE.md}>
{item.name}
</Heading>
) : (
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
)}
{item.isBeta ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
</Paragraph>
</View>
) : null}
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}
/>
) : rightBtn ? (
<Button
title={rightBtn.name}
type="shade"
height={30}
fontSize={SIZE.xs}
iconSize={SIZE.xs}
icon={rightBtn.icon}
style={{
borderRadius: 100,
paddingHorizontal: 16
}}
onPress={rightBtn.func}
/>
) : null}
</PressableButton>
);
},
(prev, next) => {
if (prev.item.name !== next.item.name) return false;
if (prev.rightBtn?.name !== next.rightBtn?.name) return false;
return true;
}
);

View File

@@ -0,0 +1,170 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { SIZE, normalize } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
function _MenuItem({
item,
index,
testID,
rightBtn
}: {
item: any;
index: number;
testID: string;
rightBtn?: {
name: string;
icon: string;
func: () => void;
};
}) {
const { colors } = useThemeColors();
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.name
);
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
if (item.func) {
item.func();
} else {
if (useNavigationStore.getState().currentRoute !== item.name) {
Navigation.navigate(item.name, {
canGoBack: false,
beta: item.isBeta
});
}
}
if (item.close) {
setImmediate(() => {
Navigation.closeDrawer();
});
}
};
return (
<PressableButton
testID={testID}
key={item.name + index}
onPress={_onPress}
type={isFocused ? "selected" : "gray"}
customStyle={{
width: "100%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 8,
justifyContent: "space-between",
alignItems: "center",
height: normalize(50),
marginBottom: 5
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<Icon
style={{
width: 30,
textAlignVertical: "center",
textAlign: "left"
}}
allowFontScaling
name={item.icon}
color={
item.icon === "crown"
? colors.static.yellow
: isFocused
? colors.selected.icon
: colors.secondary.icon
}
size={SIZE.lg - 2}
/>
{isFocused ? (
<Heading color={colors.selected.heading} size={SIZE.md}>
{item.name}
</Heading>
) : (
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
)}
{item.isBeta ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
</Paragraph>
</View>
) : null}
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}
/>
) : rightBtn ? (
<Button
title={rightBtn.name}
type="shade"
height={30}
fontSize={SIZE.xs}
iconSize={SIZE.xs}
icon={rightBtn.icon}
style={{
borderRadius: 100,
paddingHorizontal: 16
}}
onPress={rightBtn.func}
/>
) : null}
</PressableButton>
);
}
export const MenuItem = React.memo(_MenuItem, (prev, next) => {
if (prev.item.name !== next.item.name) return false;
if (prev.rightBtn?.name !== next.rightBtn?.name) return false;
return true;
});

View File

@@ -74,13 +74,12 @@ export const PinnedSection = React.memo(
>
<ReorderableList
onListOrderChanged={(data) => {
db.settings.setSideBarOrder("pinned", data);
db.settings.setSideBarOrder("shortcuts", data);
}}
onHiddenItemsChanged={(data) => {
db.settings.setSideBarHiddenItems("pinned", data);
}}
itemOrder={db.settings.getSideBarOrder("pinned")}
hiddenItems={db.settings.getSideBarHiddenItems("pinned")}
onHiddenItemsChanged={(data) => {}}
canHideItems={false}
itemOrder={db.settings.getSideBarOrder("shortcuts")}
hiddenItems={[]}
alwaysBounceVertical={false}
data={menuPins}
style={{

View File

@@ -209,6 +209,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
onDrawerStateChange(true);
},
closeDrawer: (animated = true) => {
if (forcedLock.value) return;
if (deviceMode === "tablet") {
translateX.value = animated ? withTiming(0) : 0;
return;

File diff suppressed because it is too large Load Diff

View File

@@ -1018,6 +1018,7 @@
"clipboard-polyfill": "4.0.0",
"comlink": "^4.3.1",
"cronosjs": "^1.7.1",
"date-fns": "^2.30.0",
"dayjs": "1.11.9",
"electron-trpc": "0.5.2",
"event-source-polyfill": "^1.0.25",
@@ -1037,6 +1038,7 @@
"qclone": "^1.2.0",
"react": "18.2.0",
"react-complex-tree": "^2.2.4",
"react-day-picker": "^8.9.1",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1",
@@ -2016,15 +2018,6 @@
"@trpc/server": "10.38.3"
}
},
"node_modules/@trpc/server": {
"version": "10.38.3",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.38.3.tgz",
"integrity": "sha512-9s8/kwo2IDB5hwB2SKZZrfevRhdb1f9fdXtIYd3lbQuf2jQaC/LyQuHaIQjDQoUx9updBfsHXcFFPiCP1DL6pg==",
"funding": [
"https://trpc.io/sponsor"
],
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.1",
"dev": true,

253
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4358,7 +4358,7 @@
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"devOptional": true
"dev": true
},
"node_modules/@types/q": {
"version": "1.5.8",
@@ -4382,7 +4382,7 @@
"version": "18.2.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
"devOptional": true,
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4417,7 +4417,7 @@
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"devOptional": true
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -9713,7 +9713,7 @@
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"devOptional": true,
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -17758,20 +17758,6 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View File

@@ -973,6 +973,22 @@
"@styled-system/css": "^5.1.5"
}
},
"node_modules/@theme-ui/color-modes": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.1.tgz",
"integrity": "sha512-G2YoNEMwZroRS0DcftUG+E/8WM5/Osf8TRrQLLK+L43HJ4BmaWuBmVeyoNOaPBDlAuqMBx2203VRgoPmUaMqOg==",
"dev": true,
"peer": true,
"dependencies": {
"@theme-ui/core": "^0.16.1",
"@theme-ui/css": "^0.16.1",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/components": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
@@ -1018,6 +1034,22 @@
"@emotion/react": "^11.11.1"
}
},
"node_modules/@theme-ui/theme-provider": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.1.tgz",
"integrity": "sha512-+/3BJYLIOC2DwTS76cqNhigRQJJ+qOT845DYF7t3TaG2fXDfgh16/DGZSnVjGOGc9dYE3C/ZFAYcVDVwO94Guw==",
"dev": true,
"peer": true,
"dependencies": {
"@theme-ui/color-modes": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@theme-ui/css": "^0.16.1"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@tiptap/core": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.12.tgz",
@@ -2508,8 +2540,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
@@ -2573,7 +2604,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -3149,7 +3179,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -3170,7 +3199,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
@@ -3302,7 +3330,6 @@
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
}