add ios app

This commit is contained in:
penghanyuan
2026-01-31 21:20:50 +01:00
parent a170632270
commit 748f252c2f
63 changed files with 38507 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { AppColors } from '@/constants/theme';
import { Chapter } from '@/data/mockData';
interface ChapterContentProps {
chapter: Chapter;
}
export function ChapterContent({ chapter }: ChapterContentProps) {
// Parse content to handle quotes
const renderContent = () => {
if (!chapter.content) {
return (
<Text style={styles.emptyText}>
</Text>
);
}
const parts = chapter.content.split('"');
return parts.map((part, index) => {
// Check if this part is a quote (starts and ends with quote marks in original)
if (part.startsWith('"') || (index > 0 && parts[index - 1].endsWith('"'))) {
return null;
}
// Check for quote pattern
const quoteMatch = chapter.content?.match(/"([^"]+)"/g);
const isQuote = quoteMatch && quoteMatch.some(q => q.includes(part) && part.length > 10);
if (part.trim().startsWith('"') && part.trim().endsWith('"')) {
return (
<View key={index} style={styles.quoteBlock}>
<Text style={styles.quoteText}>{part}</Text>
</View>
);
}
// Split by newlines and render paragraphs
return part.split('\n\n').map((paragraph, pIndex) => {
if (!paragraph.trim()) return null;
// Check if it's a quote
if (paragraph.trim().startsWith('"') && paragraph.trim().endsWith('"')) {
return (
<View key={`${index}-${pIndex}`} style={styles.quoteBlock}>
<Text style={styles.quoteText}>{paragraph.trim()}</Text>
</View>
);
}
return (
<Text key={`${index}-${pIndex}`} style={styles.paragraph}>
{paragraph.trim()}
</Text>
);
});
});
};
return (
<View style={styles.container}>
{/* Chapter Header */}
<View style={styles.header}>
<Text style={styles.chapterNumber}>
{['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][chapter.number - 1] || chapter.number}
</Text>
<Text style={styles.chapterTitle}>{chapter.title}</Text>
</View>
{/* Content */}
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.contentContainer}
>
{renderContent()}
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
header: {
marginBottom: 24,
},
chapterNumber: {
fontSize: 13,
color: AppColors.mediumPurple,
fontWeight: '500',
marginBottom: 4,
},
chapterTitle: {
fontSize: 26,
fontWeight: '600',
color: AppColors.deepPurple,
},
content: {
flex: 1,
},
contentContainer: {
paddingBottom: 100,
},
paragraph: {
fontSize: 16,
lineHeight: 30,
color: AppColors.deepPurple,
marginBottom: 20,
textAlign: 'justify',
},
quoteBlock: {
marginVertical: 24,
paddingVertical: 16,
paddingHorizontal: 20,
backgroundColor: AppColors.lavender,
borderLeftWidth: 3,
borderLeftColor: AppColors.mediumPurple,
borderTopRightRadius: 12,
borderBottomRightRadius: 12,
},
quoteText: {
fontSize: 15,
fontStyle: 'italic',
color: AppColors.deepPurple,
lineHeight: 24,
},
emptyText: {
fontSize: 15,
color: AppColors.slatePurple,
textAlign: 'center',
marginTop: 40,
},
});

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { AppColors } from '@/constants/theme';
import { Chapter } from '@/data/mockData';
interface ChapterItemProps {
chapter: Chapter;
onPress: () => void;
}
export function ChapterItem({ chapter, onPress }: ChapterItemProps) {
const getStatusText = () => {
switch (chapter.status) {
case 'complete':
return `已整理 · 约${chapter.pageCount}`;
case 'partial':
return `部分整理 · 约${chapter.pageCount}`;
case 'pending':
return '待补充';
}
};
const isComplete = chapter.status === 'complete';
return (
<TouchableOpacity
style={styles.container}
onPress={onPress}
activeOpacity={0.7}
>
<View style={styles.number}>
<Text style={styles.numberText}>
{chapter.number.toString().padStart(2, '0')}
</Text>
</View>
<View style={styles.content}>
<Text style={styles.title}>{chapter.title}</Text>
<Text style={[styles.status, isComplete && styles.statusComplete]}>
{getStatusText()}
</Text>
</View>
<Ionicons
name="chevron-forward"
size={20}
color={AppColors.slatePurple}
/>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: AppColors.white,
borderRadius: 14,
marginBottom: 12,
shadowColor: AppColors.deepPurple,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.04,
shadowRadius: 8,
elevation: 1,
},
number: {
width: 32,
height: 32,
borderRadius: 10,
backgroundColor: AppColors.lavender,
alignItems: 'center',
justifyContent: 'center',
marginRight: 14,
},
numberText: {
fontSize: 14,
fontWeight: '600',
color: AppColors.deepPurple,
},
content: {
flex: 1,
},
title: {
fontSize: 15,
fontWeight: '500',
color: AppColors.deepPurple,
marginBottom: 2,
},
status: {
fontSize: 12,
color: AppColors.slatePurple,
},
statusComplete: {
color: AppColors.mediumPurple,
},
});

View File

@@ -0,0 +1,196 @@
import { AppColors } from '@/constants/theme';
import { Memoir } from '@/data/mockData';
import { Ionicons } from '@expo/vector-icons';
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
interface FullMemoirContentProps {
memoir: Memoir;
}
const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
function renderChapterContent(content: string | undefined) {
if (!content) {
return (
<Text style={styles.emptyText}>
</Text>
);
}
return content.split('\n\n').map((paragraph, index) => {
if (!paragraph.trim()) return null;
// Check if it's a quote
if (paragraph.trim().startsWith('"') && paragraph.trim().endsWith('"')) {
return (
<View key={index} style={styles.quoteBlock}>
<Text style={styles.quoteText}>{paragraph.trim()}</Text>
</View>
);
}
return (
<Text key={index} style={styles.paragraph}>
{paragraph.trim()}
</Text>
);
});
}
export function FullMemoirContent({ memoir }: FullMemoirContentProps) {
return (
<View style={styles.container}>
{/* Content */}
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.contentContainer}
>
{/* Book Title */}
<View style={styles.bookHeader}>
<Text style={styles.bookTitle}>{memoir.title}</Text>
<Text style={styles.bookSubtitle}>{memoir.subtitle}</Text>
</View>
{/* All Chapters */}
{memoir.chapters.map((chapter, index) => (
<View key={chapter.id} style={styles.chapterSection}>
{/* Chapter Header */}
<View style={styles.chapterHeader}>
<Text style={styles.chapterNumber}>
{chineseNumbers[chapter.number - 1] || chapter.number}
</Text>
<Text style={styles.chapterTitle}>{chapter.title}</Text>
</View>
{/* Chapter Content */}
<View style={styles.chapterContent}>
{renderChapterContent(chapter.content)}
</View>
{/* Chapter Divider (except for last chapter) */}
{index < memoir.chapters.length - 1 && (
<View style={styles.chapterDivider}>
<View style={styles.dividerLine} />
<Ionicons name="leaf-outline" size={16} color={AppColors.lavender} />
<View style={styles.dividerLine} />
</View>
)}
</View>
))}
{/* End Mark */}
<View style={styles.endMark}>
<Text style={styles.endMarkText}> </Text>
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
content: {
flex: 1,
},
contentContainer: {
paddingBottom: 100,
},
bookHeader: {
alignItems: 'center',
marginBottom: 40,
paddingBottom: 32,
borderBottomWidth: 1,
borderBottomColor: 'rgba(32, 0, 40, 0.08)',
},
bookTitle: {
fontSize: 28,
fontWeight: '600',
color: AppColors.deepPurple,
marginBottom: 8,
textAlign: 'center',
},
bookSubtitle: {
fontSize: 14,
color: AppColors.slatePurple,
textAlign: 'center',
},
chapterSection: {
marginBottom: 16,
},
chapterHeader: {
marginBottom: 20,
},
chapterNumber: {
fontSize: 13,
color: AppColors.mediumPurple,
fontWeight: '500',
marginBottom: 4,
},
chapterTitle: {
fontSize: 22,
fontWeight: '600',
color: AppColors.deepPurple,
},
chapterContent: {
marginBottom: 24,
},
paragraph: {
fontSize: 16,
lineHeight: 30,
color: AppColors.deepPurple,
marginBottom: 20,
textAlign: 'justify',
},
quoteBlock: {
marginVertical: 24,
paddingVertical: 16,
paddingHorizontal: 20,
backgroundColor: AppColors.lavender,
borderLeftWidth: 3,
borderLeftColor: AppColors.mediumPurple,
borderTopRightRadius: 12,
borderBottomRightRadius: 12,
},
quoteText: {
fontSize: 15,
fontStyle: 'italic',
color: AppColors.deepPurple,
lineHeight: 24,
},
emptyText: {
fontSize: 15,
color: AppColors.slatePurple,
fontStyle: 'italic',
},
chapterDivider: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginVertical: 32,
gap: 12,
},
dividerLine: {
flex: 1,
height: 1,
backgroundColor: 'rgba(32, 0, 40, 0.08)',
maxWidth: 80,
},
endMark: {
alignItems: 'center',
marginTop: 40,
paddingTop: 32,
borderTopWidth: 1,
borderTopColor: 'rgba(32, 0, 40, 0.08)',
},
endMarkText: {
fontSize: 14,
color: AppColors.slatePurple,
letterSpacing: 2,
},
});