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,73 @@
import { Tabs } from 'expo-router';
import React from 'react';
import { Platform } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { HapticTab } from '@/components/haptic-tab';
import { AppColors } from '@/constants/theme';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: AppColors.mediumPurple,
tabBarInactiveTintColor: AppColors.slatePurple,
headerShown: false,
tabBarButton: HapticTab,
tabBarStyle: {
backgroundColor: AppColors.white,
borderTopColor: 'rgba(32, 0, 40, 0.08)',
borderTopWidth: 1,
height: Platform.OS === 'ios' ? 90 : 70,
paddingTop: 8,
paddingBottom: Platform.OS === 'ios' ? 30 : 10,
},
tabBarLabelStyle: {
fontSize: 11,
fontWeight: '500',
marginTop: 2,
},
}}>
<Tabs.Screen
name="index"
options={{
title: '聊天',
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? 'chatbubble' : 'chatbubble-outline'}
size={24}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="memoir"
options={{
title: '回忆录',
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? 'book' : 'book-outline'}
size={24}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: '我的',
tabBarIcon: ({ color, focused }) => (
<Ionicons
name={focused ? 'person' : 'person-outline'}
size={24}
color={color}
/>
),
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,126 @@
import { ConversationItem } from '@/components/chat/ConversationItem';
import { AppColors } from '@/constants/theme';
import { mockConversations } from '@/data/mockData';
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function ChatListScreen() {
const handleConversationPress = (conversationId: string) => {
router.push(`/chat/${conversationId}`);
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}></Text>
</View>
{/* Content */}
<ScrollView
style={styles.content}
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}
>
{/* Section Title */}
<Text style={styles.sectionTitle}></Text>
{/* Conversation List */}
{mockConversations.map((conversation) => (
<React.Fragment key={conversation.id}>
<ConversationItem
conversation={conversation}
onPress={() => handleConversationPress(conversation.id)}
/>
<View style={styles.divider} />
</React.Fragment>
))}
{/* Tip Card */}
<View style={styles.tipCard}>
<View style={styles.tipHeader}>
<Ionicons
name="information-circle-outline"
size={18}
color={AppColors.mediumPurple}
/>
<Text style={styles.tipTitle}></Text>
</View>
<Text style={styles.tipContent}>
AI
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.mediumPurple,
},
header: {
height: 62,
paddingHorizontal: 20,
justifyContent: 'center',
backgroundColor: AppColors.mediumPurple,
},
headerTitle: {
fontSize: 32,
fontWeight: '600',
color: AppColors.white,
},
content: {
flex: 1,
backgroundColor: AppColors.cream,
},
contentContainer: {
paddingBottom: 20,
},
sectionTitle: {
fontSize: 13,
color: AppColors.slatePurple,
fontWeight: '500',
letterSpacing: 0.5,
paddingHorizontal: 20,
paddingTop: 16,
paddingBottom: 10,
},
divider: {
height: 1,
backgroundColor: 'rgba(32, 0, 40, 0.06)',
marginHorizontal: 20,
},
tipCard: {
marginHorizontal: 20,
marginTop: 20,
padding: 16,
backgroundColor: AppColors.white,
borderRadius: 16,
shadowColor: AppColors.deepPurple,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.06,
shadowRadius: 12,
elevation: 2,
},
tipHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
tipTitle: {
fontSize: 14,
fontWeight: '500',
color: AppColors.deepPurple,
marginLeft: 8,
},
tipContent: {
fontSize: 13,
color: AppColors.slatePurple,
lineHeight: 21,
},
});

View File

@@ -0,0 +1,372 @@
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,
},
});

View File

@@ -0,0 +1,179 @@
import { SectionCard } from '@/components/profile/SectionCard';
import { SettingItem } from '@/components/profile/SettingItem';
import { AppColors } from '@/constants/theme';
import { mockUserProfile, settingsSections } from '@/data/mockData';
import { Ionicons } from '@expo/vector-icons';
import React, { useState } from 'react';
import { Alert, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function ProfileScreen() {
const [settings, setSettings] = useState({
largeFont: false,
darkMode: false,
reminder: true,
});
const handleToggle = (key: keyof typeof settings) => (value: boolean) => {
setSettings(prev => ({ ...prev, [key]: value }));
};
const showToast = (message: string) => {
Alert.alert('提示', message);
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.contentContainer}
>
{/* Profile Header */}
<View style={styles.profileHeader}>
<View style={styles.avatar}>
<Ionicons
name="person-outline"
size={36}
color={AppColors.deepPurple}
/>
</View>
<Text style={styles.profileName}>{mockUserProfile.nickname}</Text>
<View style={styles.planBadge}>
<View style={styles.planDot} />
<Text style={styles.planText}>{mockUserProfile.planLabel}</Text>
</View>
</View>
{/* Payment Section */}
<SectionCard title={settingsSections.payment.title}>
{settingsSections.payment.items.map((item, index) => (
<SettingItem
key={item.id}
icon={item.icon}
label={item.label}
description={item.description}
type={item.type}
isLast={index === settingsSections.payment.items.length - 1}
onPress={() => showToast('功能开发中...')}
/>
))}
</SectionCard>
{/* Privacy Section */}
{/* <SectionCard title={settingsSections.privacy.title}>
{settingsSections.privacy.items.map((item, index) => (
<SettingItem
key={item.id}
icon={item.icon}
label={item.label}
description={item.description}
type={item.type}
isLast={index === settingsSections.privacy.items.length - 1}
onPress={() => showToast('数据导出中...')}
/>
))}
</SectionCard> */}
{/* Settings Section */}
{/* <SectionCard title={settingsSections.settings.title}>
<SettingItem
icon="time-outline"
label="语速"
description="标准"
type="arrow"
onPress={() => showToast('功能开发中...')}
/>
<SettingItem
icon="text-outline"
label="大字模式"
type="toggle"
value={settings.largeFont}
onToggle={handleToggle('largeFont')}
/>
<SettingItem
icon="moon-outline"
label="夜间模式"
type="toggle"
value={settings.darkMode}
onToggle={handleToggle('darkMode')}
isLast
/>
</SectionCard> */}
{/* Help Section */}
<SectionCard title={settingsSections.help.title}>
{settingsSections.help.items.map((item, index) => (
<SettingItem
key={item.id}
icon={item.icon}
label={item.label}
type={item.type}
isLast={index === settingsSections.help.items.length - 1}
onPress={() => showToast('功能开发中...')}
/>
))}
</SectionCard>
{/* Version Info */}
<Text style={styles.version}> 1.0.0</Text>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.cream,
},
content: {
flex: 1,
},
contentContainer: {
padding: 20,
paddingTop: 28,
paddingBottom: 40,
},
profileHeader: {
alignItems: 'center',
paddingVertical: 20,
marginBottom: 4,
},
avatar: {
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: AppColors.lavender,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 12,
},
profileName: {
fontSize: 18,
fontWeight: '600',
color: AppColors.deepPurple,
marginBottom: 4,
},
planBadge: {
flexDirection: 'row',
alignItems: 'center',
},
planDot: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: AppColors.mediumPurple,
marginRight: 4,
},
planText: {
fontSize: 13,
color: AppColors.mediumPurple,
},
version: {
textAlign: 'center',
fontSize: 12,
color: AppColors.slatePurple,
marginTop: 20,
},
});

68
app-ios/app/_layout.tsx Normal file
View File

@@ -0,0 +1,68 @@
import { GluestackUIProvider } from '@gluestack-ui/themed';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { gluestackConfig, AppColors } from '@/constants/theme';
// Custom light theme with our colors
const CustomLightTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: AppColors.mediumPurple,
background: AppColors.cream,
card: AppColors.white,
text: AppColors.deepPurple,
border: AppColors.lavender,
notification: AppColors.mediumPurple,
},
};
// Custom dark theme with our colors
const CustomDarkTheme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: AppColors.lavender,
background: AppColors.deepPurple,
card: '#2D0036',
text: AppColors.white,
border: AppColors.slatePurple,
notification: AppColors.mediumPurple,
},
};
export const unstable_settings = {
anchor: '(tabs)',
};
export default function RootLayout() {
const colorScheme = useColorScheme();
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<GluestackUIProvider config={gluestackConfig}>
<ThemeProvider value={colorScheme === 'dark' ? CustomDarkTheme : CustomLightTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="chat/[id]"
options={{
headerShown: false,
animation: 'slide_from_right',
gestureEnabled: true,
gestureDirection: 'horizontal',
}}
/>
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
</Stack>
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
</ThemeProvider>
</GluestackUIProvider>
</GestureHandlerRootView>
);
}

186
app-ios/app/chat/[id].tsx Normal file
View File

@@ -0,0 +1,186 @@
import { ChatBubble, TimeSeparator, TypingIndicator } from '@/components/chat/ChatBubble';
import { ChatInput } from '@/components/chat/ChatInput';
import { AppColors } from '@/constants/theme';
import {
getCurrentTimeString,
getRandomAIResponse,
Message,
mockConversations,
mockMessages,
} from '@/data/mockData';
import { Ionicons } from '@expo/vector-icons';
import { router, useLocalSearchParams } from 'expo-router';
import React, { useEffect, useRef, useState } from 'react';
import {
KeyboardAvoidingView,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function ChatDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const scrollViewRef = useRef<ScrollView>(null);
const [messages, setMessages] = useState<Message[]>(mockMessages);
const [isTyping, setIsTyping] = useState(false);
// Find conversation info
const conversation = mockConversations.find(c => c.id === id) || mockConversations[0];
// Scroll to bottom when messages change
useEffect(() => {
setTimeout(() => {
scrollViewRef.current?.scrollToEnd({ animated: true });
}, 100);
}, [messages, isTyping]);
const handleSendMessage = (text: string) => {
// Add user message
const userMessage: Message = {
id: `m${Date.now()}`,
conversationId: id || '1',
senderType: 'user',
contentType: 'text',
content: text,
timestamp: getCurrentTimeString(),
};
setMessages(prev => [...prev, userMessage]);
// Simulate AI typing
setTimeout(() => {
setIsTyping(true);
}, 500);
// Add AI response after delay
setTimeout(() => {
setIsTyping(false);
const aiMessage: Message = {
id: `m${Date.now() + 1}`,
conversationId: id || '1',
senderType: 'ai',
contentType: 'text',
content: getRandomAIResponse(),
timestamp: getCurrentTimeString(),
};
setMessages(prev => [...prev, aiMessage]);
}, 1500 + Math.random() * 1000);
};
const handleBack = () => {
router.back();
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={handleBack}
activeOpacity={0.7}
>
<Ionicons name="chevron-back" size={24} color={AppColors.white} />
</TouchableOpacity>
<View style={styles.headerTitleArea}>
<Text style={styles.headerTitle}>{conversation.title}</Text>
<Text style={styles.headerStatus}>线</Text>
</View>
</View>
{/* Messages */}
<KeyboardAvoidingView
style={styles.messageContainer}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={0}
>
<ScrollView
ref={scrollViewRef}
style={styles.messageList}
contentContainerStyle={styles.messageListContent}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
<TimeSeparator time="今天 14:30" />
{messages.map((message) => (
<ChatBubble key={message.id} message={message} />
))}
{isTyping && <TypingIndicator />}
</ScrollView>
{/* Input Area */}
<ChatInput
onSendMessage={handleSendMessage}
onVoicePress={() => {
// Voice input placeholder
console.log('Voice input pressed');
}}
onEmojiPress={() => {
// Emoji picker placeholder
console.log('Emoji picker pressed');
}}
onMorePress={() => {
// More options placeholder
console.log('More options pressed');
}}
/>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.mediumPurple,
},
header: {
height: 62,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
backgroundColor: AppColors.mediumPurple,
},
backButton: {
width: 40,
height: 40,
borderRadius: 12,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
alignItems: 'center',
justifyContent: 'center',
},
headerTitleArea: {
flex: 1,
alignItems: 'center',
marginRight: 40, // Balance the back button
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: AppColors.white,
},
headerStatus: {
fontSize: 12,
color: 'rgba(255, 255, 255, 0.8)',
marginTop: 2,
},
messageContainer: {
flex: 1,
backgroundColor: AppColors.cream,
},
messageList: {
flex: 1,
backgroundColor: AppColors.cream,
},
messageListContent: {
paddingVertical: 16,
},
});

29
app-ios/app/modal.tsx Normal file
View File

@@ -0,0 +1,29 @@
import { Link } from 'expo-router';
import { StyleSheet } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
export default function ModalScreen() {
return (
<ThemedView style={styles.container}>
<ThemedText type="title">This is a modal</ThemedText>
<Link href="/" dismissTo style={styles.link}>
<ThemedText type="link">Go to home screen</ThemedText>
</Link>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});