Skip to content

Commit fd840b8

Browse files
committed
fix: improve sheet loading UX and add Liquid Glass buttons
- Add progressive loading messages for sheets ("Loading Sheet", "Crunching numbers", etc.) - Fix loading position shift by using single overlay with consistent positioning - Check cached note type for immediate "Loading Sheet" display - Add Liquid Glass styling to Create Folder buttons (Home, FolderNotes, SubfoldersList) - Add Note and Sheet filters to Filter & Sort sheet - Reorganize filters in 2x2 grid layout, sorted alphabetically - Add spreadsheet preview in notes list (shows first 6 cell values instead of raw JSON)
1 parent b63125a commit fd840b8

File tree

14 files changed

+605
-245
lines changed

14 files changed

+605
-245
lines changed

apps/mobile/v1/src/components/SheetsViewer.tsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useMemo, useState } from 'react';
2-
import { ActivityIndicator, StyleSheet, View } from 'react-native';
1+
import React, { useEffect, useMemo, useState } from 'react';
2+
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
33
import { WebView } from 'react-native-webview';
44

55
interface SheetsViewerProps {
@@ -13,14 +13,48 @@ interface SheetsViewerProps {
1313
};
1414
isDark: boolean;
1515
};
16+
onLoaded?: () => void;
17+
hideLoadingOverlay?: boolean;
1618
}
1719

1820
/**
1921
* SheetsViewer - Read-only spreadsheet viewer for mobile
2022
* Uses Univer library via WebView to render spreadsheets exactly as they appear on web
2123
*/
22-
export function SheetsViewer({ content, theme }: SheetsViewerProps) {
24+
// Fun loading messages that appear progressively
25+
const LOADING_MESSAGES = [
26+
'Loading Sheet',
27+
'Crunching numbers',
28+
'Almost there',
29+
'Worth the wait',
30+
];
31+
32+
export function SheetsViewer({ content, theme, onLoaded, hideLoadingOverlay }: SheetsViewerProps) {
2333
const [loading, setLoading] = useState(true);
34+
const [messageIndex, setMessageIndex] = useState(0);
35+
36+
// Progress through loading messages
37+
useEffect(() => {
38+
if (!loading) return;
39+
40+
const timers: ReturnType<typeof setTimeout>[] = [];
41+
42+
// After 1 second, show second message
43+
timers.push(setTimeout(() => setMessageIndex(1), 1000));
44+
// After 2 seconds, show third message
45+
timers.push(setTimeout(() => setMessageIndex(2), 2000));
46+
// After 3.5 seconds, show fourth message
47+
timers.push(setTimeout(() => setMessageIndex(3), 3500));
48+
49+
return () => timers.forEach(clearTimeout);
50+
}, [loading]);
51+
52+
// Reset message index when loading starts
53+
useEffect(() => {
54+
if (loading) {
55+
setMessageIndex(0);
56+
}
57+
}, [loading]);
2458

2559
// Escape content for safe inclusion in HTML
2660
const escapedContent = useMemo(() => {
@@ -565,9 +599,12 @@ export function SheetsViewer({ content, theme }: SheetsViewerProps) {
565599

566600
return (
567601
<View style={styles.container}>
568-
{loading && (
602+
{loading && !hideLoadingOverlay && (
569603
<View style={styles.loadingOverlay}>
570604
<ActivityIndicator size="large" color={theme.colors.mutedForeground} />
605+
<Text style={[styles.loadingText, { color: theme.colors.mutedForeground }]}>
606+
{LOADING_MESSAGES[messageIndex]}
607+
</Text>
571608
</View>
572609
)}
573610
<WebView
@@ -589,15 +626,20 @@ export function SheetsViewer({ content, theme }: SheetsViewerProps) {
589626
// @ts-ignore - iOS specific - require user action to show keyboard
590627
keyboardDisplayRequiresUserAction={true}
591628
onLoadEnd={() => {
592-
setTimeout(() => setLoading(false), 1000);
629+
setTimeout(() => {
630+
setLoading(false);
631+
onLoaded?.();
632+
}, 1000);
593633
}}
594634
onMessage={(event) => {
595635
try {
596636
const data = JSON.parse(event.nativeEvent.data);
597637
if (data.type === 'loaded') {
598638
setLoading(false);
639+
onLoaded?.();
599640
} else if (data.type === 'error') {
600641
setLoading(false);
642+
onLoaded?.();
601643
console.error('SheetsViewer error:', data.message);
602644
} else if (data.type === 'debug') {
603645
console.log('SheetsViewer debug:', JSON.stringify(data, null, 2));
@@ -620,15 +662,15 @@ const styles = StyleSheet.create({
620662
backgroundColor: 'transparent',
621663
},
622664
loadingOverlay: {
623-
position: 'absolute',
624-
top: 0,
625-
left: 0,
626-
right: 0,
627-
bottom: 0,
665+
...StyleSheet.absoluteFillObject,
628666
alignItems: 'center',
629667
justifyContent: 'center',
630668
zIndex: 1,
631-
paddingBottom: 60, // Account for header
669+
},
670+
loadingText: {
671+
marginTop: 12,
672+
fontSize: 16,
673+
fontWeight: '500',
632674
},
633675
});
634676

apps/mobile/v1/src/screens/FolderNotes/components/NotesList/CreateFolderSheet.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,21 @@ export const CreateFolderSheet = forwardRef<BottomSheetModal, CreateFolderSheetP
141141

142142
<View style={styles.bottomSheetFooter}>
143143
<TouchableOpacity
144-
style={[styles.createButton, { backgroundColor: theme.colors.primary }]}
145144
onPress={handleCreateFolder}
146145
disabled={isCreatingFolder}
146+
style={{ opacity: isCreatingFolder ? 0.6 : 1 }}
147147
>
148-
<Text style={[styles.createButtonText, { color: theme.colors.primaryForeground }]}>
149-
{isCreatingFolder ? 'Creating...' : 'Create'}
150-
</Text>
148+
<GlassView
149+
glassEffectStyle="regular"
150+
style={[styles.glassCreateButton, { backgroundColor: theme.isDark ? 'rgba(255, 255, 255, 0.01)' : 'rgba(0, 0, 0, 0.01)' }]}
151+
pointerEvents="none"
152+
>
153+
<View style={[styles.createButton, { backgroundColor: theme.colors.primary }]}>
154+
<Text style={[styles.createButtonText, { color: theme.colors.primaryForeground }]}>
155+
{isCreatingFolder ? 'Creating...' : 'Create'}
156+
</Text>
157+
</View>
158+
</GlassView>
151159
</TouchableOpacity>
152160
</View>
153161
</BottomSheetView>
@@ -224,10 +232,14 @@ const styles = StyleSheet.create({
224232
bottomSheetFooter: {
225233
paddingHorizontal: 20,
226234
},
235+
glassCreateButton: {
236+
borderRadius: 12,
237+
overflow: 'hidden',
238+
},
227239
createButton: {
228240
width: '100%',
229241
padding: 14,
230-
borderRadius: 8,
242+
borderRadius: 12,
231243
alignItems: 'center',
232244
},
233245
createButtonText: {

apps/mobile/v1/src/screens/FolderNotes/components/NotesList/FilterSortSheet.tsx

Lines changed: 117 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import { useTheme } from '@/src/theme';
88

99
export interface FilterConfig {
1010
showAttachmentsOnly: boolean;
11-
showStarredOnly: boolean;
12-
showHiddenOnly: boolean;
1311
showCodeOnly: boolean;
1412
showDiagramOnly: boolean;
13+
showHiddenOnly: boolean;
14+
showNoteOnly: boolean;
1515
showPublicOnly: boolean;
16+
showSheetOnly: boolean;
17+
showStarredOnly: boolean;
1618
}
1719

1820
export interface SortConfig {
@@ -75,85 +77,112 @@ export const FilterSortSheet = forwardRef<BottomSheetModal, FilterSortSheetProps
7577
<Text style={[styles.inputLabel, { color: theme.colors.mutedForeground }]}>FILTER</Text>
7678
<TouchableOpacity
7779
style={[styles.viewModeButton, { backgroundColor: theme.colors.muted, opacity: hasActiveFilters ? 1 : 0 }]}
78-
onPress={() => setFilterConfig({ showAttachmentsOnly: false, showStarredOnly: false, showHiddenOnly: false, showCodeOnly: false, showDiagramOnly: false, showPublicOnly: false })}
80+
onPress={() => setFilterConfig({ showAttachmentsOnly: false, showCodeOnly: false, showDiagramOnly: false, showHiddenOnly: false, showNoteOnly: false, showPublicOnly: false, showSheetOnly: false, showStarredOnly: false })}
7981
activeOpacity={0.7}
8082
disabled={!hasActiveFilters}
8183
>
8284
<Ionicons name="close" size={16} color={theme.colors.mutedForeground} />
8385
</TouchableOpacity>
8486
</View>
8587

86-
<TouchableOpacity
87-
style={[styles.filterOption, { backgroundColor: filterConfig.showAttachmentsOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
88-
onPress={() => setFilterConfig(prev => ({ ...prev, showAttachmentsOnly: !prev.showAttachmentsOnly }))}
89-
>
90-
<Ionicons
91-
name={filterConfig.showAttachmentsOnly ? "checkbox" : "square-outline"}
92-
size={24}
93-
color={filterConfig.showAttachmentsOnly ? theme.colors.primary : theme.colors.mutedForeground}
94-
/>
95-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Attachment</Text>
96-
</TouchableOpacity>
88+
{/* 2x2 grid layout - Alphabetically ordered */}
89+
<View style={styles.filterGrid}>
90+
<TouchableOpacity
91+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showAttachmentsOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
92+
onPress={() => setFilterConfig(prev => ({ ...prev, showAttachmentsOnly: !prev.showAttachmentsOnly }))}
93+
>
94+
<Ionicons
95+
name={filterConfig.showAttachmentsOnly ? "checkbox" : "square-outline"}
96+
size={20}
97+
color={filterConfig.showAttachmentsOnly ? theme.colors.primary : theme.colors.mutedForeground}
98+
/>
99+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Attachment</Text>
100+
</TouchableOpacity>
97101

98-
<TouchableOpacity
99-
style={[styles.filterOption, { backgroundColor: filterConfig.showStarredOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
100-
onPress={() => setFilterConfig(prev => ({ ...prev, showStarredOnly: !prev.showStarredOnly }))}
101-
>
102-
<Ionicons
103-
name={filterConfig.showStarredOnly ? "checkbox" : "square-outline"}
104-
size={24}
105-
color={filterConfig.showStarredOnly ? theme.colors.primary : theme.colors.mutedForeground}
106-
/>
107-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Starred</Text>
108-
</TouchableOpacity>
102+
<TouchableOpacity
103+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showCodeOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
104+
onPress={() => setFilterConfig(prev => ({ ...prev, showCodeOnly: !prev.showCodeOnly }))}
105+
>
106+
<Ionicons
107+
name={filterConfig.showCodeOnly ? "checkbox" : "square-outline"}
108+
size={20}
109+
color={filterConfig.showCodeOnly ? theme.colors.primary : theme.colors.mutedForeground}
110+
/>
111+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Code</Text>
112+
</TouchableOpacity>
109113

110-
<TouchableOpacity
111-
style={[styles.filterOption, { backgroundColor: filterConfig.showHiddenOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
112-
onPress={() => setFilterConfig(prev => ({ ...prev, showHiddenOnly: !prev.showHiddenOnly }))}
113-
>
114-
<Ionicons
115-
name={filterConfig.showHiddenOnly ? "checkbox" : "square-outline"}
116-
size={24}
117-
color={filterConfig.showHiddenOnly ? theme.colors.primary : theme.colors.mutedForeground}
118-
/>
119-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Hidden</Text>
120-
</TouchableOpacity>
114+
<TouchableOpacity
115+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showDiagramOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
116+
onPress={() => setFilterConfig(prev => ({ ...prev, showDiagramOnly: !prev.showDiagramOnly }))}
117+
>
118+
<Ionicons
119+
name={filterConfig.showDiagramOnly ? "checkbox" : "square-outline"}
120+
size={20}
121+
color={filterConfig.showDiagramOnly ? theme.colors.primary : theme.colors.mutedForeground}
122+
/>
123+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Diagram</Text>
124+
</TouchableOpacity>
121125

122-
<TouchableOpacity
123-
style={[styles.filterOption, { backgroundColor: filterConfig.showCodeOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
124-
onPress={() => setFilterConfig(prev => ({ ...prev, showCodeOnly: !prev.showCodeOnly }))}
125-
>
126-
<Ionicons
127-
name={filterConfig.showCodeOnly ? "checkbox" : "square-outline"}
128-
size={24}
129-
color={filterConfig.showCodeOnly ? theme.colors.primary : theme.colors.mutedForeground}
130-
/>
131-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Code</Text>
132-
</TouchableOpacity>
126+
<TouchableOpacity
127+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showHiddenOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
128+
onPress={() => setFilterConfig(prev => ({ ...prev, showHiddenOnly: !prev.showHiddenOnly }))}
129+
>
130+
<Ionicons
131+
name={filterConfig.showHiddenOnly ? "checkbox" : "square-outline"}
132+
size={20}
133+
color={filterConfig.showHiddenOnly ? theme.colors.primary : theme.colors.mutedForeground}
134+
/>
135+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Hidden</Text>
136+
</TouchableOpacity>
133137

134-
<TouchableOpacity
135-
style={[styles.filterOption, { backgroundColor: filterConfig.showDiagramOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
136-
onPress={() => setFilterConfig(prev => ({ ...prev, showDiagramOnly: !prev.showDiagramOnly }))}
137-
>
138-
<Ionicons
139-
name={filterConfig.showDiagramOnly ? "checkbox" : "square-outline"}
140-
size={24}
141-
color={filterConfig.showDiagramOnly ? theme.colors.primary : theme.colors.mutedForeground}
142-
/>
143-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Diagram</Text>
144-
</TouchableOpacity>
138+
<TouchableOpacity
139+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showNoteOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
140+
onPress={() => setFilterConfig(prev => ({ ...prev, showNoteOnly: !prev.showNoteOnly }))}
141+
>
142+
<Ionicons
143+
name={filterConfig.showNoteOnly ? "checkbox" : "square-outline"}
144+
size={20}
145+
color={filterConfig.showNoteOnly ? theme.colors.primary : theme.colors.mutedForeground}
146+
/>
147+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Note</Text>
148+
</TouchableOpacity>
145149

146-
<TouchableOpacity
147-
style={[styles.filterOption, { backgroundColor: filterConfig.showPublicOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent' }]}
148-
onPress={() => setFilterConfig(prev => ({ ...prev, showPublicOnly: !prev.showPublicOnly }))}
149-
>
150-
<Ionicons
151-
name={filterConfig.showPublicOnly ? "checkbox" : "square-outline"}
152-
size={24}
153-
color={filterConfig.showPublicOnly ? theme.colors.primary : theme.colors.mutedForeground}
154-
/>
155-
<Text style={[styles.filterOptionText, { color: theme.colors.foreground }]}>Public</Text>
156-
</TouchableOpacity>
150+
<TouchableOpacity
151+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showPublicOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
152+
onPress={() => setFilterConfig(prev => ({ ...prev, showPublicOnly: !prev.showPublicOnly }))}
153+
>
154+
<Ionicons
155+
name={filterConfig.showPublicOnly ? "checkbox" : "square-outline"}
156+
size={20}
157+
color={filterConfig.showPublicOnly ? theme.colors.primary : theme.colors.mutedForeground}
158+
/>
159+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Public</Text>
160+
</TouchableOpacity>
161+
162+
<TouchableOpacity
163+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showSheetOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
164+
onPress={() => setFilterConfig(prev => ({ ...prev, showSheetOnly: !prev.showSheetOnly }))}
165+
>
166+
<Ionicons
167+
name={filterConfig.showSheetOnly ? "checkbox" : "square-outline"}
168+
size={20}
169+
color={filterConfig.showSheetOnly ? theme.colors.primary : theme.colors.mutedForeground}
170+
/>
171+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Sheet</Text>
172+
</TouchableOpacity>
173+
174+
<TouchableOpacity
175+
style={[styles.filterOptionGrid, { backgroundColor: filterConfig.showStarredOnly ? 'rgba(59, 130, 246, 0.1)' : 'transparent', borderColor: theme.colors.border }]}
176+
onPress={() => setFilterConfig(prev => ({ ...prev, showStarredOnly: !prev.showStarredOnly }))}
177+
>
178+
<Ionicons
179+
name={filterConfig.showStarredOnly ? "checkbox" : "square-outline"}
180+
size={20}
181+
color={filterConfig.showStarredOnly ? theme.colors.primary : theme.colors.mutedForeground}
182+
/>
183+
<Text style={[styles.filterOptionTextGrid, { color: theme.colors.foreground }]}>Starred</Text>
184+
</TouchableOpacity>
185+
</View>
157186

158187
<Text style={[styles.inputLabel, { color: theme.colors.mutedForeground, marginTop: 24 }]}>SORT BY</Text>
159188

@@ -257,6 +286,25 @@ const styles = StyleSheet.create({
257286
alignItems: 'center',
258287
justifyContent: 'center',
259288
},
289+
filterGrid: {
290+
flexDirection: 'row',
291+
flexWrap: 'wrap',
292+
gap: 8,
293+
},
294+
filterOptionGrid: {
295+
flexDirection: 'row',
296+
alignItems: 'center',
297+
paddingVertical: 10,
298+
paddingHorizontal: 12,
299+
borderRadius: 8,
300+
borderWidth: 1,
301+
width: '48%',
302+
gap: 8,
303+
},
304+
filterOptionTextGrid: {
305+
fontSize: 14,
306+
flex: 1,
307+
},
260308
filterOption: {
261309
flexDirection: 'row',
262310
alignItems: 'center',

0 commit comments

Comments
 (0)