feat(Theme): enhance theming and UI consistency across components
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 8m15s
Frontend CI / ota-android (push) Successful in 10m56s
Frontend CI / build-android-apk (push) Successful in 1h3m22s

- Updated app.json to set userInterfaceStyle to automatic for improved theme adaptability.
- Refactored layout components to utilize useAppColors for dynamic theming, ensuring consistent color usage.
- Introduced SystemChrome component to manage system UI background color based on theme.
- Enhanced TabsLayout, ProfileStackLayout, and other components to leverage new theming structure.
- Improved QRCodeScanner, SearchBar, and CommentItem styles to align with the updated theme system.
- Consolidated styles in SystemMessageItem and TabBar for better maintainability and visual coherence.
This commit is contained in:
lafay
2026-03-25 05:16:54 +08:00
parent 90d834695f
commit 4ee3079b9f
86 changed files with 6777 additions and 5890 deletions

View File

@@ -5,7 +5,7 @@
* 支持预览图优化
*/
import React, { useMemo, useCallback, useState, useEffect } from 'react';
import React, { useMemo, useCallback, useState, useEffect, createContext, useContext } from 'react';
import {
View,
StyleSheet,
@@ -16,7 +16,7 @@ import {
Image,
} from 'react-native';
import { SmartImage } from './SmartImage';
import { colors, spacing, borderRadius } from '../../theme';
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
import { getPreviewImageUrl, ImageDisplayMode } from '../../utils/imageHelper';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
@@ -101,6 +101,89 @@ const calculateGridDimensions = (
};
};
function createImageGridStyles(colors: AppColors) {
return StyleSheet.create({
container: {
marginTop: spacing.sm,
},
fullSize: {
flex: 1,
width: '100%',
height: '100%',
},
singleContainer: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
horizontalContainer: {
flexDirection: 'row',
},
horizontalItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
gridItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
aspectRatio: 1,
},
gridItem2: {
width: '48%',
},
gridItem3: {
width: '31%',
},
masonryContainer: {
flexDirection: 'row',
},
masonryColumn: {
flex: 1,
},
masonryItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
moreOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: borderRadius.md,
},
moreText: {
color: colors.text.inverse,
fontSize: fontSizes.xl,
fontWeight: '500',
},
compactContainer: {
marginTop: spacing.xs,
},
compactGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
},
compactItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
});
}
type ImageGridStyles = ReturnType<typeof createImageGridStyles>;
const ImageGridStylesContext = createContext<ImageGridStyles | null>(null);
function useImageGridStyles(): ImageGridStyles {
const ctx = useContext(ImageGridStylesContext);
if (!ctx) {
throw new Error('useImageGridStyles must be used within ImageGrid or CompactImageGrid');
}
return ctx;
}
// ─── 单张图片子组件 ───────────────────────────────────────────────────────────
// 独立成组件,方便用 useState 管理异步加载到的实际尺寸
@@ -123,6 +206,7 @@ const SingleImageItem: React.FC<SingleImageItemProps> = ({
usePreview,
displayMode,
}) => {
const styles = useImageGridStyles();
const [aspectRatio, setAspectRatio] = useState<number | null>(null);
const mainUri = image.uri || image.url || '';
const thumbnailUri =
@@ -245,7 +329,8 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
displayMode = 'list',
usePreview = true,
}) => {
// 通过 onLayout 拿到容器实际宽度
const colors = useAppColors();
const gridStyles = useMemo(() => createImageGridStyles(colors), [colors]);
const [containerWidth, setContainerWidth] = useState(0);
// 过滤有效图片 - 支持 uri 或 url 字段
@@ -303,7 +388,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
// 渲染横向双图
const renderHorizontal = () => {
return (
<View style={[styles.horizontalContainer, { gap }]}>
<View style={[gridStyles.horizontalContainer, { gap }]}>
{displayImages.map((image, index) => {
const previewUrl = usePreview && displayMode
? getPreviewImageUrl(image as any, displayMode)
@@ -314,7 +399,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
key={image.id || index}
onPress={() => handleImagePress(index)}
style={[
styles.horizontalItem,
gridStyles.horizontalItem,
{
flex: 1,
aspectRatio: 1,
@@ -324,7 +409,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
>
<SmartImage
source={{ uri: previewUrl }}
style={styles.fullSize}
style={gridStyles.fullSize}
resizeMode="cover"
borderRadius={borderRadiusValue}
/>
@@ -338,7 +423,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
// 渲染网格布局
const renderGrid = () => {
return (
<View style={[styles.gridContainer, { gap }]}>
<View style={[gridStyles.gridContainer, { gap }]}>
{displayImages.map((image, index) => {
const isLastVisible = index === displayImages.length - 1;
const showOverlay = isLastVisible && remainingCount > 0 && showMoreOverlay;
@@ -352,8 +437,8 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
key={image.id || index}
onPress={() => handleImagePress(index)}
style={[
styles.gridItem,
gridColumns === 3 ? styles.gridItem3 : styles.gridItem2,
gridStyles.gridItem,
gridColumns === 3 ? gridStyles.gridItem3 : gridStyles.gridItem2,
{
borderRadius: borderRadiusValue,
},
@@ -361,13 +446,13 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
>
<SmartImage
source={{ uri: previewUrl }}
style={styles.fullSize}
style={gridStyles.fullSize}
resizeMode="cover"
borderRadius={borderRadiusValue}
/>
{showOverlay && (
<View style={styles.moreOverlay}>
<Text style={styles.moreText}>+{remainingCount}</Text>
<View style={gridStyles.moreOverlay}>
<Text style={gridStyles.moreText}>+{remainingCount}</Text>
</View>
)}
</Pressable>
@@ -397,7 +482,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
const renderColumn = (columnImages: ImageGridItem[], columnIndex: number) => {
return (
<View style={[styles.masonryColumn, { gap }]}>
<View style={[gridStyles.masonryColumn, { gap }]}>
{columnImages.map((image, index) => {
const actualIndex = columnIndex + index * 2;
const aspectRatio = image.width && image.height
@@ -414,7 +499,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
key={image.id || actualIndex}
onPress={() => handleImagePress(actualIndex)}
style={[
styles.masonryItem,
gridStyles.masonryItem,
{
width: itemWidth,
height: Math.max(height, itemWidth * 0.7),
@@ -424,7 +509,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
>
<SmartImage
source={{ uri: previewUrl }}
style={styles.fullSize}
style={gridStyles.fullSize}
resizeMode="cover"
borderRadius={borderRadiusValue}
/>
@@ -436,7 +521,7 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
};
return (
<View style={[styles.masonryContainer, { gap }]}>
<View style={[gridStyles.masonryContainer, { gap }]}>
{renderColumn(leftColumn, 0)}
{renderColumn(rightColumn, 1)}
</View>
@@ -464,13 +549,15 @@ export const ImageGrid: React.FC<ImageGridProps> = ({
}
return (
<View
style={[styles.container, style]}
testID={testID}
onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}
>
{renderContent()}
</View>
<ImageGridStylesContext.Provider value={gridStyles}>
<View
style={[gridStyles.container, style]}
testID={testID}
onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}
>
{renderContent()}
</View>
</ImageGridStylesContext.Provider>
);
};
@@ -490,6 +577,8 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
borderRadius: borderRadiusValue = borderRadius.sm,
...props
}) => {
const colors = useAppColors();
const gridStyles = useMemo(() => createImageGridStyles(colors), [colors]);
const containerWidth = maxWidth || SCREEN_WIDTH - DEFAULT_CONTAINER_PADDING - 36 - spacing.sm; // 36是头像宽度
const renderCompactGrid = () => {
@@ -506,7 +595,7 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
<Pressable
onPress={() => props.onImagePress?.(images, 0)}
style={[
styles.compactItem,
gridStyles.compactItem,
{
width: size,
height: size,
@@ -516,7 +605,7 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
>
<SmartImage
source={{ uri: image.uri || image.url, width: image.width, height: image.height }}
style={styles.fullSize}
style={gridStyles.fullSize}
resizeMode="cover"
borderRadius={borderRadiusValue}
/>
@@ -529,13 +618,13 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
const { itemSize } = calculateGridDimensions(count, containerWidth, gap, columns);
return (
<View style={[styles.compactGrid, { gap }]}>
<View style={[gridStyles.compactGrid, { gap }]}>
{images.slice(0, 6).map((image, index) => (
<Pressable
key={image.id || index}
onPress={() => props.onImagePress?.(images, index)}
style={[
styles.compactItem,
gridStyles.compactItem,
{
width: itemSize,
height: itemSize,
@@ -545,13 +634,13 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
>
<SmartImage
source={{ uri: image.uri || image.url, width: image.width, height: image.height }}
style={styles.fullSize}
style={gridStyles.fullSize}
resizeMode="cover"
borderRadius={borderRadiusValue}
/>
{index === 5 && images.length > 6 && (
<View style={styles.moreOverlay}>
<Text style={styles.moreText}>+{images.length - 6}</Text>
<View style={gridStyles.moreOverlay}>
<Text style={gridStyles.moreText}>+{images.length - 6}</Text>
</View>
)}
</Pressable>
@@ -560,86 +649,11 @@ export const CompactImageGrid: React.FC<CompactImageGridProps> = ({
);
};
return <View style={styles.compactContainer}>{renderCompactGrid()}</View>;
return (
<ImageGridStylesContext.Provider value={gridStyles}>
<View style={gridStyles.compactContainer}>{renderCompactGrid()}</View>
</ImageGridStylesContext.Provider>
);
};
const styles = StyleSheet.create({
container: {
marginTop: spacing.sm,
},
fullSize: {
flex: 1,
width: '100%',
height: '100%',
},
// 单图样式
singleContainer: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
// 横向布局样式
horizontalContainer: {
flexDirection: 'row',
},
horizontalItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
// 网格布局样式
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
gridItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
aspectRatio: 1,
},
gridItem2: {
width: '48%', // 2列布局每列约48%宽度,留有余量避免换行
},
gridItem3: {
width: '31%', // 3列布局每列约31%宽度,留有余量避免换行
},
// 瀑布流样式
masonryContainer: {
flexDirection: 'row',
},
masonryColumn: {
flex: 1,
},
masonryItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
// 更多遮罩 - 类似微博的灰色蒙版
moreOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: borderRadius.md,
},
moreText: {
color: colors.text.inverse,
fontSize: fontSizes.xl,
fontWeight: '500',
},
// 紧凑模式样式
compactContainer: {
marginTop: spacing.xs,
},
compactGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
},
compactItem: {
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
});
// 导入字体大小
import { fontSizes } from '../../theme';
export default ImageGrid;