refactor(TabsLayout, ImageGallery, ChatScreen, GroupInfoScreen, PrivateChatInfoScreen): enhance UI components and improve layout structure
- 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:
@@ -7,9 +7,9 @@ import { colors, shadows } from '../../../src/theme';
|
|||||||
import { BREAKPOINTS } from '../../../src/hooks/useResponsive';
|
import { BREAKPOINTS } from '../../../src/hooks/useResponsive';
|
||||||
import { useTotalUnreadCount } from '../../../src/stores';
|
import { useTotalUnreadCount } from '../../../src/stores';
|
||||||
|
|
||||||
const TAB_BAR_HEIGHT = 64;
|
const TAB_BAR_HEIGHT = 56;
|
||||||
const TAB_BAR_MARGIN = 14;
|
|
||||||
const TAB_BAR_FLOATING_MARGIN = 12;
|
const TAB_BAR_FLOATING_MARGIN = 12;
|
||||||
|
const TAB_BAR_MARGIN = 24;
|
||||||
|
|
||||||
export default function TabsLayout() {
|
export default function TabsLayout() {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
@@ -27,19 +27,25 @@ export default function TabsLayout() {
|
|||||||
? { display: 'none', height: 0, overflow: 'hidden' }
|
? { display: 'none', height: 0, overflow: 'hidden' }
|
||||||
: {
|
: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: TAB_BAR_MARGIN,
|
left: 0,
|
||||||
right: TAB_BAR_MARGIN,
|
right: 0,
|
||||||
|
marginHorizontal: TAB_BAR_MARGIN,
|
||||||
bottom: TAB_BAR_FLOATING_MARGIN + insets.bottom,
|
bottom: TAB_BAR_FLOATING_MARGIN + insets.bottom,
|
||||||
height: TAB_BAR_HEIGHT,
|
height: TAB_BAR_HEIGHT,
|
||||||
borderRadius: 24,
|
borderRadius: 22,
|
||||||
backgroundColor: colors.background.paper,
|
backgroundColor: colors.background.paper,
|
||||||
...shadows.lg,
|
...shadows.lg,
|
||||||
borderTopWidth: 0,
|
borderTopWidth: 0,
|
||||||
elevation: 100,
|
elevation: 100,
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
},
|
},
|
||||||
|
tabBarItemStyle: {
|
||||||
|
borderRadius: 16,
|
||||||
|
marginHorizontal: 2,
|
||||||
|
paddingVertical: 1,
|
||||||
|
},
|
||||||
tabBarShowLabel: true,
|
tabBarShowLabel: true,
|
||||||
tabBarLabelStyle: { fontSize: 12, fontWeight: '600', marginTop: -2 },
|
tabBarLabelStyle: { fontSize: 11, fontWeight: '600', marginTop: -1 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
@@ -49,7 +55,7 @@ export default function TabsLayout() {
|
|||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={focused ? 'home' : 'home-outline'}
|
name={focused ? 'home' : 'home-outline'}
|
||||||
size={focused ? 26 : 24}
|
size={focused ? 24 : 22}
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -63,7 +69,7 @@ export default function TabsLayout() {
|
|||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={focused ? 'message-text' : 'message-text-outline'}
|
name={focused ? 'message-text' : 'message-text-outline'}
|
||||||
size={focused ? 26 : 24}
|
size={focused ? 24 : 22}
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -74,7 +80,7 @@ export default function TabsLayout() {
|
|||||||
options={{
|
options={{
|
||||||
title: '课表',
|
title: '课表',
|
||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<MaterialCommunityIcons name="calendar-today" size={focused ? 26 : 24} color={color} />
|
<MaterialCommunityIcons name="calendar-today" size={focused ? 24 : 22} color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -85,7 +91,7 @@ export default function TabsLayout() {
|
|||||||
tabBarIcon: ({ color, focused }) => (
|
tabBarIcon: ({ color, focused }) => (
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={focused ? 'account' : 'account-outline'}
|
name={focused ? 'account' : 'account-outline'}
|
||||||
size={focused ? 26 : 24}
|
size={focused ? 24 : 22}
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Text,
|
Text,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
ActivityIndicator,
|
|
||||||
Alert,
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Image as ExpoImage } from 'expo-image';
|
import { Image as ExpoImage } from 'expo-image';
|
||||||
@@ -25,10 +24,8 @@ import {
|
|||||||
import Animated, {
|
import Animated, {
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useDerivedValue,
|
|
||||||
runOnJS,
|
runOnJS,
|
||||||
withTiming,
|
withTiming,
|
||||||
withSpring,
|
|
||||||
} from 'react-native-reanimated';
|
} from 'react-native-reanimated';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
@@ -91,7 +88,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
||||||
currentIndexRef.current = currentIndex;
|
currentIndexRef.current = currentIndex;
|
||||||
const [showControls, setShowControls] = useState(true);
|
const [showControls, setShowControls] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [saveToast, setSaveToast] = useState<'success' | 'error' | null>(null);
|
const [saveToast, setSaveToast] = useState<'success' | 'error' | null>(null);
|
||||||
@@ -132,7 +128,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
if (visible) {
|
if (visible) {
|
||||||
setCurrentIndex(initialIndex);
|
setCurrentIndex(initialIndex);
|
||||||
setShowControls(true);
|
setShowControls(true);
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
setError(false);
|
||||||
resetZoom();
|
resetZoom();
|
||||||
StatusBar.setHidden(true, 'fade');
|
StatusBar.setHidden(true, 'fade');
|
||||||
@@ -166,7 +161,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
if (clampedIndex === currentIndexRef.current) {
|
if (clampedIndex === currentIndexRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
setError(false);
|
||||||
setCurrentIndex(clampedIndex);
|
setCurrentIndex(clampedIndex);
|
||||||
onIndexChange?.(clampedIndex);
|
onIndexChange?.(clampedIndex);
|
||||||
@@ -266,24 +260,16 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
// 滑动切换图片相关状态
|
// 滑动切换图片相关状态
|
||||||
const swipeTranslateX = useSharedValue(0);
|
const swipeTranslateX = useSharedValue(0);
|
||||||
|
|
||||||
// 滑动切图完成后,让新图从目标方向进入,避免旧图先弹回导致前后图闪烁
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const direction = pendingSwipeDirectionRef.current;
|
|
||||||
if (direction == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pendingSwipeDirectionRef.current = null;
|
pendingSwipeDirectionRef.current = null;
|
||||||
swipeTranslateX.value = -direction * SCREEN_WIDTH;
|
swipeTranslateX.value = 0;
|
||||||
swipeTranslateX.value = withTiming(0, { duration: 180 });
|
|
||||||
}, [currentIndex, swipeTranslateX]);
|
}, [currentIndex, swipeTranslateX]);
|
||||||
|
|
||||||
const switchWithDirection = useCallback(
|
const switchWithDirection = useCallback(
|
||||||
(targetIndex: number, direction: -1 | 1) => {
|
(targetIndex: number, direction: -1 | 1) => {
|
||||||
swipeTranslateX.value = withTiming(direction * SCREEN_WIDTH, { duration: 200 }, () => {
|
updateIndexFromSwipe(targetIndex, direction);
|
||||||
runOnJS(updateIndexFromSwipe)(targetIndex, direction);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[swipeTranslateX, updateIndexFromSwipe]
|
[updateIndexFromSwipe]
|
||||||
);
|
);
|
||||||
|
|
||||||
const goToPrev = useCallback(() => {
|
const goToPrev = useCallback(() => {
|
||||||
@@ -310,16 +296,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
// 放大状态下:拖动图片
|
// 放大状态下:拖动图片
|
||||||
translateX.value = savedTranslateX.value + e.translationX;
|
translateX.value = savedTranslateX.value + e.translationX;
|
||||||
translateY.value = savedTranslateY.value + e.translationY;
|
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) => {
|
.onEnd((e) => {
|
||||||
@@ -337,17 +313,10 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
|
|
||||||
if (shouldGoNext && currentIndex < validImages.length - 1) {
|
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) {
|
} else if (shouldGoPrev && currentIndex > 0) {
|
||||||
// 向右滑动,显示上一张
|
// 向右滑动,显示上一张
|
||||||
swipeTranslateX.value = withTiming(SCREEN_WIDTH, { duration: 200 }, () => {
|
runOnJS(updateIndexFromSwipe)(currentIndex - 1, 1);
|
||||||
runOnJS(updateIndexFromSwipe)(currentIndex - 1, 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 回弹到原位
|
|
||||||
swipeTranslateX.value = withTiming(0, { duration: 200 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -411,7 +380,7 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
disabled={saving}
|
disabled={saving}
|
||||||
>
|
>
|
||||||
{saving ? (
|
{saving ? (
|
||||||
<ActivityIndicator size="small" color="#FFF" />
|
<MaterialCommunityIcons name="loading" size={24} color="#FFF" />
|
||||||
) : (
|
) : (
|
||||||
<MaterialCommunityIcons name="download" size={24} color="#FFF" />
|
<MaterialCommunityIcons name="download" size={24} color="#FFF" />
|
||||||
)}
|
)}
|
||||||
@@ -423,12 +392,6 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
{/* 图片显示区域 */}
|
{/* 图片显示区域 */}
|
||||||
<GestureDetector gesture={composedGesture}>
|
<GestureDetector gesture={composedGesture}>
|
||||||
<View style={styles.imageContainer}>
|
<View style={styles.imageContainer}>
|
||||||
{loading && (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
<ActivityIndicator size="large" color="#FFF" />
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
<MaterialCommunityIcons name="image-off" size={48} color="#999" />
|
<MaterialCommunityIcons name="image-off" size={48} color="#999" />
|
||||||
@@ -447,13 +410,10 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
|||||||
recyclingKey={currentImage.url}
|
recyclingKey={currentImage.url}
|
||||||
transition={null}
|
transition={null}
|
||||||
allowDownscaling
|
allowDownscaling
|
||||||
onLoadStart={() => setLoading(true)}
|
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
setLoading(false);
|
|
||||||
setError(false);
|
setError(false);
|
||||||
}}
|
}}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
setLoading(false);
|
|
||||||
setError(true);
|
setError(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -584,13 +544,6 @@ const styles = StyleSheet.create({
|
|||||||
width: SCREEN_WIDTH,
|
width: SCREEN_WIDTH,
|
||||||
height: SCREEN_HEIGHT * 0.8,
|
height: SCREEN_HEIGHT * 0.8,
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
|
||||||
...StyleSheet.absoluteFillObject,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: 5,
|
|
||||||
backgroundColor: '#000',
|
|
||||||
},
|
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useNavigation, useRouter } from 'expo-router';
|
import { useNavigation, useRouter } from 'expo-router';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { Text, ImageGallery, ImageGridItem } from '../../components/common';
|
import { Text, ImageGallery, ImageGridItem } from '../../components/common';
|
||||||
import { colors } from '../../theme';
|
import { colors } from '../../theme';
|
||||||
import * as hrefs from '../../navigation/hrefs';
|
import * as hrefs from '../../navigation/hrefs';
|
||||||
@@ -364,12 +364,13 @@ export const ChatScreen: React.FC = () => {
|
|||||||
}, [handleMessageListContentSizeChange]);
|
}, [handleMessageListContentSizeChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<SafeAreaView style={containerStyle} edges={['top']}>
|
||||||
style={containerStyle}
|
<KeyboardAvoidingView
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
style={styles.container}
|
||||||
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||||
>
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
||||||
<StatusBar style="dark" backgroundColor="#FFFFFF" />
|
>
|
||||||
|
<StatusBar style="dark" backgroundColor="#FFFFFF" />
|
||||||
|
|
||||||
{/* 顶部栏 */}
|
{/* 顶部栏 */}
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
@@ -587,32 +588,33 @@ export const ChatScreen: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 群信息侧边栏面板 - 仅大屏幕显示 */}
|
{/* 群信息侧边栏面板 - 仅大屏幕显示 */}
|
||||||
<GroupInfoPanel
|
<GroupInfoPanel
|
||||||
visible={showGroupInfoPanel}
|
visible={showGroupInfoPanel}
|
||||||
groupId={routeGroupId ? String(routeGroupId) : undefined}
|
groupId={routeGroupId ? String(routeGroupId) : undefined}
|
||||||
groupInfo={groupInfo as any}
|
groupInfo={groupInfo as any}
|
||||||
conversationId={conversationId || undefined}
|
conversationId={conversationId || undefined}
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
isPinned={false}
|
isPinned={false}
|
||||||
onClose={handleCloseGroupInfoPanel}
|
onClose={handleCloseGroupInfoPanel}
|
||||||
onTogglePin={(pinned) => {
|
onTogglePin={(pinned) => {
|
||||||
// TODO: 实现置顶功能
|
// TODO: 实现置顶功能
|
||||||
console.log('Toggle pin:', pinned);
|
console.log('Toggle pin:', pinned);
|
||||||
}}
|
}}
|
||||||
onLeaveGroup={() => {
|
onLeaveGroup={() => {
|
||||||
// TODO: 实现退出群聊功能
|
// TODO: 实现退出群聊功能
|
||||||
console.log('Leave group');
|
console.log('Leave group');
|
||||||
}}
|
}}
|
||||||
onInviteMembers={() => {
|
onInviteMembers={() => {
|
||||||
// TODO: 实现邀请新成员功能
|
// TODO: 实现邀请新成员功能
|
||||||
console.log('Invite members');
|
console.log('Invite members');
|
||||||
}}
|
}}
|
||||||
onViewAllMembers={() => {
|
onViewAllMembers={() => {
|
||||||
// TODO: 实现查看全部成员功能
|
// TODO: 实现查看全部成员功能
|
||||||
console.log('View all members');
|
console.log('View all members');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import * as hrefs from '../../navigation/hrefs';
|
|||||||
import { messageManager } from '../../stores/messageManager';
|
import { messageManager } from '../../stores/messageManager';
|
||||||
import { groupManager } from '../../stores/groupManager';
|
import { groupManager } from '../../stores/groupManager';
|
||||||
import MutualFollowSelectorModal from './components/MutualFollowSelectorModal';
|
import MutualFollowSelectorModal from './components/MutualFollowSelectorModal';
|
||||||
|
import { AppBackButton } from '../../components/common';
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||||
|
|
||||||
@@ -537,7 +538,12 @@ const GroupInfoScreen: React.FC = () => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.pageHeader}>
|
||||||
|
<AppBackButton onPress={() => router.back()} />
|
||||||
|
<Text variant="h3" style={styles.pageTitle}>群聊信息</Text>
|
||||||
|
<View style={styles.pageHeaderPlaceholder} />
|
||||||
|
</View>
|
||||||
<Loading />
|
<Loading />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -545,7 +551,12 @@ const GroupInfoScreen: React.FC = () => {
|
|||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.pageHeader}>
|
||||||
|
<AppBackButton onPress={() => router.back()} />
|
||||||
|
<Text variant="h3" style={styles.pageTitle}>群聊信息</Text>
|
||||||
|
<View style={styles.pageHeaderPlaceholder} />
|
||||||
|
</View>
|
||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
<Text variant="body" color={colors.text.secondary}>
|
<Text variant="body" color={colors.text.secondary}>
|
||||||
群组信息不存在
|
群组信息不存在
|
||||||
@@ -556,7 +567,12 @@ const GroupInfoScreen: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.pageHeader}>
|
||||||
|
<AppBackButton onPress={() => router.back()} />
|
||||||
|
<Text variant="h3" style={styles.pageTitle}>群聊信息</Text>
|
||||||
|
<View style={styles.pageHeaderPlaceholder} />
|
||||||
|
</View>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
@@ -1060,6 +1076,23 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.background.default,
|
backgroundColor: colors.background.default,
|
||||||
},
|
},
|
||||||
|
pageHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: spacing.md,
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
backgroundColor: colors.background.paper,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: colors.divider,
|
||||||
|
},
|
||||||
|
pageTitle: {
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
pageHeaderPlaceholder: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { userManager } from '../../stores/userManager';
|
|||||||
import { Avatar, Text, Button, Loading, Divider } from '../../components/common';
|
import { Avatar, Text, Button, Loading, Divider } from '../../components/common';
|
||||||
import { User } from '../../types';
|
import { User } from '../../types';
|
||||||
import * as hrefs from '../../navigation/hrefs';
|
import * as hrefs from '../../navigation/hrefs';
|
||||||
|
import { AppBackButton } from '../../components/common';
|
||||||
|
|
||||||
const PrivateChatInfoScreen: React.FC = () => {
|
const PrivateChatInfoScreen: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -309,7 +310,12 @@ const PrivateChatInfoScreen: React.FC = () => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.pageHeader}>
|
||||||
|
<AppBackButton onPress={() => router.back()} />
|
||||||
|
<Text variant="h3" style={styles.pageTitle}>私聊信息</Text>
|
||||||
|
<View style={styles.pageHeaderPlaceholder} />
|
||||||
|
</View>
|
||||||
<Loading />
|
<Loading />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -322,7 +328,12 @@ const PrivateChatInfoScreen: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} edges={['bottom']}>
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||||
|
<View style={styles.pageHeader}>
|
||||||
|
<AppBackButton onPress={() => router.back()} />
|
||||||
|
<Text variant="h3" style={styles.pageTitle}>私聊信息</Text>
|
||||||
|
<View style={styles.pageHeaderPlaceholder} />
|
||||||
|
</View>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
@@ -414,6 +425,23 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: colors.background.default,
|
backgroundColor: colors.background.default,
|
||||||
},
|
},
|
||||||
|
pageHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
paddingHorizontal: spacing.md,
|
||||||
|
paddingVertical: spacing.sm,
|
||||||
|
backgroundColor: colors.background.paper,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: colors.divider,
|
||||||
|
},
|
||||||
|
pageTitle: {
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
pageHeaderPlaceholder: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
import { View, TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
import { Avatar, Text, AppBackButton } from '../../../../components/common';
|
import { Avatar, Text, AppBackButton } from '../../../../components/common';
|
||||||
import { colors, spacing } from '../../../../theme';
|
import { colors, spacing } from '../../../../theme';
|
||||||
@@ -69,14 +68,9 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView edges={['top']} style={styles.headerContainer}>
|
<View style={styles.headerContainer}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
{/* 大屏幕(>= 768px)时隐藏返回按钮 */}
|
<AppBackButton style={styles.backButton} onPress={onBack} />
|
||||||
{!isWideScreen ? (
|
|
||||||
<AppBackButton style={styles.backButton} onPress={onBack} />
|
|
||||||
) : (
|
|
||||||
<View style={styles.backButton} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={styles.headerCenter}>
|
<View style={styles.headerCenter}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -136,7 +130,7 @@ export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user