refactor(TabsLayout, ImageGallery, ChatScreen, GroupInfoScreen, PrivateChatInfoScreen): enhance UI components and improve layout structure
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 3m4s
Frontend CI / ota-android (push) Successful in 12m24s
Frontend CI / build-android-apk (push) Successful in 1h17m36s

- Adjusted tab bar dimensions and margins in TabsLayout for better visual consistency.
- Streamlined ImageGallery by removing loading states and optimizing image transition handling.
- Updated ChatScreen to utilize SafeAreaView for improved layout on different devices.
- Enhanced GroupInfoScreen and PrivateChatInfoScreen with a consistent page header layout, including back navigation.
- Refactored ChatHeader to simplify structure and improve maintainability.
This commit is contained in:
lafay
2026-03-24 15:39:28 +08:00
parent 2ddb9cadd8
commit b49cc0f3bd
6 changed files with 126 additions and 110 deletions

View File

@@ -13,7 +13,6 @@ import {
TouchableOpacity,
Text,
StatusBar,
ActivityIndicator,
Alert,
} from 'react-native';
import { Image as ExpoImage } from 'expo-image';
@@ -25,10 +24,8 @@ import {
import Animated, {
useSharedValue,
useAnimatedStyle,
useDerivedValue,
runOnJS,
withTiming,
withSpring,
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
@@ -91,7 +88,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
const [currentIndex, setCurrentIndex] = useState(initialIndex);
currentIndexRef.current = currentIndex;
const [showControls, setShowControls] = useState(true);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [saving, setSaving] = useState(false);
const [saveToast, setSaveToast] = useState<'success' | 'error' | null>(null);
@@ -132,7 +128,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
if (visible) {
setCurrentIndex(initialIndex);
setShowControls(true);
setLoading(true);
setError(false);
resetZoom();
StatusBar.setHidden(true, 'fade');
@@ -166,7 +161,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
if (clampedIndex === currentIndexRef.current) {
return;
}
setLoading(true);
setError(false);
setCurrentIndex(clampedIndex);
onIndexChange?.(clampedIndex);
@@ -266,24 +260,16 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
// 滑动切换图片相关状态
const swipeTranslateX = useSharedValue(0);
// 滑动切图完成后,让新图从目标方向进入,避免旧图先弹回导致前后图闪烁
useEffect(() => {
const direction = pendingSwipeDirectionRef.current;
if (direction == null) {
return;
}
pendingSwipeDirectionRef.current = null;
swipeTranslateX.value = -direction * SCREEN_WIDTH;
swipeTranslateX.value = withTiming(0, { duration: 180 });
swipeTranslateX.value = 0;
}, [currentIndex, swipeTranslateX]);
const switchWithDirection = useCallback(
(targetIndex: number, direction: -1 | 1) => {
swipeTranslateX.value = withTiming(direction * SCREEN_WIDTH, { duration: 200 }, () => {
runOnJS(updateIndexFromSwipe)(targetIndex, direction);
});
updateIndexFromSwipe(targetIndex, direction);
},
[swipeTranslateX, updateIndexFromSwipe]
[updateIndexFromSwipe]
);
const goToPrev = useCallback(() => {
@@ -310,16 +296,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
// 放大状态下:拖动图片
translateX.value = savedTranslateX.value + e.translationX;
translateY.value = savedTranslateY.value + e.translationY;
} else if (validImages.length > 1) {
// 未放大且有多张图片:切换图片的跟随效果
const isFirst = currentIndex === 0 && e.translationX > 0;
const isLast = currentIndex === validImages.length - 1 && e.translationX < 0;
if (isFirst || isLast) {
// 边界阻力效果
swipeTranslateX.value = e.translationX * 0.3;
} else {
swipeTranslateX.value = e.translationX;
}
}
})
.onEnd((e) => {
@@ -337,17 +313,10 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
if (shouldGoNext && currentIndex < validImages.length - 1) {
// 向左滑动,显示下一张
swipeTranslateX.value = withTiming(-SCREEN_WIDTH, { duration: 200 }, () => {
runOnJS(updateIndexFromSwipe)(currentIndex + 1, -1);
});
runOnJS(updateIndexFromSwipe)(currentIndex + 1, -1);
} else if (shouldGoPrev && currentIndex > 0) {
// 向右滑动,显示上一张
swipeTranslateX.value = withTiming(SCREEN_WIDTH, { duration: 200 }, () => {
runOnJS(updateIndexFromSwipe)(currentIndex - 1, 1);
});
} else {
// 回弹到原位
swipeTranslateX.value = withTiming(0, { duration: 200 });
runOnJS(updateIndexFromSwipe)(currentIndex - 1, 1);
}
}
});
@@ -411,7 +380,7 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
disabled={saving}
>
{saving ? (
<ActivityIndicator size="small" color="#FFF" />
<MaterialCommunityIcons name="loading" size={24} color="#FFF" />
) : (
<MaterialCommunityIcons name="download" size={24} color="#FFF" />
)}
@@ -423,12 +392,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
{/* 图片显示区域 */}
<GestureDetector gesture={composedGesture}>
<View style={styles.imageContainer}>
{loading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#FFF" />
</View>
)}
{error && (
<View style={styles.errorContainer}>
<MaterialCommunityIcons name="image-off" size={48} color="#999" />
@@ -447,13 +410,10 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
recyclingKey={currentImage.url}
transition={null}
allowDownscaling
onLoadStart={() => setLoading(true)}
onLoad={() => {
setLoading(false);
setError(false);
}}
onError={() => {
setLoading(false);
setError(true);
}}
/>
@@ -584,13 +544,6 @@ const styles = StyleSheet.create({
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT * 0.8,
},
loadingContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
zIndex: 5,
backgroundColor: '#000',
},
errorContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',