Skip to content

Commit 60eff62

Browse files
committed
fix(mobile): move sheet glass controls to ViewNote level for proper liquid glass effect
1 parent 36d07ef commit 60eff62

6 files changed

Lines changed: 805 additions & 169 deletions

File tree

apps/mobile/v1/app/edit-sheet.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '../src/screens/EditSheet';

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

Lines changed: 52 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
1+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import {
33
ActivityIndicator,
44
Animated,
55
Dimensions,
66
NativeScrollEvent,
77
NativeSyntheticEvent,
8-
ScrollView,
98
StyleSheet,
109
Text,
11-
TouchableOpacity,
1210
View,
1311
} from 'react-native';
14-
import { GlassView } from 'expo-glass-effect';
1512

16-
// Background colors for glass effect based on theme
17-
const GLASS_BG_DARK = 'rgba(255, 255, 255, 0.01)';
18-
const GLASS_BG_LIGHT = 'rgba(0, 0, 0, 0.01)';
13+
// Background colors for glass effect - exported for parent to use
14+
export const GLASS_BG_DARK = 'rgba(255, 255, 255, 0.01)';
15+
export const GLASS_BG_LIGHT = 'rgba(0, 0, 0, 0.01)';
1916

2017
// Excel date serial number range (1900-2099)
2118
const MIN_DATE_SERIAL = 1;
@@ -49,32 +46,6 @@ const getTextAlign = (ht?: number): 'left' | 'center' | 'right' => {
4946
}
5047
};
5148

52-
/**
53-
* Memoized glass button for sheet tabs.
54-
* Prevents re-render on parent state changes to preserve iOS liquid glass effect.
55-
*/
56-
const GlassTabButton = memo(function GlassTabButton({
57-
name,
58-
isDark,
59-
textColor,
60-
}: {
61-
name: string;
62-
isDark: boolean;
63-
textColor: string;
64-
}) {
65-
return (
66-
<GlassView
67-
glassEffectStyle="regular"
68-
style={[styles.glassTab, { backgroundColor: isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT }]}
69-
pointerEvents="none"
70-
>
71-
<View style={styles.tabInner}>
72-
<Text style={[styles.tabText, { color: textColor }]}>{name}</Text>
73-
</View>
74-
</GlassView>
75-
);
76-
});
77-
7849
// Types based on Univer's data structure
7950
interface ICellData {
8051
v?: string | number | boolean | null;
@@ -136,6 +107,19 @@ interface IWorkbookData {
136107
styles?: { [styleId: string]: IStyleData | null };
137108
}
138109

110+
export interface SheetControlsData {
111+
zoom: number;
112+
minZoom: number;
113+
maxZoom: number;
114+
sheetNames: string[];
115+
activeSheetIndex: number;
116+
hasMultipleSheets: boolean;
117+
tabBarHeight: number;
118+
onZoomIn: () => void;
119+
onZoomOut: () => void;
120+
onSelectSheet: (index: number) => void;
121+
}
122+
139123
interface NativeSheetsViewerProps {
140124
content: string;
141125
theme: {
@@ -151,6 +135,8 @@ interface NativeSheetsViewerProps {
151135
onLoaded?: () => void;
152136
hideLoadingOverlay?: boolean;
153137
bottomInset?: number;
138+
/** Callback to provide sheet controls data to parent for rendering outside ScrollView */
139+
onControlsReady?: (controls: SheetControlsData) => void;
154140
}
155141

156142
// Constants - match typical Excel/Sheets defaults (these are only used as last resort)
@@ -280,6 +266,7 @@ export function NativeSheetsViewer({
280266
onLoaded,
281267
hideLoadingOverlay,
282268
bottomInset = 0,
269+
onControlsReady,
283270
}: NativeSheetsViewerProps) {
284271
const [loading, setLoading] = useState(true);
285272
const [activeSheetIndex, setActiveSheetIndex] = useState(0);
@@ -453,6 +440,19 @@ export function NativeSheetsViewer({
453440
return label;
454441
};
455442

443+
// Zoom control handlers
444+
const handleZoomIn = useCallback(() => {
445+
setZoom(z => Math.min(MAX_ZOOM, z + 0.25));
446+
}, []);
447+
448+
const handleZoomOut = useCallback(() => {
449+
setZoom(z => Math.max(MIN_ZOOM, z - 0.25));
450+
}, []);
451+
452+
const handleSelectSheet = useCallback((index: number) => {
453+
setActiveSheetIndex(index);
454+
}, []);
455+
456456
useEffect(() => {
457457
if (currentSheet) {
458458
setLoading(false);
@@ -466,6 +466,24 @@ export function NativeSheetsViewer({
466466
const cellBg = theme.colors.background;
467467
const tabBarHeight = hasMultipleSheets ? 48 + bottomInset : 0;
468468

469+
// Notify parent about controls data so it can render glass controls outside ScrollView
470+
useEffect(() => {
471+
if (currentSheet && onControlsReady) {
472+
onControlsReady({
473+
zoom,
474+
minZoom: MIN_ZOOM,
475+
maxZoom: MAX_ZOOM,
476+
sheetNames: sheetInfo.names,
477+
activeSheetIndex,
478+
hasMultipleSheets,
479+
tabBarHeight,
480+
onZoomIn: handleZoomIn,
481+
onZoomOut: handleZoomOut,
482+
onSelectSheet: handleSelectSheet,
483+
});
484+
}
485+
}, [currentSheet, onControlsReady, zoom, sheetInfo.names, activeSheetIndex, hasMultipleSheets, tabBarHeight, handleZoomIn, handleZoomOut, handleSelectSheet]);
486+
469487
if (!workbook || !currentSheet) {
470488
return (
471489
<View style={[styles.container, { backgroundColor: cellBg }]}>
@@ -833,72 +851,7 @@ export function NativeSheetsViewer({
833851
</Animated.ScrollView>
834852
</View>
835853

836-
{/* Zoom controls */}
837-
<View style={[styles.zoomControls, { bottom: tabBarHeight + 16 }]}>
838-
<TouchableOpacity onPress={() => setZoom(z => Math.max(MIN_ZOOM, z - 0.25))}>
839-
<GlassView
840-
glassEffectStyle="regular"
841-
style={[styles.glassButton, { backgroundColor: theme.isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT }]}
842-
pointerEvents="none"
843-
>
844-
<View style={styles.zoomButtonInner}>
845-
<Text style={[styles.zoomButtonText, { color: theme.colors.foreground }]}></Text>
846-
</View>
847-
</GlassView>
848-
</TouchableOpacity>
849-
<GlassView
850-
glassEffectStyle="regular"
851-
style={[styles.glassLabelContainer, { backgroundColor: theme.isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT }]}
852-
>
853-
<View style={styles.zoomLabelInner}>
854-
<Text style={[styles.zoomLabel, { color: theme.colors.foreground }]}>
855-
{Math.round(zoom * 100)}%
856-
</Text>
857-
</View>
858-
</GlassView>
859-
<TouchableOpacity onPress={() => setZoom(z => Math.min(MAX_ZOOM, z + 0.25))}>
860-
<GlassView
861-
glassEffectStyle="regular"
862-
style={[styles.glassButton, { backgroundColor: theme.isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT }]}
863-
pointerEvents="none"
864-
>
865-
<View style={styles.zoomButtonInner}>
866-
<Text style={[styles.zoomButtonText, { color: theme.colors.foreground }]}>+</Text>
867-
</View>
868-
</GlassView>
869-
</TouchableOpacity>
870-
</View>
871-
872-
873-
{/* Sheet tabs */}
874-
{hasMultipleSheets && (
875-
<View
876-
style={[
877-
styles.tabBar,
878-
{
879-
backgroundColor: cellBg,
880-
borderTopColor: borderColor,
881-
paddingBottom: bottomInset,
882-
},
883-
]}
884-
>
885-
<ScrollView
886-
horizontal
887-
showsHorizontalScrollIndicator={false}
888-
contentContainerStyle={styles.tabScrollContent}
889-
>
890-
{sheetInfo.names.map((name, index) => (
891-
<TouchableOpacity
892-
key={`sheet-tab-${name}`}
893-
onPress={() => setActiveSheetIndex(index)}
894-
style={{ opacity: index === activeSheetIndex ? 1 : 0.6 }}
895-
>
896-
<GlassTabButton name={name} isDark={theme.isDark} textColor={theme.colors.foreground} />
897-
</TouchableOpacity>
898-
))}
899-
</ScrollView>
900-
</View>
901-
)}
854+
{/* Glass controls (zoom, tabs) are rendered by parent outside ScrollView for proper liquid glass */}
902855
</View>
903856
);
904857
}
@@ -963,70 +916,4 @@ const styles = StyleSheet.create({
963916
headerText: {
964917
fontWeight: '500',
965918
},
966-
zoomControls: {
967-
position: 'absolute',
968-
right: 16,
969-
flexDirection: 'row',
970-
alignItems: 'center',
971-
zIndex: 20,
972-
gap: 8,
973-
},
974-
glassButton: {
975-
borderRadius: 19,
976-
overflow: 'hidden',
977-
},
978-
glassLabelContainer: {
979-
borderRadius: 19,
980-
overflow: 'hidden',
981-
},
982-
zoomButtonInner: {
983-
width: 38,
984-
height: 38,
985-
alignItems: 'center',
986-
justifyContent: 'center',
987-
},
988-
zoomButtonText: {
989-
fontSize: 22,
990-
fontWeight: '500',
991-
},
992-
zoomLabelInner: {
993-
height: 38,
994-
paddingHorizontal: 12,
995-
alignItems: 'center',
996-
justifyContent: 'center',
997-
},
998-
zoomLabel: {
999-
fontSize: 15,
1000-
fontWeight: '600',
1001-
minWidth: 44,
1002-
textAlign: 'center',
1003-
},
1004-
tabBar: {
1005-
position: 'absolute',
1006-
bottom: 0,
1007-
left: 0,
1008-
right: 0,
1009-
flexDirection: 'row',
1010-
borderTopWidth: StyleSheet.hairlineWidth,
1011-
paddingTop: 10,
1012-
paddingHorizontal: 8,
1013-
},
1014-
tabScrollContent: {
1015-
gap: 8,
1016-
paddingHorizontal: 8,
1017-
},
1018-
glassTab: {
1019-
borderRadius: 19,
1020-
overflow: 'hidden',
1021-
},
1022-
tabInner: {
1023-
height: 38,
1024-
paddingHorizontal: 16,
1025-
justifyContent: 'center',
1026-
alignItems: 'center',
1027-
},
1028-
tabText: {
1029-
fontSize: 15,
1030-
fontWeight: '500',
1031-
},
1032919
});

0 commit comments

Comments
 (0)