feat: 章节软删除、对话左滑删除,移除已读状态

- 章节:详情页增加删除按钮,软删除(is_active=False),AI 不再修改但保留供参考
- 章节:get_chapter 增加 is_active 校验,已删除章节返回 404
- 章节:AI 生成时参考同类别已删除章节摘要
- 对话:左滑显示删除,调用 hard delete API,删除前二次确认
- 对话:根布局包裹 GestureHandlerRootView 以支持 Swipeable
- 对话:移除已读/未读状态展示及相关 i18n
This commit is contained in:
Kevin
2026-03-19 10:44:35 +08:00
parent 1aa3d8593c
commit 9a1d31c71f
12 changed files with 223 additions and 80 deletions

View File

@@ -1,10 +1,11 @@
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { useLocalSearchParams } from 'expo-router';
import { Settings, X } from 'lucide-react-native';
import { router, useLocalSearchParams } from 'expo-router';
import { Settings, Trash2, X } from 'lucide-react-native';
import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Modal,
Platform,
Pressable,
@@ -20,7 +21,7 @@ import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { ScreenHeader } from '@/components/screen-header';
import { ScreenGutter } from '@/constants/layout';
import { useChapterDetail } from '@/features/memoir/hooks';
import { useChapterDetail, useDeleteChapter } from '@/features/memoir/hooks';
// Life-Echo reading colors (from HTML reference)
const READING_COLORS = {
@@ -536,6 +537,7 @@ export default function ChapterScreen() {
const insets = useSafeAreaInsets();
const { t } = useTranslation('memoir');
const { data: chapter, isLoading } = useChapterDetail(id ?? '');
const deleteChapter = useDeleteChapter();
const [settingsVisible, setSettingsVisible] = useState(false);
const [fontSize, setFontSize] = useState<FontSize>('default');
@@ -568,6 +570,7 @@ export default function ChapterScreen() {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: bgColor,
gap: 16,
}}
>
<Text
@@ -576,6 +579,25 @@ export default function ChapterScreen() {
>
{t('chapterReading.chapterNotFound')}
</Text>
<Pressable
onPress={() => router.back()}
style={({ pressed }) => ({
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 12,
backgroundColor: READING_COLORS.primary,
opacity: pressed ? 0.8 : 1,
})}
accessibilityRole="button"
accessibilityLabel={t('chapterReading.back')}
>
<Text
style={{ color: '#fff', fontSize: 16, fontWeight: '600' }}
selectable={false}
>
{t('chapterReading.back')}
</Text>
</Pressable>
</View>
);
}
@@ -583,6 +605,25 @@ export default function ChapterScreen() {
const sections = chapter.sections ?? [];
const coverImageUrl = chapter.cover_image?.url ?? null;
const handleDeletePress = () => {
Alert.alert(
t('chapterReading.deleteChapter'),
t('chapterReading.confirmDeleteMessage'),
[
{ text: t('chapterReading.cancel'), style: 'cancel' },
{
text: t('chapterReading.deleteChapterAction'),
style: 'destructive',
onPress: () => {
deleteChapter.mutate(chapter.id, {
onSuccess: () => router.back(),
});
},
},
],
);
};
return (
<View style={{ flex: 1, backgroundColor: bgColor }}>
<ScreenHeader
@@ -592,19 +633,38 @@ export default function ChapterScreen() {
title={chapter.title}
backAccessibilityLabel={t('chapterReading.back')}
right={
<Pressable
onPress={() => setSettingsVisible(true)}
style={({ pressed }) => ({
padding: 8,
marginRight: -8,
borderRadius: 9999,
opacity: pressed ? 0.7 : 1,
})}
accessibilityLabel={t('chapterReading.settings')}
accessibilityRole="button"
>
<Icon as={Settings} size={24} color={READING_COLORS.primary} />
</Pressable>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
<Pressable
onPress={handleDeletePress}
disabled={deleteChapter.isPending}
style={({ pressed }) => ({
padding: 8,
borderRadius: 9999,
opacity: pressed ? 0.7 : 1,
})}
accessibilityLabel={t('chapterReading.deleteChapter')}
accessibilityRole="button"
>
<Icon
as={Trash2}
size={22}
color={READING_COLORS.onSurfaceVariant}
/>
</Pressable>
<Pressable
onPress={() => setSettingsVisible(true)}
style={({ pressed }) => ({
padding: 8,
marginRight: -8,
borderRadius: 9999,
opacity: pressed ? 0.7 : 1,
})}
accessibilityLabel={t('chapterReading.settings')}
accessibilityRole="button"
>
<Icon as={Settings} size={24} color={READING_COLORS.primary} />
</Pressable>
</View>
}
/>