feat(ui): improve navigation, component props, and chat logic
- update `app.json` with splash screen configuration - implement tab navigation redirection in `TabsLayout` for apps and profile tabs - update `HighlightText` prop usage from `style` to `highlightStyle` in `PostCard` and `PostContentRenderer` - fix chat message segment construction to avoid empty text segments when sending only images - refine UI styling by removing unnecessary shadows and adjusting `UserScreen` header visibility - improve `HomeScreen` scroll behavior by disabling animation on FlashList scroll-to-top
This commit is contained in:
9
app.json
9
app.json
@@ -155,7 +155,14 @@
|
|||||||
],
|
],
|
||||||
"./plugins/withHuaweiPush",
|
"./plugins/withHuaweiPush",
|
||||||
"expo-callkit-telecom",
|
"expo-callkit-telecom",
|
||||||
"expo-splash-screen",
|
[
|
||||||
|
"expo-splash-screen",
|
||||||
|
{
|
||||||
|
"image": "./assets/splash-icon.png",
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
"expo-status-bar"
|
"expo-status-bar"
|
||||||
],
|
],
|
||||||
"extra": {
|
"extra": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useCallback } from 'react';
|
import { useMemo, useCallback } from 'react';
|
||||||
import { Platform, Pressable, useWindowDimensions } from 'react-native';
|
import { Platform, Pressable, useWindowDimensions } from 'react-native';
|
||||||
import { Tabs, usePathname } from 'expo-router';
|
import { Tabs, usePathname, useRouter } from 'expo-router';
|
||||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import type { PressableProps } from 'react-native';
|
import type { PressableProps } from 'react-native';
|
||||||
@@ -28,6 +28,7 @@ export default function TabsLayout() {
|
|||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
const unreadCount = useTotalUnreadCount();
|
const unreadCount = useTotalUnreadCount();
|
||||||
const scrollHideTabBar = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
|
const scrollHideTabBar = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
|
||||||
const triggerHomeTabPress = useHomeTabPressStore((s) => s.triggerPress);
|
const triggerHomeTabPress = useHomeTabPressStore((s) => s.triggerPress);
|
||||||
@@ -41,6 +42,18 @@ export default function TabsLayout() {
|
|||||||
}
|
}
|
||||||
}, [pathname, triggerHomeTabPress]);
|
}, [pathname, triggerHomeTabPress]);
|
||||||
|
|
||||||
|
const handleAppsTabPress = useCallback(() => {
|
||||||
|
if (pathname.startsWith('/apps/')) {
|
||||||
|
router.replace('/apps');
|
||||||
|
}
|
||||||
|
}, [pathname, router]);
|
||||||
|
|
||||||
|
const handleProfileTabPress = useCallback(() => {
|
||||||
|
if (pathname.startsWith('/profile/')) {
|
||||||
|
router.replace('/profile');
|
||||||
|
}
|
||||||
|
}, [pathname, router]);
|
||||||
|
|
||||||
const tabBarStyle = useMemo(() => {
|
const tabBarStyle = useMemo(() => {
|
||||||
if (hideTabBar) {
|
if (hideTabBar) {
|
||||||
return { display: 'none' as const, height: 0, overflow: 'hidden' as const };
|
return { display: 'none' as const, height: 0, overflow: 'hidden' as const };
|
||||||
@@ -116,6 +129,9 @@ export default function TabsLayout() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
listeners={{
|
||||||
|
tabPress: handleAppsTabPress,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="messages"
|
name="messages"
|
||||||
@@ -143,6 +159,9 @@ export default function TabsLayout() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
listeners={{
|
||||||
|
tabPress: handleProfileTabPress,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ const PostCardInner: React.FC<PostCardProps> = (normalizedProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<View style={styles.gridNoImagePreview}>
|
<View style={styles.gridNoImagePreview}>
|
||||||
<Text style={styles.gridNoImageText} numberOfLines={6}>
|
<Text style={styles.gridNoImageText} numberOfLines={6}>
|
||||||
{highlightKeyword ? <HighlightText text={contentPreview} keyword={highlightKeyword} style={styles.highlight} /> : contentPreview}
|
{highlightKeyword ? <HighlightText text={contentPreview} keyword={highlightKeyword} highlightStyle={styles.highlight} /> : contentPreview}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -676,7 +676,7 @@ const PostCardInner: React.FC<PostCardProps> = (normalizedProps) => {
|
|||||||
|
|
||||||
{!!post.title && (
|
{!!post.title && (
|
||||||
<Text style={styles.gridTitleMain} numberOfLines={hasImage ? 2 : 3}>
|
<Text style={styles.gridTitleMain} numberOfLines={hasImage ? 2 : 3}>
|
||||||
{highlightKeyword ? <HighlightText text={post.title} keyword={highlightKeyword} style={styles.highlight} /> : post.title}
|
{highlightKeyword ? <HighlightText text={post.title} keyword={highlightKeyword} highlightStyle={styles.highlight} /> : post.title}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -748,7 +748,7 @@ const PostCardInner: React.FC<PostCardProps> = (normalizedProps) => {
|
|||||||
|
|
||||||
{!!post.title && (
|
{!!post.title && (
|
||||||
<Text style={styles.title} numberOfLines={isCompact ? 2 : undefined}>
|
<Text style={styles.title} numberOfLines={isCompact ? 2 : undefined}>
|
||||||
{highlightKeyword ? <HighlightText text={post.title} keyword={highlightKeyword} style={styles.highlight} /> : post.title}
|
{highlightKeyword ? <HighlightText text={post.title} keyword={highlightKeyword} highlightStyle={styles.highlight} /> : post.title}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ const PostCardInner: React.FC<PostCardProps> = (normalizedProps) => {
|
|||||||
style={styles.content}
|
style={styles.content}
|
||||||
numberOfLines={isExpanded ? undefined : contentNumberOfLines}
|
numberOfLines={isExpanded ? undefined : contentNumberOfLines}
|
||||||
>
|
>
|
||||||
{highlightKeyword ? <HighlightText text={content} keyword={highlightKeyword} style={styles.highlight} /> : content}
|
{highlightKeyword ? <HighlightText text={content} keyword={highlightKeyword} highlightStyle={styles.highlight} /> : content}
|
||||||
</Text>
|
</Text>
|
||||||
{shouldTruncate && (
|
{shouldTruncate && (
|
||||||
<TouchableOpacity onPress={() => setIsExpanded((prev) => !prev)} style={styles.expandBtn}>
|
<TouchableOpacity onPress={() => setIsExpanded((prev) => !prev)} style={styles.expandBtn}>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const PostContentRenderer: React.FC<PostContentRendererProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Text numberOfLines={numberOfLines} selectable style={textStyle}>
|
<Text numberOfLines={numberOfLines} selectable style={textStyle}>
|
||||||
{highlightKeyword && content ? (
|
{highlightKeyword && content ? (
|
||||||
<HighlightText text={content} keyword={highlightKeyword} style={highlightStyle} />
|
<HighlightText text={content} keyword={highlightKeyword} highlightStyle={highlightStyle} />
|
||||||
) : (
|
) : (
|
||||||
content || ''
|
content || ''
|
||||||
)}
|
)}
|
||||||
@@ -85,7 +85,7 @@ const PostContentRenderer: React.FC<PostContentRendererProps> = ({
|
|||||||
elements.push(
|
elements.push(
|
||||||
<Text key={key} selectable style={textStyle}>
|
<Text key={key} selectable style={textStyle}>
|
||||||
{highlightKeyword ? (
|
{highlightKeyword ? (
|
||||||
<HighlightText text={text} keyword={highlightKeyword} style={highlightStyle} />
|
<HighlightText text={text} keyword={highlightKeyword} highlightStyle={highlightStyle} />
|
||||||
) : (
|
) : (
|
||||||
text
|
text
|
||||||
)}
|
)}
|
||||||
@@ -125,7 +125,7 @@ const PostContentRenderer: React.FC<PostContentRendererProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Text numberOfLines={numberOfLines} selectable style={textStyle}>
|
<Text numberOfLines={numberOfLines} selectable style={textStyle}>
|
||||||
{highlightKeyword && content ? (
|
{highlightKeyword && content ? (
|
||||||
<HighlightText text={content} keyword={highlightKeyword} style={highlightStyle} />
|
<HighlightText text={content} keyword={highlightKeyword} highlightStyle={highlightStyle} />
|
||||||
) : (
|
) : (
|
||||||
content || ''
|
content || ''
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -551,7 +551,6 @@ function createUserProfileHeaderStyles(colors: AppColors) {
|
|||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
backgroundColor: colors.background.paper,
|
backgroundColor: colors.background.paper,
|
||||||
borderRadius: borderRadius.lg,
|
borderRadius: borderRadius.lg,
|
||||||
...shadows.lg,
|
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: colors.divider,
|
borderColor: colors.divider,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ export const HomeScreen: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (homeTabPressCount > 0) {
|
if (homeTabPressCount > 0) {
|
||||||
// 滚动 FlashList 到顶部
|
// 滚动 FlashList 到顶部
|
||||||
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: false });
|
||||||
// 滚动 ScrollView 到顶部(网格模式)
|
// 滚动 ScrollView 到顶部(网格模式)
|
||||||
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
||||||
// 重置底部 Tab 栏状态
|
// 重置底部 Tab 栏状态
|
||||||
|
|||||||
@@ -208,11 +208,6 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
|||||||
backgroundColor: 'rgba(255, 255, 255, 0.96)',
|
backgroundColor: 'rgba(255, 255, 255, 0.96)',
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: 'rgba(0, 0, 0, 0.08)',
|
borderColor: 'rgba(0, 0, 0, 0.08)',
|
||||||
shadowColor: colors.chat.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.12,
|
|
||||||
shadowRadius: 6,
|
|
||||||
elevation: 4,
|
|
||||||
},
|
},
|
||||||
jumpToLatestFabText: {
|
jumpToLatestFabText: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|||||||
@@ -1104,7 +1104,10 @@ export const useChatScreen = (props?: ChatScreenProps) => {
|
|||||||
setSending(true);
|
setSending(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const segments: MessageSegment[] = [...buildTextSegments(trimmedText, replyingTo)];
|
// 有文本或回复时才构建文本段,纯图片时不塞空 text
|
||||||
|
const segments: MessageSegment[] = (trimmedText || replyingTo)
|
||||||
|
? [...buildTextSegments(trimmedText, replyingTo)]
|
||||||
|
: [];
|
||||||
for (const url of uploadedUrls) {
|
for (const url of uploadedUrls) {
|
||||||
segments.push({
|
segments.push({
|
||||||
type: 'image',
|
type: 'image',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const UserScreen: React.FC = () => {
|
|||||||
<UserProfileScreen
|
<UserProfileScreen
|
||||||
mode={isSelfProfile ? 'self' : 'other'}
|
mode={isSelfProfile ? 'self' : 'other'}
|
||||||
userId={isSelfProfile ? undefined : userId}
|
userId={isSelfProfile ? undefined : userId}
|
||||||
hasHeader={false}
|
hasHeader
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user