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 4981bb9a1..d666b3c41 100644 Binary files a/apps/mobile/native/fonts/MaterialCommunityIcons.ttf and b/apps/mobile/native/fonts/MaterialCommunityIcons.ttf differ 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);