diff --git a/apps/mobile/src/lib/preferences.ts b/apps/mobile/src/lib/preferences.ts new file mode 100644 index 00000000..9b96ca72 --- /dev/null +++ b/apps/mobile/src/lib/preferences.ts @@ -0,0 +1,12 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; + +const MEMO_LIST_DENSITY_KEY = "edgeever.mobile.memoListDensity"; + +export type MobileMemoListDensity = "preview" | "compact"; + +export const readMobileMemoListDensity = async (): Promise => { + const value = await AsyncStorage.getItem(MEMO_LIST_DENSITY_KEY); + return value === "compact" ? "compact" : "preview"; +}; + +export const writeMobileMemoListDensity = (density: MobileMemoListDensity) => AsyncStorage.setItem(MEMO_LIST_DENSITY_KEY, density); diff --git a/apps/mobile/src/screens/WorkspaceScreen.tsx b/apps/mobile/src/screens/WorkspaceScreen.tsx index f22ba69d..a4a5893b 100644 --- a/apps/mobile/src/screens/WorkspaceScreen.tsx +++ b/apps/mobile/src/screens/WorkspaceScreen.tsx @@ -62,6 +62,7 @@ import { import { SafeAreaView } from "react-native-safe-area-context"; import type { ApiToken, MemoDetail, MemoRevision, MemoSummary, Notebook, ResourceListItem, TagSummary } from "@edgeever/shared"; import { clearMobileMemoDraft, readMobileMemoDraft, writeMobileMemoDraft } from "../lib/mobile-drafts"; +import { readMobileMemoListDensity, writeMobileMemoListDensity, type MobileMemoListDensity } from "../lib/preferences"; import { useSession } from "../lib/session"; import { emptyMobileSyncQueueSummary, @@ -171,6 +172,7 @@ export const WorkspaceScreen = () => { const [memoView, setMemoView] = useState("notebook"); const [memoFilterMode, setMemoFilterMode] = useState("all"); const [memoSortMode, setMemoSortMode] = useState("updated-desc"); + const [memoListDensity, setMemoListDensity] = useState("preview"); const [selectedMemoId, setSelectedMemoId] = useState(null); const [searchText, setSearchText] = useState(""); const [createOpen, setCreateOpen] = useState(false); @@ -320,6 +322,25 @@ export const WorkspaceScreen = () => { }; }, []); + useEffect(() => { + let mounted = true; + + readMobileMemoListDensity().then((density) => { + if (mounted) { + setMemoListDensity(density); + } + }); + + return () => { + mounted = false; + }; + }, []); + + const handleMemoListDensityChange = (density: MobileMemoListDensity) => { + setMemoListDensity(density); + void writeMobileMemoListDensity(density); + }; + const invalidateWorkspace = async () => { await Promise.all([ queryClient.invalidateQueries({ queryKey: ["mobile", "notebooks"] }), @@ -545,6 +566,7 @@ export const WorkspaceScreen = () => { isRefreshing={isRefreshing} memoCount={memosQuery.data?.totalCount ?? memos.length} memoFilterMode={memoFilterMode} + memoListDensity={memoListDensity} memoSortMode={memoSortMode} memoView={memoView} memos={memos} @@ -553,6 +575,7 @@ export const WorkspaceScreen = () => { onCreate={() => setCreateOpen(true)} onEmptyTrash={handleEmptyTrash} onFilterModeChange={setMemoFilterMode} + onMemoListDensityChange={handleMemoListDensityChange} onOpenTemplates={() => setTemplatesOpen(true)} onMemoPress={handleMemoPress} onMemoLongPress={toggleSelectedMemo} @@ -757,6 +780,7 @@ const NotesView = ({ isRefreshing, memoCount, memoFilterMode, + memoListDensity, memoSortMode, memoView, memos, @@ -765,6 +789,7 @@ const NotesView = ({ onCreate, onEmptyTrash, onFilterModeChange, + onMemoListDensityChange, onMemoLongPress, onMemoPress, onOpenTemplates, @@ -783,6 +808,7 @@ const NotesView = ({ isRefreshing: boolean; memoCount: number; memoFilterMode: MemoFilterMode; + memoListDensity: MobileMemoListDensity; memoSortMode: MemoSortMode; memoView: MemoView; memos: MemoSummary[]; @@ -791,6 +817,7 @@ const NotesView = ({ onCreate: () => void; onEmptyTrash: () => void; onFilterModeChange: (filterMode: MemoFilterMode) => void; + onMemoListDensityChange: (density: MobileMemoListDensity) => void; onMemoLongPress: (memoId: string) => void; onMemoPress: (memoId: string) => void; onOpenTemplates: () => void; @@ -863,6 +890,10 @@ const NotesView = ({ onSortModeChange("created-desc")} /> onSortModeChange("title-asc")} /> + + onMemoListDensityChange("preview")} /> + onMemoListDensityChange("compact")} /> + ) : null} @@ -873,6 +904,7 @@ const NotesView = ({ isError={isError} isLoading={isLoading} isRefreshing={isRefreshing} + listDensity={memoListDensity} memos={memos} onMemoLongPress={onMemoLongPress} onMemoPress={onMemoPress} @@ -931,6 +963,7 @@ const SearchView = ({ isError={false} isLoading={isLoading} isRefreshing={isRefreshing} + listDensity="preview" memos={results} onMemoPress={onMemoPress} onRefresh={onRefresh} @@ -2805,6 +2838,7 @@ const MemoList = ({ isError, isLoading, isRefreshing, + listDensity, memos, onMemoLongPress, onMemoPress, @@ -2817,6 +2851,7 @@ const MemoList = ({ isError: boolean; isLoading: boolean; isRefreshing: boolean; + listDensity: MobileMemoListDensity; memos: MemoSummary[]; onMemoLongPress?: (memoId: string) => void; onMemoPress: (memoId: string) => void; @@ -2849,6 +2884,7 @@ const MemoList = ({ renderItem={({ item }) => ( onMemoLongPress(item.id) : undefined} onPress={() => onMemoPress(item.id)} selected={selectedMemoIds.has(item.id)} @@ -3034,19 +3070,21 @@ const OptionPill = ({ active, label, onPress }: { active: boolean; label: string ); const MemoCard = ({ + listDensity, memo, onLongPress, onPress, selected = false, selectionMode = false, }: { + listDensity: MobileMemoListDensity; memo: MemoSummary; onLongPress?: () => void; onPress: () => void; selected?: boolean; selectionMode?: boolean; }) => ( - + {selectionMode ? ( @@ -3060,10 +3098,12 @@ const MemoCard = ({ {memo.isPinned ? 置顶 : null} - - {memo.excerpt || "没有正文预览"} - - + {listDensity === "preview" ? ( + + {memo.excerpt || "没有正文预览"} + + ) : null} + {formatDate(memo.updatedAt)} {memo.tags.slice(0, 2).map((tag) => ( @@ -3733,6 +3773,10 @@ const styles = StyleSheet.create({ marginBottom: 10, padding: 14, }, + memoCardCompact: { + marginBottom: 8, + padding: 11, + }, memoCardSelected: { backgroundColor: "#f8fafc", borderColor: "#0f172a", @@ -3779,6 +3823,9 @@ const styles = StyleSheet.create({ gap: 8, marginTop: 12, }, + memoMetaCompact: { + marginTop: 8, + }, memoDate: { color: "#94a3b8", fontSize: 12,