mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
ui changes
This commit is contained in:
parent
9ba14f2f33
commit
0290b9318e
3 changed files with 122 additions and 18 deletions
|
|
@ -1254,11 +1254,11 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
name="AIChat"
|
||||
component={AIChatScreen}
|
||||
options={{
|
||||
animation: Platform.OS === 'android' ? 'slide_from_bottom' : 'slide_from_bottom',
|
||||
animationDuration: Platform.OS === 'android' ? 250 : 300,
|
||||
animation: Platform.OS === 'android' ? 'none' : 'slide_from_bottom',
|
||||
animationDuration: Platform.OS === 'android' ? 220 : 300,
|
||||
presentation: 'modal',
|
||||
gestureEnabled: true,
|
||||
gestureDirection: 'vertical',
|
||||
gestureDirection: Platform.OS === 'android' ? 'vertical' : 'vertical',
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: currentTheme.colors.darkBackground,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ import {
|
|||
ActivityIndicator,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||
import { useRoute, useNavigation, RouteProp, useFocusEffect } from '@react-navigation/native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { Image } from 'expo-image';
|
||||
import { BlurView as ExpoBlurView } from 'expo-blur';
|
||||
import { BlurView as CommunityBlurView } from '@react-native-community/blur';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { aiService, ChatMessage, ContentContext, createMovieContext, createEpisodeContext, generateConversationStarters } from '../services/aiService';
|
||||
import { tmdbService } from '../services/tmdbService';
|
||||
|
|
@ -27,7 +30,8 @@ import Animated, {
|
|||
withSpring,
|
||||
withTiming,
|
||||
interpolate,
|
||||
Extrapolate
|
||||
Extrapolate,
|
||||
runOnJS
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
|
@ -102,10 +106,18 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({ message, isLast }) => {
|
|||
styles.userBubble,
|
||||
{ backgroundColor: currentTheme.colors.primary }
|
||||
] : [
|
||||
styles.assistantBubble,
|
||||
{ backgroundColor: currentTheme.colors.elevation2 }
|
||||
styles.assistantBubble,
|
||||
{ backgroundColor: 'transparent' }
|
||||
]
|
||||
]}>
|
||||
{!isUser && (
|
||||
<View style={styles.assistantBlurBackdrop} pointerEvents="none">
|
||||
{Platform.OS === 'ios'
|
||||
? <ExpoBlurView intensity={70} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
: <CommunityBlurView blurAmount={16} blurRadius={8} style={StyleSheet.absoluteFill} />}
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: 'rgba(0,0,0,0.50)' }]} />
|
||||
</View>
|
||||
)}
|
||||
{isUser ? (
|
||||
<Text style={[styles.messageText, { color: 'white' }]}>
|
||||
{message.content}
|
||||
|
|
@ -281,6 +293,18 @@ const AIChatScreen: React.FC = () => {
|
|||
const [context, setContext] = useState<ContentContext | null>(null);
|
||||
const [isLoadingContext, setIsLoadingContext] = useState(true);
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [backdropUrl, setBackdropUrl] = useState<string | null>(null);
|
||||
|
||||
// Ensure Android cleans up heavy image resources when leaving the screen to avoid flash on back
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
return () => {
|
||||
if (Platform.OS === 'android') {
|
||||
setBackdropUrl(null);
|
||||
}
|
||||
};
|
||||
}, [])
|
||||
);
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
|
@ -288,11 +312,21 @@ const AIChatScreen: React.FC = () => {
|
|||
// Animation values
|
||||
const headerOpacity = useSharedValue(1);
|
||||
const inputContainerY = useSharedValue(0);
|
||||
// Android full-screen modal fade
|
||||
const modalOpacity = useSharedValue(Platform.OS === 'android' ? 0 : 1);
|
||||
|
||||
useEffect(() => {
|
||||
loadContext();
|
||||
}, []);
|
||||
|
||||
// Animate in on Android for full-screen modal feel
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'android') {
|
||||
// Use spring to avoid jank on some devices
|
||||
modalOpacity.value = withSpring(1, { damping: 20, stiffness: 140 });
|
||||
}
|
||||
}, [modalOpacity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (context && messages.length === 0) {
|
||||
// Generate conversation starters
|
||||
|
|
@ -320,6 +354,10 @@ const AIChatScreen: React.FC = () => {
|
|||
|
||||
const movieContext = createMovieContext(movieData);
|
||||
setContext(movieContext);
|
||||
try {
|
||||
const path = movieData.backdrop_path || movieData.poster_path || null;
|
||||
if (path) setBackdropUrl(`https://image.tmdb.org/t/p/w780${path}`);
|
||||
} catch {}
|
||||
} else {
|
||||
// Series: resolve TMDB numeric id first (contentId may be IMDb/stremio id)
|
||||
let tmdbNumericId: number | null = null;
|
||||
|
|
@ -343,6 +381,10 @@ const AIChatScreen: React.FC = () => {
|
|||
]);
|
||||
|
||||
if (!showData) throw new Error('Unable to load TV show details');
|
||||
try {
|
||||
const path = showData.backdrop_path || showData.poster_path || null;
|
||||
if (path) setBackdropUrl(`https://image.tmdb.org/t/p/w780${path}`);
|
||||
} catch {}
|
||||
|
||||
if (episodeData && seasonNumber && episodeNumber) {
|
||||
const episodeContext = createEpisodeContext(
|
||||
|
|
@ -533,21 +575,44 @@ const AIChatScreen: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Animated.View style={{ flex: 1, opacity: modalOpacity }}>
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
{backdropUrl && (
|
||||
<View style={StyleSheet.absoluteFill} pointerEvents="none">
|
||||
<Image
|
||||
source={{ uri: backdropUrl }}
|
||||
style={StyleSheet.absoluteFill}
|
||||
contentFit="cover"
|
||||
recyclingKey={backdropUrl || undefined}
|
||||
/>
|
||||
{Platform.OS === 'ios'
|
||||
? <ExpoBlurView intensity={60} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
: <CommunityBlurView blurAmount={12} blurRadius={6} style={StyleSheet.absoluteFill} />}
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: Platform.OS === 'android' ? 'rgba(0,0,0,0.28)' : 'rgba(0,0,0,0.45)' }]} />
|
||||
</View>
|
||||
)}
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
||||
{/* Header */}
|
||||
<Animated.View style={[
|
||||
styles.header,
|
||||
{
|
||||
backgroundColor: currentTheme.colors.darkBackground,
|
||||
paddingTop: insets.top
|
||||
backgroundColor: 'transparent',
|
||||
paddingTop: Platform.OS === 'ios' ? 8 : insets.top
|
||||
},
|
||||
headerAnimatedStyle
|
||||
]}>
|
||||
<View style={styles.headerContent}>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.goBack()}
|
||||
onPress={() => {
|
||||
if (Platform.OS === 'android') {
|
||||
modalOpacity.value = withSpring(0, { damping: 18, stiffness: 160 }, (finished) => {
|
||||
if (finished) runOnJS(navigation.goBack)();
|
||||
});
|
||||
} else {
|
||||
navigation.goBack();
|
||||
}
|
||||
}}
|
||||
style={styles.backButton}
|
||||
>
|
||||
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
|
||||
|
|
@ -571,13 +636,16 @@ const AIChatScreen: React.FC = () => {
|
|||
{/* Chat Messages */}
|
||||
<KeyboardAvoidingView
|
||||
style={styles.chatContainer}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
keyboardVerticalOffset={insets.top + 60}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
||||
keyboardVerticalOffset={Platform.OS === 'ios' ? insets.top + 60 : 0}
|
||||
>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
style={styles.messagesContainer}
|
||||
contentContainerStyle={styles.messagesContent}
|
||||
contentContainerStyle={[
|
||||
styles.messagesContent,
|
||||
{ paddingBottom: 120 + insets.bottom }
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
|
|
@ -637,10 +705,19 @@ const AIChatScreen: React.FC = () => {
|
|||
{/* Input Container */}
|
||||
<Animated.View style={[
|
||||
styles.inputContainer,
|
||||
{ backgroundColor: currentTheme.colors.darkBackground },
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
paddingBottom: 12 + insets.bottom
|
||||
},
|
||||
inputAnimatedStyle
|
||||
]}>
|
||||
<View style={[styles.inputWrapper, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
||||
<View style={[styles.inputWrapper, { backgroundColor: 'transparent' }]}>
|
||||
<View style={styles.inputBlurBackdrop} pointerEvents="none">
|
||||
{Platform.OS === 'ios'
|
||||
? <ExpoBlurView intensity={50} tint="dark" style={StyleSheet.absoluteFill} />
|
||||
: <CommunityBlurView blurAmount={10} blurRadius={4} style={StyleSheet.absoluteFill} />}
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: Platform.OS === 'android' ? 'rgba(0,0,0,0.15)' : 'rgba(0,0,0,0.25)' }]} />
|
||||
</View>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
style={[
|
||||
|
|
@ -679,6 +756,7 @@ const AIChatScreen: React.FC = () => {
|
|||
</Animated.View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -827,12 +905,17 @@ const styles = StyleSheet.create({
|
|||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
userBubble: {
|
||||
borderBottomRightRadius: 4,
|
||||
borderBottomRightRadius: 20,
|
||||
},
|
||||
assistantBubble: {
|
||||
borderBottomLeftRadius: 4,
|
||||
borderBottomLeftRadius: 20,
|
||||
},
|
||||
assistantBlurBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
borderRadius: 20,
|
||||
},
|
||||
messageText: {
|
||||
fontSize: 16,
|
||||
|
|
@ -876,6 +959,11 @@ const styles = StyleSheet.create({
|
|||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
gap: 12,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
inputBlurBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
borderRadius: 24,
|
||||
},
|
||||
textInput: {
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -132,7 +132,16 @@ CRITICAL INSTRUCTIONS:
|
|||
3. If Release Status shows "RELEASED AND AVAILABLE FOR VIEWING", the content IS AVAILABLE. Do not say it's "upcoming" or "unreleased".
|
||||
4. Compare air dates to today's date (${currentDate}) to determine if something has already aired.
|
||||
5. Base ALL responses on the verified information above, NOT on your training knowledge.
|
||||
6. If asked about release dates or availability, refer ONLY to the database information provided.`;
|
||||
6. If asked about release dates or availability, refer ONLY to the database information provided.
|
||||
|
||||
FORMATTING RULES (use Markdown):
|
||||
- Use short paragraphs separated by blank lines.
|
||||
- Use clear headings (## or ###) when helpful.
|
||||
- Use bullet lists for points, character lists, and steps.
|
||||
- Add a blank line before and after lists and headings.
|
||||
- Keep lines concise; avoid giant unbroken blocks of text.
|
||||
- Wrap inline code/terms with backticks only when appropriate.
|
||||
`;
|
||||
} else {
|
||||
const movie = context as MovieContext;
|
||||
const currentDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
|
|
@ -164,6 +173,13 @@ CRITICAL INSTRUCTIONS:
|
|||
5. If asked about release dates or availability, refer ONLY to the database information provided.
|
||||
6. You can discuss themes, production, performances, and high-level plot setup without revealing twists, surprises, or outcomes.
|
||||
|
||||
FORMATTING RULES (use Markdown):
|
||||
- Use short paragraphs separated by blank lines.
|
||||
- Use clear headings (## or ###) when helpful.
|
||||
- Use bullet lists for points and steps.
|
||||
- Add a blank line before and after lists and headings.
|
||||
- Keep lines concise; avoid giant unbroken blocks of text.
|
||||
|
||||
Answer questions about this movie using only the verified database information above, including plot analysis, character development, themes, cinematography, production notes, and trivia. Provide detailed, informative responses while remaining spoiler-safe.`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue