373 lines
9.9 KiB
TypeScript
373 lines
9.9 KiB
TypeScript
import { ChapterContent } from '@/components/memoir/ChapterContent';
|
|
import { ChapterItem } from '@/components/memoir/ChapterItem';
|
|
import { FullMemoirContent } from '@/components/memoir/FullMemoirContent';
|
|
import { AppColors } from '@/constants/theme';
|
|
import { Chapter, mockMemoir } from '@/data/mockData';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import React, { useEffect, useState } from 'react';
|
|
import {
|
|
Dimensions,
|
|
Modal,
|
|
Platform,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View,
|
|
} from 'react-native';
|
|
import Animated, {
|
|
Easing,
|
|
useAnimatedStyle,
|
|
useSharedValue,
|
|
withTiming,
|
|
} from 'react-native-reanimated';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
|
|
type ViewType = 'toc' | 'chapter' | 'fullRead';
|
|
|
|
export default function MemoirScreen() {
|
|
const [currentView, setCurrentView] = useState<ViewType>('toc');
|
|
const [selectedChapter, setSelectedChapter] = useState<Chapter | null>(null);
|
|
const [showMoreMenu, setShowMoreMenu] = useState(false);
|
|
|
|
// Animation values
|
|
const tocTranslateX = useSharedValue(0);
|
|
const readingTranslateX = useSharedValue(SCREEN_WIDTH);
|
|
|
|
const isReading = currentView === 'chapter' || currentView === 'fullRead';
|
|
|
|
useEffect(() => {
|
|
if (isReading) {
|
|
// Slide reading view in from right
|
|
tocTranslateX.value = withTiming(-SCREEN_WIDTH * 0.3, {
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
});
|
|
readingTranslateX.value = withTiming(0, {
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
});
|
|
} else {
|
|
// Slide reading view out to right
|
|
tocTranslateX.value = withTiming(0, {
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
});
|
|
readingTranslateX.value = withTiming(SCREEN_WIDTH, {
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
});
|
|
}
|
|
}, [isReading]);
|
|
|
|
const tocAnimatedStyle = useAnimatedStyle(() => ({
|
|
transform: [{ translateX: tocTranslateX.value }],
|
|
}));
|
|
|
|
const readingAnimatedStyle = useAnimatedStyle(() => ({
|
|
transform: [{ translateX: readingTranslateX.value }],
|
|
}));
|
|
|
|
const handleChapterPress = (chapter: Chapter) => {
|
|
setSelectedChapter(chapter);
|
|
setCurrentView('chapter');
|
|
};
|
|
|
|
const handleBackToToc = () => {
|
|
setCurrentView('toc');
|
|
setTimeout(() => {
|
|
setSelectedChapter(null);
|
|
}, 300);
|
|
};
|
|
|
|
const handleReadAll = () => {
|
|
setCurrentView('fullRead');
|
|
setSelectedChapter(null);
|
|
};
|
|
|
|
const handleExportPdf = () => {
|
|
setShowMoreMenu(false);
|
|
console.log('Export PDF pressed');
|
|
};
|
|
|
|
const handleShare = () => {
|
|
setShowMoreMenu(false);
|
|
console.log('Share pressed');
|
|
};
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<View style={styles.contentWrapper}>
|
|
{/* TOC View */}
|
|
<Animated.View style={[styles.tocView, tocAnimatedStyle]}>
|
|
<View style={styles.purpleHeader}>
|
|
<Text style={styles.purpleHeaderTitle}>回忆录</Text>
|
|
</View>
|
|
<ScrollView
|
|
style={styles.tocContainer}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={styles.tocContent}
|
|
>
|
|
{/* Book Info */}
|
|
<View style={styles.bookHeader}>
|
|
<Text style={styles.bookTitle}>{mockMemoir.title}</Text>
|
|
<Text style={styles.bookSubtitle}>{mockMemoir.subtitle}</Text>
|
|
<Text style={styles.bookUpdate}>更新于 {mockMemoir.updatedAt}</Text>
|
|
</View>
|
|
|
|
{/* Chapter List */}
|
|
{mockMemoir.chapters.map((chapter) => (
|
|
<ChapterItem
|
|
key={chapter.id}
|
|
chapter={chapter}
|
|
onPress={() => handleChapterPress(chapter)}
|
|
/>
|
|
))}
|
|
</ScrollView>
|
|
|
|
{/* Read All Button */}
|
|
{mockMemoir.chapters.length > 0 && (
|
|
<View style={styles.floatingActions}>
|
|
<TouchableOpacity
|
|
style={styles.readAllButton}
|
|
onPress={handleReadAll}
|
|
activeOpacity={0.8}
|
|
>
|
|
<Ionicons name="book" size={18} color={AppColors.white} />
|
|
<Text style={styles.readAllButtonText}>阅读全文</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
</Animated.View>
|
|
|
|
{/* Reading View */}
|
|
<Animated.View style={[styles.readingView, readingAnimatedStyle]}>
|
|
<View style={styles.readingHeader}>
|
|
<TouchableOpacity
|
|
style={styles.backButton}
|
|
onPress={handleBackToToc}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="chevron-back" size={24} color={AppColors.white} />
|
|
<Text style={styles.backButtonText}>返回目录</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={styles.moreButton}
|
|
onPress={() => setShowMoreMenu(true)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="ellipsis-horizontal" size={22} color={AppColors.white} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
<View style={styles.readingContainer}>
|
|
{currentView === 'fullRead' ? (
|
|
<FullMemoirContent memoir={mockMemoir} />
|
|
) : selectedChapter ? (
|
|
<ChapterContent chapter={selectedChapter} />
|
|
) : null}
|
|
</View>
|
|
</Animated.View>
|
|
</View>
|
|
|
|
{/* More Menu Modal */}
|
|
<Modal
|
|
visible={showMoreMenu}
|
|
transparent
|
|
animationType="fade"
|
|
onRequestClose={() => setShowMoreMenu(false)}
|
|
>
|
|
<Pressable
|
|
style={styles.modalOverlay}
|
|
onPress={() => setShowMoreMenu(false)}
|
|
>
|
|
<View style={styles.menuContainer}>
|
|
<TouchableOpacity
|
|
style={styles.menuItem}
|
|
onPress={handleExportPdf}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="download-outline" size={20} color={AppColors.deepPurple} />
|
|
<Text style={styles.menuItemText}>导出 PDF</Text>
|
|
</TouchableOpacity>
|
|
<View style={styles.menuDivider} />
|
|
<TouchableOpacity
|
|
style={styles.menuItem}
|
|
onPress={handleShare}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Ionicons name="share-social-outline" size={20} color={AppColors.deepPurple} />
|
|
<Text style={styles.menuItemText}>分享</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</Pressable>
|
|
</Modal>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: AppColors.mediumPurple,
|
|
},
|
|
contentWrapper: {
|
|
flex: 1,
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
},
|
|
tocView: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
},
|
|
readingView: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: AppColors.mediumPurple,
|
|
},
|
|
purpleHeader: {
|
|
height: 62,
|
|
paddingHorizontal: 20,
|
|
justifyContent: 'center',
|
|
backgroundColor: AppColors.mediumPurple,
|
|
},
|
|
purpleHeaderTitle: {
|
|
fontSize: 32,
|
|
fontWeight: '600',
|
|
color: AppColors.white,
|
|
},
|
|
readingHeader: {
|
|
height: 62,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: AppColors.mediumPurple,
|
|
paddingHorizontal: 16,
|
|
},
|
|
backButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
backButtonText: {
|
|
fontSize: 16,
|
|
color: AppColors.white,
|
|
marginLeft: 4,
|
|
},
|
|
moreButton: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 20,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
tocContainer: {
|
|
flex: 1,
|
|
backgroundColor: AppColors.cream,
|
|
},
|
|
tocContent: {
|
|
padding: 20,
|
|
paddingBottom: 120,
|
|
},
|
|
bookHeader: {
|
|
alignItems: 'center',
|
|
marginBottom: 24,
|
|
paddingVertical: 20,
|
|
backgroundColor: AppColors.white,
|
|
borderRadius: 16,
|
|
shadowColor: AppColors.deepPurple,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.06,
|
|
shadowRadius: 12,
|
|
elevation: 2,
|
|
},
|
|
bookTitle: {
|
|
fontSize: 24,
|
|
fontWeight: '600',
|
|
color: AppColors.deepPurple,
|
|
marginBottom: 6,
|
|
},
|
|
bookSubtitle: {
|
|
fontSize: 13,
|
|
color: AppColors.slatePurple,
|
|
},
|
|
bookUpdate: {
|
|
fontSize: 12,
|
|
color: AppColors.mediumPurple,
|
|
marginTop: 6,
|
|
},
|
|
readingContainer: {
|
|
flex: 1,
|
|
backgroundColor: AppColors.cream,
|
|
},
|
|
floatingActions: {
|
|
position: 'absolute',
|
|
bottom: Platform.OS === 'ios' ? 100 : 80,
|
|
left: 0,
|
|
right: 0,
|
|
alignItems: 'center',
|
|
paddingHorizontal: 20,
|
|
},
|
|
readAllButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 14,
|
|
paddingHorizontal: 32,
|
|
borderRadius: 25,
|
|
backgroundColor: AppColors.mediumPurple,
|
|
gap: 8,
|
|
shadowColor: AppColors.deepPurple,
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 12,
|
|
elevation: 4,
|
|
},
|
|
readAllButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: AppColors.white,
|
|
},
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
justifyContent: 'flex-start',
|
|
alignItems: 'flex-end',
|
|
paddingTop: Platform.OS === 'ios' ? 110 : 90,
|
|
paddingRight: 20,
|
|
},
|
|
menuContainer: {
|
|
backgroundColor: AppColors.white,
|
|
borderRadius: 12,
|
|
paddingVertical: 8,
|
|
minWidth: 160,
|
|
shadowColor: AppColors.deepPurple,
|
|
shadowOffset: { width: 0, height: 4 },
|
|
shadowOpacity: 0.15,
|
|
shadowRadius: 16,
|
|
elevation: 8,
|
|
},
|
|
menuItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 16,
|
|
gap: 12,
|
|
},
|
|
menuItemText: {
|
|
fontSize: 15,
|
|
color: AppColors.deepPurple,
|
|
},
|
|
menuDivider: {
|
|
height: 1,
|
|
backgroundColor: 'rgba(32, 0, 40, 0.08)',
|
|
marginHorizontal: 16,
|
|
},
|
|
});
|