mobile: add support for changin sidebar + default home page

This commit is contained in:
Ammar Ahmed
2025-04-08 12:40:16 +05:00
committed by Abdullah Atta
parent b6d0d124fd
commit 552e8f50b7
16 changed files with 303 additions and 40 deletions

View File

@@ -38,7 +38,6 @@ import { fluidTabsRef } from "../../utils/global-refs";
import { Header } from "../list-items/headers/header"; import { Header } from "../list-items/headers/header";
import { Empty, PlaceholderData } from "./empty"; import { Empty, PlaceholderData } from "./empty";
import { ListItemWrapper } from "./list-item.wrapper"; import { ListItemWrapper } from "./list-item.wrapper";
import { useSelectionStore } from "../../stores/use-selection-store";
type ListProps = { type ListProps = {
data: VirtualizedGrouping<Item> | undefined; data: VirtualizedGrouping<Item> | undefined;

View File

@@ -66,6 +66,7 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
"move-notebook", "move-notebook",
"pin", "pin",
"default-notebook", "default-notebook",
"default-homepage",
"add-shortcut", "add-shortcut",
"reorder", "reorder",
"rename-color", "rename-color",

View File

@@ -0,0 +1,98 @@
/*
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 { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { eCloseSheet } from "../../../utils/events";
import { SideMenuItem } from "../../../utils/menu-items";
import { AppFontSize } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { useSideBarDraggingStore } from "../../side-menu/dragging-store";
import AppIcon from "../../ui/AppIcon";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
const { colors } = useThemeColors();
return (
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
{[
{
title: strings.setAsHomepage(),
onPress: () => {
SettingsService.setProperty("homepageV2", {
id: item.id,
type: "default"
});
eSendEvent(eCloseSheet);
},
icon: "home-outline"
},
{
title: strings.reorder(),
onPress: () => {
useSideBarDraggingStore.setState({
dragging: true
});
eSendEvent(eCloseSheet);
},
icon: "sort-ascending"
}
].map((item) => (
<Pressable
key={item.title}
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center",
flexDirection: "row",
justifyContent: "flex-start",
gap: DefaultAppStyles.GAP_SMALL,
borderRadius: 0,
paddingHorizontal: DefaultAppStyles.GAP
}}
onPress={() => {
item.onPress();
}}
>
<AppIcon
color={colors.secondary.icon}
name={item.icon}
size={AppFontSize.xl}
/>
<Paragraph>{item.title}</Paragraph>
</Pressable>
))}
</View>
);
};
MenuItemProperties.present = (item: SideMenuItem) => {
presentSheet({
component: <MenuItemProperties item={item} />
});
};

View File

@@ -55,6 +55,7 @@ import {
} from "./stores"; } from "./stores";
import { useSideBarDraggingStore } from "./dragging-store"; import { useSideBarDraggingStore } from "./dragging-store";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import SettingsService from "../../services/settings";
const renderScene = SceneMap({ const renderScene = SceneMap({
home: SideMenuHome, home: SideMenuHome,
notebooks: SideMenuNotebooks, notebooks: SideMenuNotebooks,
@@ -65,7 +66,9 @@ const renderScene = SceneMap({
export const SideMenu = React.memo( export const SideMenu = React.memo(
function SideMenu() { function SideMenu() {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const [index, setIndex] = React.useState(0); const [index, setIndex] = React.useState(
SettingsService.getProperty("defaultSidebarTab")
);
const [routes] = React.useState<Route[]>([ const [routes] = React.useState<Route[]>([
{ {
key: "home", key: "home",
@@ -113,7 +116,7 @@ const TabBar = (
const dragging = useSideBarDraggingStore((state) => state.dragging); const dragging = useSideBarDraggingStore((state) => state.dragging);
const { colors, isDark } = useThemeColors(); const { colors, isDark } = useThemeColors();
const groupOptions = useGroupOptions( const groupOptions = useGroupOptions(
props.navigationState.index === 1 ? "notebook" : "tags" props.navigationState.index === 1 ? "notebooks" : "tags"
); );
const notebookSelectionEnabled = useSideMenuNotebookSelectionStore( const notebookSelectionEnabled = useSideMenuNotebookSelectionStore(
(state) => state.enabled (state) => state.enabled
@@ -371,7 +374,7 @@ const TabBar = (
<IconButton <IconButton
name={ name={
groupOptions.sortDirection === "asc" groupOptions?.sortDirection === "asc"
? "sort-ascending" ? "sort-ascending"
: "sort-descending" : "sort-descending"
} }

View File

@@ -36,6 +36,7 @@ import { eSendEvent } from "../../services/event-manager";
import { eOpenPremiumDialog } from "../../utils/events"; import { eOpenPremiumDialog } from "../../utils/events";
import { useUserStore } from "../../stores/use-user-store"; import { useUserStore } from "../../stores/use-user-store";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { MenuItemProperties } from "../sheets/menu-item-properties";
const pro = { const pro = {
title: strings.getNotesnookPro(), title: strings.getNotesnookPro(),
@@ -96,6 +97,7 @@ export function SideMenuHome() {
style={{ style={{
width: "100%" width: "100%"
}} }}
disableDefaultDrag
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderDraggableItem={({ item, index }) => { renderDraggableItem={({ item, index }) => {
return ( return (
@@ -106,7 +108,10 @@ export function SideMenuHome() {
title: title:
strings.routes[ strings.routes[
item.title as keyof typeof strings.routes item.title as keyof typeof strings.routes
]?.() || item.title ]?.() || item.title,
onLongPress: () => {
MenuItemProperties.present(item);
}
}} }}
testID={item.title} testID={item.title}
index={index} index={index}

View File

@@ -64,6 +64,8 @@ import { eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions"; import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text"; import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
import SettingsService from "../services/settings";
import { useSettingStore } from "../stores/use-setting-store";
export type ActionId = export type ActionId =
| "select" | "select"
@@ -105,7 +107,8 @@ export type ActionId =
| "pin-to-notifications" | "pin-to-notifications"
| "favorite" | "favorite"
| "remove-from-notebook" | "remove-from-notebook"
| "trash"; | "trash"
| "default-homepage";
export type Action = { export type Action = {
id: ActionId; id: ActionId;
@@ -155,6 +158,11 @@ export const useActions = ({
); );
const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false); const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false);
const [locked, setLocked] = useState(false); const [locked, setLocked] = useState(false);
const isHomepage = useSettingStore(
(state) =>
state.settings.homepageV2?.type === item.type &&
state.settings.homepageV2?.id === item.id
);
const isPublished = const isPublished =
item.type === "note" && db.monographs.isPublished(item.id); item.type === "note" && db.monographs.isPublished(item.id);
@@ -549,6 +557,31 @@ export const useActions = ({
}); });
} }
if (
item.type === "notebook" ||
item.type === "tag" ||
item.type === "color"
) {
actions.push({
id: "default-homepage",
title: isHomepage ? strings.unsetAsHomepage() : strings.setAsHomepage(),
icon: "home-outline",
isToggle: true,
checked: isHomepage,
onPress: () => {
SettingsService.setProperty(
"homepageV2",
isHomepage
? undefined
: {
id: item.id,
type: item.type
}
);
}
});
}
if (item.type === "note") { if (item.type === "note") {
async function openHistory() { async function openHistory() {
presentSheet({ presentSheet({

View File

@@ -21,12 +21,13 @@ import { db } from "../common/database";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager"; import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import Navigation from "../services/navigation"; import Navigation from "../services/navigation";
import { eGroupOptionsUpdated } from "../utils/events"; import { eGroupOptionsUpdated } from "../utils/events";
import { useSettingStore } from "../stores/use-setting-store";
export function useGroupOptions(type: any) { export function useGroupOptions(type: any) {
const appLoading = useSettingStore((state) => state.isAppLoading);
const [groupOptions, setGroupOptions] = useState( const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions(type) db.settings?.getGroupOptions(type)
); );
useEffect(() => { useEffect(() => {
const onUpdate = (groupType: string) => { const onUpdate = (groupType: string) => {
if (groupType !== type) return; if (groupType !== type) return;
@@ -43,10 +44,15 @@ export function useGroupOptions(type: any) {
}; };
eSubscribeEvent(eGroupOptionsUpdated, onUpdate); eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
if (!appLoading) {
onUpdate(type);
}
return () => { return () => {
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate); eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
}; };
}, [type, groupOptions]); }, [type, groupOptions, appLoading]);
return groupOptions; return groupOptions;
} }

View File

@@ -29,6 +29,7 @@ import useNavigationStore, {
import { useSelectionStore } from "../stores/use-selection-store"; import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store"; import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs"; import { rootNavigatorRef } from "../utils/global-refs";
import { db } from "../common/database";
const RootStack = createNativeStackNavigator(); const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator(); const AppStack = createNativeStackNavigator();
@@ -46,17 +47,97 @@ let ColoredNotes: any = null;
const AppNavigation = React.memo( const AppNavigation = React.memo(
() => { () => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const homepage = SettingsService.get().homepage; const [home, setHome] = React.useState<{
React.useEffect(() => { name: string;
setTimeout(() => { params: any;
useNavigationStore.getState().update(homepage as keyof RouteParams); }>();
useNavigationStore.getState().setFocusedRouteId(homepage); const homepageV2 = SettingsService.get().homepageV2;
}, 300); const loading = useSettingStore((state) => state.isAppLoading);
}, [homepage]);
return ( React.useEffect(() => {
(async () => {
if (loading) return;
if (!homepageV2) {
setHome({
name: "Notes",
params: undefined
});
return;
}
switch (homepageV2.type) {
case "notebook": {
const notebook = await db.notebooks.notebook(homepageV2.id);
if (notebook) {
setHome({
name: "Notebook",
params: {
item: notebook,
id: notebook.id,
title: notebook.title
}
});
return;
}
}
case "color": {
const color = await db.colors.color(homepageV2.id);
if (color) {
setHome({
name: "ColoredNotes",
params: {
item: color,
id: color.id,
title: color.title
}
});
return;
}
break;
}
case "tag": {
const tag = await db.tags.tag(homepageV2.id);
if (tag) {
setHome({
name: "TaggedNotes",
params: {
item: tag,
id: tag.id,
title: tag.title
}
});
return;
}
break;
}
case "default":
{
setHome({
name: homepageV2.id,
params: undefined
});
}
return;
}
setHome({
name: "Notes",
params: undefined
});
})();
}, [homepageV2, loading]);
React.useEffect(() => {
useNavigationStore.getState().update(home?.name as keyof RouteParams);
useNavigationStore.getState().setFocusedRouteId(home?.params?.id || home);
}, [home]);
return !home ? null : (
<AppStack.Navigator <AppStack.Navigator
initialRouteName={homepage} initialRouteName={home.name}
screenOptions={{ screenOptions={{
headerShown: false, headerShown: false,
animation: "none", animation: "none",
@@ -96,6 +177,7 @@ const AppNavigation = React.memo(
TaggedNotes || require("../screens/notes/tagged").default; TaggedNotes || require("../screens/notes/tagged").default;
return TaggedNotes; return TaggedNotes;
}} }}
initialParams={home.name === "TaggedNotes" ? home.params : undefined}
/> />
<AppStack.Screen <AppStack.Screen
@@ -105,6 +187,7 @@ const AppNavigation = React.memo(
ColoredNotes || require("../screens/notes/colored").default; ColoredNotes || require("../screens/notes/colored").default;
return ColoredNotes; return ColoredNotes;
}} }}
initialParams={home.name === "ColoredNotes" ? home.params : undefined}
/> />
<AppStack.Screen <AppStack.Screen
@@ -130,6 +213,7 @@ const AppNavigation = React.memo(
Notebook = Notebook || require("../screens/notebook").default; Notebook = Notebook || require("../screens/notebook").default;
return Notebook; return Notebook;
}} }}
initialParams={home.name === "Notebook" ? home.params : undefined}
/> />
<AppStack.Screen <AppStack.Screen

View File

@@ -31,6 +31,7 @@ import {
DateFormatPicker, DateFormatPicker,
FontPicker, FontPicker,
HomePicker, HomePicker,
SidebarTabPicker,
TimeFormatPicker, TimeFormatPicker,
TrashIntervalPicker TrashIntervalPicker
} from "./picker/pickers"; } from "./picker/pickers";
@@ -67,5 +68,6 @@ export const components: { [name: string]: ReactElement } = {
<View style={{ paddingHorizontal: DefaultAppStyles.GAP }}> <View style={{ paddingHorizontal: DefaultAppStyles.GAP }}>
<AttachmentGroupProgress groupId="offline-mode" /> <AttachmentGroupProgress groupId="offline-mode" />
</View> </View>
) ),
"sidebar-tab-selector": <SidebarTabPicker />
}; };

View File

@@ -64,6 +64,25 @@ export const HomePicker = createSettingsPicker({
premium: true premium: true
}); });
export const SidebarTabPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultSidebarTab,
updateValue: (item) => {
SettingsService.set({ defaultSidebarTab: item });
},
formatValue: (item) => {
const SidebarTabs = [
strings.routes.Home(),
strings.routes.Notebooks(),
strings.routes.Tags()
];
return SidebarTabs[item];
},
getItemKey: (item) => item,
options: [0, 1, 2],
compareValue: (current, item) => current === item,
premium: true
});
export const TrashIntervalPicker = createSettingsPicker({ export const TrashIntervalPicker = createSettingsPicker({
getValue: () => db.settings.getTrashCleanupInterval(), getValue: () => db.settings.getTrashCleanupInterval(),
updateValue: (item) => { updateValue: (item) => {

View File

@@ -30,16 +30,12 @@ import * as RNIap from "react-native-iap";
//@ts-ignore //@ts-ignore
import { enabled } from "react-native-privacy-snapshot"; import { enabled } from "react-native-privacy-snapshot";
import ScreenGuardModule from "react-native-screenguard"; import ScreenGuardModule from "react-native-screenguard";
import { DatabaseLogger, db } from "../../common/database"; import { db } from "../../common/database";
import filesystem from "../../common/filesystem"; import filesystem from "../../common/filesystem";
import { ChangePassword } from "../../components/auth/change-password"; import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions"; import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password"; import { AppLockPassword } from "../../components/dialogs/applock-password";
import { import { endProgress, startProgress } from "../../components/dialogs/progress";
endProgress,
startProgress,
updateProgress
} from "../../components/dialogs/progress";
import { ChangeEmail } from "../../components/sheets/change-email"; import { ChangeEmail } from "../../components/sheets/change-email";
import ExportNotesSheet from "../../components/sheets/export-notes"; import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue"; import { Issue } from "../../components/sheets/github/issue";
@@ -64,19 +60,15 @@ import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store"; import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store"; import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants"; import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
eCloseSheet,
eCloseSimpleDialog,
eOpenRecoveryKeyDialog
} from "../../utils/events";
import { NotesnookModule } from "../../utils/notesnook-module"; import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time"; import { sleep } from "../../utils/time";
import { MFARecoveryCodes, MFASheet } from "./2fa"; import { MFARecoveryCodes, MFASheet } from "./2fa";
import { useDragState } from "./editor/state"; import { useDragState } from "./editor/state";
import { verifyUser, verifyUserWithApplock } from "./functions"; import { verifyUser, verifyUserWithApplock } from "./functions";
import { logoutUser } from "./logout";
import { SettingSection } from "./types"; import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section"; import { getTimeLeft } from "./user-section";
import { logoutUser } from "./logout";
export const settingsGroups: SettingSection[] = [ export const settingsGroups: SettingSection[] = [
{ {
@@ -639,11 +631,11 @@ export const settingsGroups: SettingSection[] = [
description: strings.behaviorDesc(), description: strings.behaviorDesc(),
sections: [ sections: [
{ {
id: "default-home", id: "default-sidebar-view",
type: "component", type: "component",
name: strings.homepage(), name: strings.defaultSidebarTab(),
description: strings.homepageDesc(), description: strings.defaultSidebarTabDesc(),
component: "homeselector" component: "sidebar-tab-selector"
}, },
{ {
id: "date-format", id: "date-format",

View File

@@ -43,6 +43,10 @@ export type Settings = {
fullBackupReminder: "never" | "weekly" | "monthly"; fullBackupReminder: "never" | "weekly" | "monthly";
encryptedBackup?: boolean; encryptedBackup?: boolean;
homepage?: string; homepage?: string;
homepageV2?: {
id: string;
type: "notebook" | "tag" | "color" | "default";
};
sort?: string; sort?: string;
sortOrder?: string; sortOrder?: string;
screenshotMode?: boolean; screenshotMode?: boolean;
@@ -93,6 +97,7 @@ export type Settings = {
offlineMode?: boolean; offlineMode?: boolean;
lastFullBackupDate?: number; lastFullBackupDate?: number;
serverUrls?: Record<HostId, string>; serverUrls?: Record<HostId, string>;
defaultSidebarTab: number;
}; };
type DimensionsType = { type DimensionsType = {
@@ -149,6 +154,7 @@ export const defaultSettings: SettingStore["settings"] = {
forcePortraitOnTablet: false, forcePortraitOnTablet: false,
useSystemTheme: true, useSystemTheme: true,
reminder: "off", reminder: "off",
defaultSidebarTab: 0,
encryptedBackup: false, encryptedBackup: false,
homepage: "Notes", homepage: "Notes",
sort: "default", sort: "default",

View File

@@ -29,6 +29,7 @@ export type SideMenuItem = {
icon: string; icon: string;
onPress?: (item: SideMenuItem) => void; onPress?: (item: SideMenuItem) => void;
onLongPress?: (item: SideMenuItem) => void; onLongPress?: (item: SideMenuItem) => void;
type: string;
}; };
export const MenuItemsList: SideMenuItem[] = [ export const MenuItemsList: SideMenuItem[] = [
@@ -36,7 +37,8 @@ export const MenuItemsList: SideMenuItem[] = [
dataType: "note", dataType: "note",
id: "Notes", id: "Notes",
title: "Notes", title: "Notes",
icon: "note-outline" icon: "note-outline",
type: "side-menu-item"
}, },
// { // {
// dataType: "notebook", // dataType: "notebook",
@@ -48,7 +50,8 @@ export const MenuItemsList: SideMenuItem[] = [
dataType: "note", dataType: "note",
id: "Favorites", id: "Favorites",
title: "Favorites", title: "Favorites",
icon: "star-outline" icon: "star-outline",
type: "side-menu-item"
}, },
// { // {
// dataType: "tag", // dataType: "tag",
@@ -60,7 +63,8 @@ export const MenuItemsList: SideMenuItem[] = [
dataType: "reminder", dataType: "reminder",
id: "Reminders", id: "Reminders",
title: "Reminders", title: "Reminders",
icon: "bell" icon: "bell",
type: "side-menu-item"
}, },
{ {
dataType: "monograph", dataType: "monograph",
@@ -70,7 +74,8 @@ export const MenuItemsList: SideMenuItem[] = [
onPress: () => { onPress: () => {
Navigation.closeDrawer(); Navigation.closeDrawer();
Monographs.navigate(); Monographs.navigate();
} },
type: "side-menu-item"
}, },
// { // {
// dataType: "note", // dataType: "note",
@@ -82,6 +87,7 @@ export const MenuItemsList: SideMenuItem[] = [
dataType: "note", dataType: "note",
id: "Trash", id: "Trash",
title: "Trash", title: "Trash",
icon: "delete-outline" icon: "delete-outline",
type: "side-menu-item"
} }
]; ];

View File

@@ -4999,6 +4999,10 @@ msgstr "Reset"
msgid "Reset account password" msgid "Reset account password"
msgstr "Reset account password" msgstr "Reset account password"
#: src/strings.ts:2467
msgid "Reset homepage"
msgstr "Reset homepage"
#: src/strings.ts:1806 #: src/strings.ts:1806
msgid "Reset selection" msgid "Reset selection"
msgstr "Reset selection" msgstr "Reset selection"

View File

@@ -4973,6 +4973,10 @@ msgstr ""
msgid "Reset account password" msgid "Reset account password"
msgstr "" msgstr ""
#: src/strings.ts:2467
msgid "Reset homepage"
msgstr ""
#: src/strings.ts:1806 #: src/strings.ts:1806
msgid "Reset selection" msgid "Reset selection"
msgstr "" msgstr ""

View File

@@ -2463,5 +2463,6 @@ Use this if changes from other devices are not appearing on this device. This wi
addNotes: () => t`Add notes`, addNotes: () => t`Add notes`,
setAsHomepage: () => t`Set as homepage`, setAsHomepage: () => t`Set as homepage`,
defaultSidebarTab: () => t`Default sidebar tab`, defaultSidebarTab: () => t`Default sidebar tab`,
defaultSidebarTabDesc: () => t`Select the default sidebar tab` defaultSidebarTabDesc: () => t`Select the default sidebar tab`,
unsetAsHomepage: () => t`Reset homepage`
}; };