From c6c08490985a3e109f5c3cef1119ff21e7eb4907 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Fri, 14 Mar 2025 16:17:08 +0500 Subject: [PATCH] mobile: add notebooks button in notebook screen to view subnotebooks --- .../components/container/floating-button.tsx | 28 +- .../app/components/sheet-provider/index.js | 2 + .../app/components/sheets/notebooks/index.tsx | 319 ++++++++++++++++++ apps/mobile/app/screens/notebook/index.tsx | 38 ++- .../native/fonts/MaterialCommunityIcons.ttf | Bin 27036 -> 27432 bytes apps/mobile/native/rspack.config.js | 2 +- apps/mobile/scripts/optimize-fonts.mjs | 5 +- 7 files changed, 377 insertions(+), 17 deletions(-) create mode 100644 apps/mobile/app/components/sheets/notebooks/index.tsx diff --git a/apps/mobile/app/components/container/floating-button.tsx b/apps/mobile/app/components/container/floating-button.tsx index 3dc8e7990..2f0402594 100644 --- a/apps/mobile/app/components/container/floating-button.tsx +++ b/apps/mobile/app/components/container/floating-button.tsx @@ -20,7 +20,7 @@ along with this program. If not, see . import { useThemeColors } from "@notesnook/theme"; import { useRoute } from "@react-navigation/native"; import React, { useCallback, useEffect } from "react"; -import { Keyboard, TouchableOpacity, View } from "react-native"; +import { Keyboard, TouchableOpacity, View, ViewStyle } from "react-native"; import Animated, { Easing, useAnimatedStyle, @@ -43,6 +43,9 @@ interface FloatingButtonProps { alwaysVisible?: boolean; icon?: string; testID?: string; + position?: "left" | "right"; + size?: "small" | "large"; + style?: ViewStyle; } const FloatingButton = ({ @@ -50,7 +53,10 @@ const FloatingButton = ({ color, alwaysVisible = false, icon, - testID + testID, + position = "right", + size = "large", + style }: FloatingButtonProps) => { const { colors } = useThemeColors(); const deviceMode = useSettingStore((state) => state.deviceMode); @@ -112,9 +118,11 @@ const FloatingButton = ({ style={[ { position: "absolute", - right: DefaultAppStyles.GAP, + right: position === "right" ? DefaultAppStyles.GAP : undefined, + left: position === "left" ? DefaultAppStyles.GAP : undefined, bottom: 20, - zIndex: 10 + zIndex: 10, + ...style }, animatedStyle ]} @@ -123,8 +131,8 @@ const FloatingButton = ({ testID={testID || notesnook.buttons.add} activeOpacity={0.95} style={{ - ...getElevationStyle(10), - borderRadius: 20, + ...getElevationStyle(5), + borderRadius: size === "small" ? 15 : 20, borderTopWidth: 0, borderBottomWidth: 0, borderLeftWidth: 0, @@ -137,10 +145,10 @@ const FloatingButton = ({ style={{ alignItems: "center", justifyContent: "center", - height: normalize(60), - width: normalize(60), + height: normalize(size === "small" ? 40 : 60), + width: normalize(size === "small" ? 40 : 60), backgroundColor: colors.primary.shade, - borderRadius: 20 + borderRadius: size === "small" ? 15 : 20 }} > diff --git a/apps/mobile/app/components/sheet-provider/index.js b/apps/mobile/app/components/sheet-provider/index.js index 4331ee156..49cacce50 100644 --- a/apps/mobile/app/components/sheet-provider/index.js +++ b/apps/mobile/app/components/sheet-provider/index.js @@ -95,6 +95,8 @@ const SheetProvider = ({ context = "global" }) => { [context] ); + console.log(data?.keyboardHandlerDisabled); + return !visible || !data ? null : ( . +*/ + +import { Notebook, VirtualizedGrouping } from "@notesnook/core"; +import { strings } from "@notesnook/intl"; +import { useThemeColors } from "@notesnook/theme"; +import React, { useEffect, useState } from "react"; +import { FlatList, View } from "react-native"; +import { db } from "../../../common/database"; +import NotebookScreen from "../../../screens/notebook"; +import { + eSendEvent, + eSubscribeEvent, + presentSheet +} from "../../../services/event-manager"; +import { + createNotebookTreeStores, + TreeItem +} from "../../../stores/create-notebook-tree-stores"; +import useNavigationStore from "../../../stores/use-navigation-store"; +import { eCloseSheet, eOnNotebookUpdated } from "../../../utils/events"; +import { AppFontSize } from "../../../utils/size"; +import { DefaultAppStyles } from "../../../utils/styles"; +import { sleep } from "../../../utils/time"; +import { Properties } from "../../properties"; +import SheetProvider from "../../sheet-provider"; +import { NotebookItem } from "../../side-menu/notebook-item"; +import { useSideMenuNotebookTreeStore } from "../../side-menu/stores"; +import { IconButton } from "../../ui/icon-button"; +import Paragraph from "../../ui/typography/paragraph"; +import { AddNotebookSheet } from "../add-notebook"; + +const { + useNotebookExpandedStore, + useNotebookSelectionStore, + useNotebookTreeStore +} = createNotebookTreeStores(false, false, "notebook-tree-sheet"); + +useNotebookSelectionStore.setState({ + multiSelect: true +}); +export const Notebooks = (props: { + rootNotebook: Notebook; + close?: (ctx?: string) => void; +}) => { + const tree = useNotebookTreeStore((state) => state.tree); + const [isLoading, setIsLoading] = useState(true); + const { colors } = useThemeColors(); + const [notebooks, setNotebooks] = useState(); + const [filteredNotebooks, setFilteredNotebooks] = + React.useState>(); + const searchTimer = React.useRef(); + const lastQuery = React.useRef(); + const loadRootNotebooks = React.useCallback(async () => { + const notebooks = await db.relations + .from( + { + type: "notebook", + id: props.rootNotebook.id + }, + "notebook" + ) + .selector.items(undefined, db.settings.getGroupOptions("notebooks")); + const items = await useNotebookTreeStore + .getState() + .addNotebooks("root", notebooks, 0); + setNotebooks(notebooks); + useNotebookTreeStore.getState().setTree(items); + }, [props.rootNotebook.id]); + + const updateNotebooks = React.useCallback(() => { + loadRootNotebooks(); + }, [loadRootNotebooks]); + + useEffect(() => { + updateNotebooks(); + }, [updateNotebooks]); + + useEffect(() => { + (async () => { + loadRootNotebooks(); + setIsLoading(false); + })(); + }, [loadRootNotebooks]); + + useEffect(() => { + const sub = eSubscribeEvent(eOnNotebookUpdated, (id) => { + if (id === props.rootNotebook.id) { + updateNotebooks(); + } + }); + return () => { + sub?.unsubscribe(); + }; + }, [updateNotebooks, props.rootNotebook.id]); + + useEffect(() => { + useNotebookSelectionStore.setState({ + selectAll: async () => { + const allNotebooks = await db.notebooks.all.items(); + const allSelected = allNotebooks.every((notebook) => { + return ( + useNotebookSelectionStore.getState().selection[notebook.id] === + "selected" + ); + }); + + if (allSelected) { + useNotebookSelectionStore.setState({ + selection: {} + }); + return; + } + + useNotebookSelectionStore.setState({ + selection: allNotebooks.reduce((acc: any, item) => { + acc[item.id] = "selected"; + return acc; + }, {}) + }); + } + }); + }, []); + + const renderItem = React.useCallback( + (info: { item: TreeItem; index: number }) => { + return ; + }, + [] + ); + + return ( + + + + + + Sub notebooks + + + { + AddNotebookSheet.present(props.rootNotebook, undefined, "local"); + }} + /> + + {!notebooks || notebooks.length === 0 ? ( + + {strings.emptyPlaceholders("notebook")} + + ) : ( + <> + item.notebook.id} + windowSize={3} + ListHeaderComponent={ + + } + renderItem={renderItem} + /> + + )} + + ); +}; + +Notebooks.present = (notebook: Notebook) => { + if (!notebook) return; + presentSheet({ + component: (ref, close) => ( + + ), + keyboardHandlerDisabled: true + }); +}; + +const NotebookItemWrapper = React.memo( + ({ item, index }: { item: TreeItem; index: number }) => { + const expanded = useNotebookExpandedStore( + (state) => state.expanded[item.notebook.id] + ); + + const selectionEnabled = useNotebookSelectionStore( + (state) => state.enabled + ); + const selected = useNotebookSelectionStore( + (state) => state.selection[item.notebook.id] === "selected" + ); + const focused = useNavigationStore( + (state) => state.focusedRouteId === item.notebook.id + ); + + const onItemUpdate = React.useCallback(async () => { + const notebook = await db.notebooks.notebook(item.notebook.id); + if (notebook) { + useSideMenuNotebookTreeStore + .getState() + .updateItem(item.notebook.id, notebook); + if (expanded) { + useSideMenuNotebookTreeStore + .getState() + .setTree( + await useSideMenuNotebookTreeStore + .getState() + .fetchAndAdd(item.notebook.id, item.depth + 1) + ); + } + } else { + useSideMenuNotebookTreeStore.getState().removeItem(item.notebook.id); + } + }, [expanded, item.depth, item.notebook.id]); + + return ( + + { + useNotebookExpandedStore.getState().setExpanded(item.notebook.id); + if (!expanded) { + useSideMenuNotebookTreeStore + .getState() + .setTree( + await useSideMenuNotebookTreeStore + .getState() + .fetchAndAdd(item.notebook.id, item.depth + 1) + ); + } else { + useSideMenuNotebookTreeStore + .getState() + .removeChildren(item.notebook.id); + } + }} + selected={selected} + selectionEnabled={selectionEnabled} + selectionStore={useNotebookSelectionStore} + onItemUpdate={onItemUpdate} + focused={focused} + onPress={() => { + eSendEvent(eCloseSheet); + NotebookScreen.navigate(item.notebook, false); + }} + onLongPress={async () => { + eSendEvent(eCloseSheet); + await sleep(300); + Properties.present(item.notebook, false); + }} + /> + + ); + }, + (prev, next) => { + return ( + prev.item.notebook.id === next.item.notebook.id && + prev.item.notebook.dateModified === next.item.notebook.dateModified && + prev.item.notebook.dateEdited === next.item.notebook.dateEdited && + prev.item.hasChildren === next.item.hasChildren && + prev.index === next.index && + prev.item.parentId === next.item.parentId + ); + } +); +NotebookItemWrapper.displayName = "NotebookItemWrapper"; diff --git a/apps/mobile/app/screens/notebook/index.tsx b/apps/mobile/app/screens/notebook/index.tsx index 578d5db02..f3a276034 100644 --- a/apps/mobile/app/screens/notebook/index.tsx +++ b/apps/mobile/app/screens/notebook/index.tsx @@ -37,6 +37,9 @@ import useNavigationStore, { import { eUpdateNotebookRoute } from "../../utils/events"; import { findRootNotebookId } from "../../utils/notebooks"; import { openEditor, setOnFirstSave } from "../notes/common"; +import { View } from "react-native"; +import { DefaultAppStyles } from "../../utils/styles"; +import { Notebooks } from "../../components/sheets/notebooks"; const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => { const [notes, setNotes] = useState>(); @@ -193,10 +196,6 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => { loading={loading} CustomLisHeader={ { - // AddNotebookSheet.present(params.current.item); - // }} breadcrumbs={breadcrumbs} notebook={params.current.item} totalNotes={notes?.placeholders.length || 0} @@ -211,7 +210,36 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => { }} /> - + + { + Notebooks.present(params.current.item); + }} + style={{ + position: "relative", + right: 0, + bottom: 5 + }} + /> + + ); }; diff --git a/apps/mobile/native/fonts/MaterialCommunityIcons.ttf b/apps/mobile/native/fonts/MaterialCommunityIcons.ttf index 4981bb9a1ebbadfa08c8e36464d624836588bacc..d666b3c41e4ad2e55a412db5de227c1d78f72fb0 100644 GIT binary patch delta 2259 zcmYk6dr*{B7>9q~BA4A|_gmNt%W`SCJG;>+1{RsEN@QkgCFBJWlqHaB%bIBBu4Im> zl?_5($jq!1&D*M(P&wK-MowciG7K@3HbqXB<23zIj#&($xlKA5w4A)vhk%<8J~R#sIyJudjYpvZ57x!IzwQS=)9Qlz}=O%UyDo#EMzoG zV9V%d1fY^$D*%h=4Fa&3?j&28p^EM$ybh?LzZQTc^f#oR>vi-9xx#q^&0`Rt8ET}F z&ENn$&w$M!fRAPo3D82bRs>*yBdY~qDII>!$8{Dva+Lrqqc;mcE4@_!*o4S;1YiX{ zAOJ5SphjM1aG8l#LXF~%2=Eg9r~oi|R3mBPIbleHy7X*D=09abx2?5we^92NW52`*w z0Cv;7gamk>X8#HB0nN4%;6wU=0PrBOyiWw!OV_af9Dr^Hyf6gV2h}hj0Q;dDc`*s_ z5&gLUFrj)y09X-|NdR~UOl%2}&6(H&0vv`KrwG6isPWtg0Yd!9nVSXRD9zFn;27O6 z0IZ0GJtx4&P;K!7!0IIM;t}8zsCIr#32+i>;$#8n37yrRF$bc!4CRzkfe1vyghZqu z8!)F?)2#Vv)~r06B`Q7p{(^+NlmD-FDDGPqNry2$KFMr0+}4x-ttcg~pv!g4>@f2; zcw5I@uKe3G9&=sFaPnl?K~Q#}hm~(1XU&KtR+1xQ{y6J6PcWz;$RBqw*zF1`!4glY zr(|E9`>GqE@W6j7P&nV%JqwiQjU0sK(uB0^B z-8~QRI)7 z(^ONLX@%*OX~;BUx)FD8+}gOlxEt|#@w?11W~aH-+-TlnK4~5{|6#FNCR%)!BbHm% zG1gA&CF@OFyltAzZwu^B&?J;4^xI?YMfQ#ML-v8h#>5kegAR+M%CXIH&M}yzOY$W7 zlDd+HlKxCiPri^6lTwf}CuK*`sTX*g4M`be>ByrWK{FNxPM9 zN-s=blzt$?o>82!F5`TrGjmnu7g?ID;;h!J>)ErjFJzD8IO21r=4{Cs80{P#{&H4G zZ>X*8Crv~zCrcLklqlt-(xdc-hAUQUtd{B~Uqhuox4yQ;pSx&jeZAN3tqLtzJgCuW it844MxqhG58_KUXM8w?>uVdm?UQQfEBF_;w69SB delta 1880 zcmW-gdr*{R6o;Q(MA>E8Z(+H!;BpcEbJB6h&>=!Iv+`1=MtDIH$yo)F5KUXP#>}iN ztQ5)2%+$!#B~vn*(SIDrnzE5GbxN5o&Qy+Lml+*%`tAAS`JQvWefPbb^M3pKU0vV1 zwuGZU+{)MhE*~WIE{d#d=pIsZ1U#o#WTVD3clPCwke-{ony8DVE^jyu^Mji>kGxT6M)md_Y)pM24P3i*}+W8^#HKmB(s zPJ+vTN_nsW_sc#5s^okF9+0)3fNGp*f)+`rP%=)APf)4&K{@`XD#a6I4NMrP_#ydW z1LB@;z$Cd=%+{DkV7liS1!y`LH)82$yCTlwa=E)lj zsFPnYV7~me*sd`PIfkpB+20SBwC(h~pV)<8bO>solF$6T>B>L41J)l|1cmtNn zDx828`Edi5%MAvo*opDqS~TWa`FR6W_{6OStdO@G&@R7cfEtq6Z@?;|PU2N1S2fXd zID@q#0juSuqDk=@`3(d0&Kdl^0c+($2CS2HVgc)AEg;|poFU~Re)V3I)c^si?2v_` zL6?C{1%Ddw5>8UO0UPBU174Pg8?Z?pVSw6_G{b<+@)riYisR9@AmBCm9x+1k>+)nV zMX?U$iSE#2hmsEYT?14?&pxqV@86O?HQ*h&&wy>RPAouW_MA6Bcii)n0r9ziGoTB{ zJK6xPLFOr~&Gc_m~0tKD=5$fJ&aMdoEzNTyMY!vidAwkF0wj;6wSK0ef+L zx4j}BpcV*F z)lyvs=$@o$K>>$xg3SgTnW#V|77)WpS3d>x;AEy5@EJ~)E|maPDqEkRfX{Jqstq_6 z`_XeM7%MEe6pD=>6A8t#XGPq}!DR>jn(K`Hhh%rhG*>XTyXLT~V`gnr)N{vMocP#vqD*KW}VF*l>KOSOO7{ZdQNN3p)f%k^%vZ+gNsrUI=)_9^FN~!TigHu diff --git a/apps/mobile/native/rspack.config.js b/apps/mobile/native/rspack.config.js index 32d3e962b..735ede8b2 100644 --- a/apps/mobile/native/rspack.config.js +++ b/apps/mobile/native/rspack.config.js @@ -52,7 +52,7 @@ module.exports = (env) => { parallelCodeSplitting: true, cache: { type: "persistent", - buildDependencies: [__filename] + buildDependencies: [__filename, path.join(__dirname, "..", "package-lock.json"), path.join(__dirname, "..", "scripts","optimize-fonts.mjs")], }, }, /** diff --git a/apps/mobile/scripts/optimize-fonts.mjs b/apps/mobile/scripts/optimize-fonts.mjs index 177396d59..8d02cf339 100644 --- a/apps/mobile/scripts/optimize-fonts.mjs +++ b/apps/mobile/scripts/optimize-fonts.mjs @@ -96,7 +96,10 @@ const EXTRA_ICON_NAMES = [ "dots-vertical", "briefcase-outline", "shield-outline", - "brain" + "brain", + "file-tree-outline", + "format-list-bulleted", + "file-tree" ]; const __filename = fileURLToPath(import.meta.url);