Files
frontend/src/screens/create/composers/ChannelPicker.tsx
lafay 45df579b72
Some checks failed
Frontend CI / ota (android) (push) Successful in 2m56s
Frontend CI / ota (ios) (push) Failing after 2m23s
Frontend CI / build-and-push-web (push) Failing after 1m31s
Frontend CI / build-android-apk (push) Successful in 47m22s
feat(messaging): implement three-tier reply message lazy loading with offline fallback
Add lazy loading pipeline for reply message previews that checks memory → SQLite → server in order, enabling offline hit and graceful degradation for deleted/inaccessible messages. Includes `getReplyMessage` API endpoint and `jumpToMessageSeq` helper for navigation to referenced messages.

feat(create): extract MomentComposer and LongPostComposer for dual posting modes

Refactor CreatePostScreen shell to delegate content editing to specialized composers based on postMode ('moment' or 'long'), enabling long posts with voting support. Add expandable FAB with mode selection on HomeScreen.

fix(navigation): use navigate instead of push to prevent duplicate chat instances

Replace router.push with router.navigate in notification bootstrap to avoid creating duplicate chat instances when already on the chat screen.

fix(ui): update author badge styling with proper text contrast

Change author badge background to use hex transparency and add dedicated text color for better readability on both light and dark themes.

chore: normalize filename handling in file uploads and improve file segment rendering

Use asset.name as authoritative filename, remove redundant shadows from file cards inside bubbles.
2026-07-02 23:26:11 +08:00

140 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 频道选择(瞬间 / 长文共用)
* 包含「发表至」入口行 + 展开后的频道单选面板。
*/
import React from 'react';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Text } from '../../../components/common';
import { spacing, fontSizes, borderRadius, useAppColors, type AppColors } from '../../../theme';
export type ChannelOption = { id: string; name: string };
interface ChannelPickerProps {
channels: ChannelOption[];
selectedId: string | null;
onSelect: (channelId: string) => void;
expanded: boolean;
onToggle: () => void;
}
export function ChannelPicker({
channels,
selectedId,
onSelect,
expanded,
onToggle,
}: ChannelPickerProps) {
const colors = useAppColors();
const styles = React.useMemo(() => createChannelPickerStyles(colors), [colors]);
const selectedName = React.useMemo(() => {
if (!selectedId) return '';
return channels.find(c => c.id === selectedId)?.name || '';
}, [selectedId, channels]);
return (
<>
{/* 入口行 */}
<TouchableOpacity style={styles.channelEntryRow} onPress={onToggle} activeOpacity={0.8}>
<Text variant="body" color={colors.text.secondary} style={styles.channelEntryLabel}>
</Text>
<View style={styles.channelEntryValueWrap}>
<Text
variant="body"
color={selectedName ? colors.text.primary : colors.text.hint}
style={styles.channelEntryValue}
>
{selectedName || '选择频道'}
</Text>
<MaterialCommunityIcons name="chevron-right" size={18} color={colors.text.hint} />
</View>
</TouchableOpacity>
{/* 展开面板 */}
{expanded && (
<View style={styles.tagsSection}>
<View style={styles.tagsContainer}>
{channels.map((channel) => {
const isSelected = selectedId === channel.id;
return (
<TouchableOpacity
key={channel.id}
style={[styles.tag, isSelected && styles.channelSelected]}
onPress={() => onSelect(channel.id)}
>
<Text
variant="caption"
color={isSelected ? colors.primary.contrast : colors.text.secondary}
style={styles.tagText}
>
{channel.name}
</Text>
</TouchableOpacity>
);
})}
</View>
{channels.length === 0 && (
<Text variant="caption" color={colors.text.hint}></Text>
)}
</View>
)}
</>
);
}
function createChannelPickerStyles(colors: AppColors) {
return StyleSheet.create({
channelEntryRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.divider,
backgroundColor: colors.background.paper,
},
channelEntryLabel: {
fontSize: fontSizes.md,
},
channelEntryValueWrap: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
channelEntryValue: {
fontSize: fontSizes.md,
},
tagsSection: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.md,
paddingBottom: spacing.sm,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.divider,
backgroundColor: colors.background.paper,
},
tagsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
},
tag: {
backgroundColor: colors.background.default,
borderRadius: borderRadius.full,
paddingHorizontal: spacing.md,
paddingVertical: spacing.xs,
borderWidth: 1,
borderColor: colors.divider,
},
channelSelected: {
backgroundColor: colors.primary.main,
borderColor: colors.primary.main,
},
tagText: {
fontWeight: '500',
},
});
}