mobile: small ui improvements and perf fixes

This commit is contained in:
Ammar Ahmed
2025-02-18 15:42:52 +05:00
committed by Abdullah Atta
parent 99cac4b041
commit a082c7d33a
14 changed files with 224 additions and 151 deletions

View File

@@ -105,7 +105,7 @@ const NoteItem = ({
{compactMode ? null : (
<Paragraph
style={{
fontSize: AppFontSize.xxs,
fontSize: AppFontSize.xxxs,
color: colors.secondary.paragraph
}}
>

View File

@@ -99,15 +99,12 @@ export const Card = ({
flexWrap: "nowrap",
flexShrink: 1
}}
size={AppFontSize.xs}
size={AppFontSize.sm}
color={colors.primary.heading}
>
{messageBoardState.actionText}
</Paragraph>
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xxs}
>
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
{messageBoardState.message}
</Paragraph>
</View>

View File

@@ -19,18 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
import { ActivityIndicator, View } from "react-native";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { TTip, useTip } from "../../services/tip-manager";
import { RouteParams } from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { Tip } from "../tip";
import { Button } from "../ui/button";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { RouteParams } from "../../stores/use-navigation-store";
export type PlaceholderData = {
title: string;
@@ -59,8 +58,6 @@ export const Empty = React.memo(function Empty({
screen
}: EmptyListProps) {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const { height } = useWindowDimensions();
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
@@ -75,7 +72,7 @@ export const Empty = React.memo(function Empty({
<View
style={[
{
height: height - (140 + insets.top),
flex: 1,
width: "80%",
justifyContent: "center",
alignSelf: "center"
@@ -99,14 +96,11 @@ export const Empty = React.memo(function Empty({
{placeholder?.button && (
<Button
testID={notesnook.buttons.add}
type="accent"
type="secondaryAccented"
title={placeholder?.button}
iconPosition="right"
icon="arrow-right"
onPress={placeholder?.action}
buttonType={{
text: "white"
}}
style={{
alignSelf: "flex-start",
borderRadius: defaultBorderRadius,

View File

@@ -151,60 +151,69 @@ export default function List(props: ListProps) {
}}
entering={props.renderedInRoute === "Search" ? undefined : FadeIn}
>
<ListView
style={styles}
ref={scrollRef}
testID={notesnook.list.id}
data={props.data?.placeholders || []}
renderItem={renderItem}
onScroll={onListScroll}
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fluidTabsRef.current?.unlock();
}}
getItemType={(item: number, index: number) => {
return props.data?.type(index);
}}
estimatedItemSize={isCompactModeEnabled ? 60 : 120}
directionalLockEnabled={true}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
refreshControl={
props.isRenderedInActionSheet ? null : (
<RefreshControl
tintColor={colors.primary.accent}
colors={[colors.primary.accent]}
progressBackgroundColor={colors.secondary.background}
onRefresh={_onRefresh}
refreshing={false}
/>
)
}
ListEmptyComponent={
props.placeholder ? (
<Empty
loading={props.loading}
title={props.headerTitle}
dataType={props.dataType}
{props.data?.placeholders?.length === 0 ? (
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
placeholder={props.placeholder}
screen={props.renderedInRoute}
/>
) : null
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
screen={props.renderedInRoute}
)}
<Empty
loading={props.loading}
title={props.headerTitle}
dataType={props.dataType}
color={props.customAccentColor}
placeholder={props.placeholder}
screen={props.renderedInRoute}
/>
</>
) : (
<ListView
style={styles}
ref={scrollRef}
testID={notesnook.list.id}
data={props.data?.placeholders || []}
renderItem={renderItem}
onScroll={onListScroll}
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fluidTabsRef.current?.unlock();
}}
getItemType={(item: number, index: number) => {
return props.data?.type(index);
}}
estimatedItemSize={isCompactModeEnabled ? 60 : 120}
directionalLockEnabled={true}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
refreshControl={
props.isRenderedInActionSheet ? null : (
<RefreshControl
tintColor={colors.primary.accent}
colors={[colors.primary.accent]}
progressBackgroundColor={colors.secondary.background}
onRefresh={_onRefresh}
refreshing={false}
/>
)}
</>
}
/>
)
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
screen={props.renderedInRoute}
/>
)}
</>
}
/>
)}
</Animated.View>
</>
);

View File

@@ -21,8 +21,8 @@ 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 { db } from "../../common/database";
import { useTotalNotes } from "../../hooks/use-db-item";
import Navigation from "../../services/navigation";
import { useFavoriteStore } from "../../stores/use-favorite-store";
import useNavigationStore, {
@@ -35,6 +35,7 @@ import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Pressable } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
import { useMonographStore } from "../../stores/use-monograph-store";
function _MenuItem({
item,
@@ -93,10 +94,12 @@ function _MenuItem({
);
break;
case "Monographs":
db.monographs.all.count().then((count) => {
setItemCount(count);
unsub = useMonographStore.subscribe((state) => {
setItemCount(state.items?.placeholders.length || 0);
});
// TODO make it reactive?
setItemCount(
useMonographStore.getState().items?.placeholders?.length || 0
);
break;
case "Trash":
unsub = useTrashStore.subscribe((state) => {
@@ -184,16 +187,14 @@ function _MenuItem({
</Paragraph>
</View>
{menuItemCount > 0 ? (
<Paragraph
size={AppFontSize.xxs}
color={
isFocused ? colors.primary.paragraph : colors.secondary.paragraph
}
>
{menuItemCount}
</Paragraph>
) : null}
<Paragraph
size={AppFontSize.xxs}
color={
isFocused ? colors.primary.paragraph : colors.secondary.paragraph
}
>
{menuItemCount}
</Paragraph>
</Pressable>
);
}

View File

@@ -202,7 +202,7 @@ const NotebookItem = ({
}}
>
<Pressable
type={isFocused ? "selected" : "transparent"}
type={isFocused || selected ? "selected" : "transparent"}
onLongPress={onLongPress}
testID={`notebook-item-${item.depth}-${index}`}
onPress={async () => {
@@ -246,12 +246,14 @@ const NotebookItem = ({
onPress={() => {
if (item.hasChildren) {
onToggleExpanded?.();
} else {
onPress?.();
}
}}
top={0}
left={20}
left={50}
bottom={0}
right={20}
right={40}
style={{
width: 32,
height: 32,
@@ -299,14 +301,12 @@ const NotebookItem = ({
</View>
) : (
<>
{totalNotes(notebook?.id) ? (
<Paragraph
size={AppFontSize.xxs}
color={colors.secondary.paragraph}
>
{totalNotes?.(notebook?.id)}
</Paragraph>
) : null}
<Paragraph
size={AppFontSize.xxs}
color={colors.secondary.paragraph}
>
{totalNotes?.(notebook?.id) || 0}
</Paragraph>
</>
)}
</Pressable>
@@ -327,7 +327,10 @@ export const SideMenuNotebooks = () => {
for (let i = 0; i < filteredNotebooks.placeholders.length; i++) {
_notebooks[i] = (await filteredNotebooks?.item(i))?.item as Notebook;
}
useSideMenuNotebookTreeStore.getState().addNotebooks("root", _notebooks, 0);
const items = await useSideMenuNotebookTreeStore
.getState()
.addNotebooks("root", _notebooks, 0);
useSideMenuNotebookTreeStore.getState().setTree(items);
}, [filteredNotebooks]);
const updateNotebooks = React.useCallback(() => {
@@ -391,6 +394,8 @@ export const SideMenuNotebooks = () => {
[]
);
console.log("RENDERING ROOT");
return (
<View
style={{
@@ -408,6 +413,8 @@ export const SideMenuNotebooks = () => {
bounces={false}
bouncesZoom={false}
overScrollMode="never"
keyExtractor={(item) => item.notebook.id}
windowSize={3}
ListHeaderComponent={
<View
style={{
@@ -457,6 +464,7 @@ const NotebookItemWrapper = React.memo(
const expanded = useSideMenuNotebookExpandedStore(
(state) => state.expanded[item.notebook.id]
);
const selectionEnabled = useSideMenuNotebookSelectionStore(
(state) => state.enabled
);
@@ -467,18 +475,6 @@ const NotebookItemWrapper = React.memo(
(state) => state.focusedRouteId === item.notebook.id
);
useEffect(() => {
if (expanded) {
useSideMenuNotebookTreeStore
.getState()
.fetchAndAdd(item.notebook.id, item.depth + 1);
} else {
useSideMenuNotebookTreeStore
.getState()
.removeChildren(item.notebook.id);
}
}, [expanded, item.depth, item.notebook]);
const onItemUpdate = React.useCallback(async () => {
const notebook = await db.notebooks.notebook(item.notebook.id);
if (notebook) {
@@ -490,6 +486,8 @@ const NotebookItemWrapper = React.memo(
}
}, [item.notebook.id]);
console.log("RENDERING", item?.notebook?.title, item.parentId);
return (
<View
style={{
@@ -501,10 +499,23 @@ const NotebookItemWrapper = React.memo(
item={item}
index={index}
expanded={expanded}
onToggleExpanded={() => {
onToggleExpanded={async () => {
useSideMenuNotebookExpandedStore
.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}

View File

@@ -66,7 +66,7 @@ const TagItem = (props: {
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
marginTop: (props.id as number) === 0 ? DefaultAppStyles.GAP : 0
marginTop: (props.id as number) === 0 ? DefaultAppStyles.GAP : 2
}}
>
{item ? (

View File

@@ -70,7 +70,7 @@ export const Tip = ({
<Button
title={strings.tip()}
icon="information"
fontSize={AppFontSize.xs}
fontSize={AppFontSize.xxxs}
iconSize={AppFontSize.xs}
style={{
width: undefined,
@@ -79,11 +79,13 @@ export const Tip = ({
alignSelf: "flex-start",
borderRadius: 100,
borderWidth: 1,
borderColor: color ? color : colors.primary.accent
borderColor: colors.secondary.border
}}
buttonType={{
text: color
textStyle={{
color: colors.secondary.paragraph
}}
iconColor={colors.secondary.icon}
type="plain"
/>
{neverShowAgain && (
@@ -114,7 +116,7 @@ export const Tip = ({
<Paragraph
style={textStyle}
color={colors.primary.paragraph}
size={AppFontSize.md}
size={AppFontSize.sm}
>
{tip.text()}
</Paragraph>

View File

@@ -38,6 +38,8 @@ import { SettingStore, useSettingStore } from "../../stores/use-setting-store";
import { AppFontSize } from "../../utils/size";
import { components } from "./components";
import { RouteParams, SettingSection } from "./types";
import Heading from "../../components/ui/typography/heading";
import { DefaultAppStyles } from "../../utils/styles";
const _SectionItem = ({ item }: { item: SettingSection }) => {
const { colors } = useThemeColors();
@@ -117,10 +119,10 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
style={{
width: "100%",
alignItems: "center",
padding: 12,
padding: DefaultAppStyles.GAP,
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 20,
paddingVertical: DefaultAppStyles.GAP,
opacity: isDisabled ? 0.5 : 1,
borderRadius: 0,
...styles
@@ -188,16 +190,17 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
paddingRight: item.type === "switch" ? 10 : 0
}}
>
<Paragraph
<Heading
color={
item.type === "danger"
? colors.error.paragraph
: colors.primary.heading
}
size={AppFontSize.md + 1}
size={AppFontSize.sm}
>
{typeof item.name === "function" ? item.name(current) : item.name}
</Paragraph>
</Heading>
{!!item.description && (
<Paragraph
color={

View File

@@ -53,10 +53,15 @@ export function createNotebookTreeStores(
addNotebooks: (
parentId: string,
notebooks: Notebook[],
depth: number
) => void;
depth: number,
tree?: TreeItem[]
) => Promise<TreeItem[]>;
updateItem: (id: string, notebook: Notebook) => void;
fetchAndAdd: (parentId: string, depth: number) => Promise<void>;
fetchAndAdd: (
parentId: string,
depth: number,
tree?: TreeItem[]
) => Promise<TreeItem[]>;
removeChildren: (id: string) => void;
}>((set, get) => ({
tree: [],
@@ -77,12 +82,9 @@ export function createNotebookTreeStores(
addNotebooks: async (
parentId: string,
notebooks: Notebook[],
depth: number
depth: number,
tree?: TreeItem[]
) => {
const parentIndex = get().tree.findIndex(
(item) => item.notebook.id === parentId
);
const items = await db.relations
.from(
{
@@ -93,29 +95,46 @@ export function createNotebookTreeStores(
)
.get();
let newTree = get().tree.slice();
let newTree = tree || get().tree.slice();
const parentIndex = newTree.findIndex(
(item) => item.notebook.id === parentId
);
for (const item of newTree) {
if (item.parentId === parentId) {
newTree = removeTreeItem(newTree, item.notebook.id);
}
}
newTree.splice(
parentIndex + 1,
0,
...notebooks.map((notebook) => {
return {
parentId,
notebook,
depth: depth,
hasChildren: items.some((item) => {
return item.fromId === notebook.id;
})
};
})
);
set({
tree: newTree
const newTreeItems = notebooks.map((notebook) => {
return {
parentId,
notebook,
depth: depth,
hasChildren: items.some((item) => {
return item.fromId === notebook.id;
})
};
});
newTree.splice(parentIndex + 1, 0, ...newTreeItems);
for (const item of newTreeItems) {
const expanded =
useSideMenuNotebookExpandedStore.getState().expanded[
item.notebook.id
] && item.hasChildren;
if (expanded) {
newTree = await get().fetchAndAdd(
item.notebook.id,
depth + 1,
newTree
);
}
}
return newTree;
},
removeItem(id) {
@@ -123,7 +142,7 @@ export function createNotebookTreeStores(
tree: removeTreeItem(get().tree, id).slice()
});
},
fetchAndAdd: async (parentId: string, depth: number) => {
fetchAndAdd: async (parentId: string, depth: number, tree?: TreeItem[]) => {
const selector = db.relations.from(
{
type: "notebook",
@@ -135,13 +154,15 @@ export function createNotebookTreeStores(
const grouped = await selector.sorted(
db.settings.getGroupOptions("notebooks")
);
const notebooks: Notebook[] = [];
for (let index = 0; index < grouped.placeholders.length; index++) {
const notebook = (await grouped.item(index)).item;
if (notebook) notebooks.push(notebook);
}
get().addNotebooks(parentId, notebooks, depth);
tree = await get().addNotebooks(parentId, notebooks, depth, tree);
return tree;
},
removeChildren(id: string) {
let newTree = get().tree.slice();
@@ -169,7 +190,9 @@ export function createNotebookTreeStores(
}>(
persist(
(set, get) => ({
expanded: {},
expanded: {
root: true
},
setExpanded(id: string) {
set({
expanded: {
@@ -180,7 +203,7 @@ export function createNotebookTreeStores(
}
}),
{
name: "side-menu-notebook-expanded",
name: "side-menu-notebook-expanded-v1",
getStorage: () => MMKV as unknown as StateStorage
}
)

View File

@@ -23,6 +23,7 @@ import { NotePreviewWidget } from "../services/note-preview-widget";
import Notifications from "../services/notifications";
import { useFavoriteStore } from "./use-favorite-store";
import { useMenuStore } from "./use-menu-store";
import { useMonographStore } from "./use-monograph-store";
import { useNotebookStore } from "./use-notebook-store";
import { useNoteStore } from "./use-notes-store";
import { useRelationStore } from "./use-relation-store";
@@ -39,6 +40,7 @@ export function initAfterSync() {
useRelationStore.getState().update();
useMenuStore.getState().setColorNotes();
useMenuStore.getState().setMenuPins();
useMonographStore.getState().refresh();
useUserStore.setState({
profile: db.settings.getProfile()
});
@@ -57,4 +59,5 @@ export function clearAllStores() {
useMenuStore.getState().clearAll();
useTrashStore.getState().clear();
useReminderStore.getState().clear();
useMonographStore.getState().clear();
}

View File

@@ -0,0 +1,30 @@
/*
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 { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
const { useStore: useMonographStore, useCollection: useMonographs } =
createDBCollectionStore({
getCollection: () =>
db.monographs.all.grouped(db.settings.getGroupOptions("notes")),
eagerlyFetchFirstBatch: true
});
export { useMonographStore, useMonographs };

View File

@@ -84,10 +84,10 @@ export const normalize = (size) => {
function getSize() {
return {
xxxs: normalize(11) * scale.fontScale,
xxxs: normalize(11.5) * scale.fontScale,
xxs: normalize(12.5) * scale.fontScale,
xs: normalize(14) * scale.fontScale,
sm: normalize(15) * scale.fontScale,
xs: normalize(13.5) * scale.fontScale,
sm: normalize(14.5) * scale.fontScale,
md: normalize(16.5) * scale.fontScale,
lg: normalize(22) * scale.fontScale,
xl: normalize(24) * scale.fontScale,

View File

@@ -25,7 +25,7 @@ import {
import Config from "react-native-config";
i18n.load({
en: __DEV__ && Config.isTesting !== "true" ? $pseudo : $en
en: !__DEV__ && Config.isTesting !== "true" ? $pseudo : $en
});
setI18nGlobal(i18n);
i18n.activate("en");