mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-23 11:07:44 +00:00
261 lines
7.7 KiB
TypeScript
261 lines
7.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
Modal,
|
|
TouchableOpacity,
|
|
TextInput,
|
|
ScrollView,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
ActivityIndicator,
|
|
} from 'react-native';
|
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
import { useTheme } from '../../contexts/ThemeContext';
|
|
import { MalApiService } from '../../services/mal/MalApi';
|
|
import { MalListStatus, MalAnimeNode } from '../../types/mal';
|
|
import { useToast } from '../../contexts/ToastContext';
|
|
|
|
interface MalEditModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
anime: MalAnimeNode;
|
|
onUpdateSuccess: () => void;
|
|
}
|
|
|
|
export const MalEditModal: React.FC<MalEditModalProps> = ({
|
|
visible,
|
|
onClose,
|
|
anime,
|
|
onUpdateSuccess,
|
|
}) => {
|
|
const { currentTheme } = useTheme();
|
|
const { showSuccess, showError } = useToast();
|
|
|
|
const [status, setStatus] = useState<MalListStatus>(anime.list_status.status);
|
|
const [episodes, setEpisodes] = useState(anime.list_status.num_episodes_watched.toString());
|
|
const [score, setScore] = useState(anime.list_status.score.toString());
|
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
setStatus(anime.list_status.status);
|
|
setEpisodes(anime.list_status.num_episodes_watched.toString());
|
|
setScore(anime.list_status.score.toString());
|
|
}
|
|
}, [visible, anime]);
|
|
|
|
const handleUpdate = async () => {
|
|
setIsUpdating(true);
|
|
try {
|
|
const epNum = parseInt(episodes, 10) || 0;
|
|
let scoreNum = parseInt(score, 10) || 0;
|
|
|
|
// Validation: MAL scores must be between 0 and 10
|
|
scoreNum = Math.max(0, Math.min(10, scoreNum));
|
|
|
|
await MalApiService.updateStatus(anime.node.id, status, epNum, scoreNum);
|
|
|
|
showSuccess('Updated', `${anime.node.title} status updated on MAL`);
|
|
onUpdateSuccess();
|
|
onClose();
|
|
} catch (error) {
|
|
showError('Update Failed', 'Could not update MAL status');
|
|
} finally {
|
|
setIsUpdating(false);
|
|
}
|
|
};
|
|
|
|
const statusOptions: { label: string; value: MalListStatus }[] = [
|
|
{ label: 'Watching', value: 'watching' },
|
|
{ label: 'Completed', value: 'completed' },
|
|
{ label: 'On Hold', value: 'on_hold' },
|
|
{ label: 'Dropped', value: 'dropped' },
|
|
{ label: 'Plan to Watch', value: 'plan_to_watch' },
|
|
];
|
|
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
transparent
|
|
animationType="fade"
|
|
onRequestClose={onClose}
|
|
>
|
|
<View style={styles.overlay}>
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
style={styles.container}
|
|
>
|
|
<View style={[styles.modalContent, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
|
<View style={styles.header}>
|
|
<Text style={[styles.title, { color: currentTheme.colors.highEmphasis }]} numberOfLines={1}>
|
|
Edit {anime.node.title}
|
|
</Text>
|
|
<TouchableOpacity onPress={onClose}>
|
|
<MaterialIcons name="close" size={24} color={currentTheme.colors.mediumEmphasis} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
<Text style={[styles.label, { color: currentTheme.colors.mediumEmphasis }]}>Status</Text>
|
|
<View style={styles.statusGrid}>
|
|
{statusOptions.map((option) => (
|
|
<TouchableOpacity
|
|
key={option.value}
|
|
style={[
|
|
styles.statusChip,
|
|
{ borderColor: currentTheme.colors.border },
|
|
status === option.value && {
|
|
backgroundColor: currentTheme.colors.primary,
|
|
borderColor: currentTheme.colors.primary
|
|
}
|
|
]}
|
|
onPress={() => setStatus(option.value)}
|
|
>
|
|
<Text style={[
|
|
styles.statusText,
|
|
{ color: currentTheme.colors.highEmphasis },
|
|
status === option.value && { color: 'white' }
|
|
]}>
|
|
{option.label}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
|
|
<View style={styles.inputRow}>
|
|
<View style={styles.inputGroup}>
|
|
<Text style={[styles.label, { color: currentTheme.colors.mediumEmphasis }]}>
|
|
Episodes ({anime.node.num_episodes || '?'})
|
|
</Text>
|
|
<TextInput
|
|
style={[styles.input, {
|
|
color: currentTheme.colors.highEmphasis,
|
|
borderColor: currentTheme.colors.border,
|
|
backgroundColor: currentTheme.colors.elevation1
|
|
}]}
|
|
value={episodes}
|
|
onChangeText={setEpisodes}
|
|
keyboardType="numeric"
|
|
placeholder="0"
|
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={[styles.label, { color: currentTheme.colors.mediumEmphasis }]}>Score (0-10)</Text>
|
|
<TextInput
|
|
style={[styles.input, {
|
|
color: currentTheme.colors.highEmphasis,
|
|
borderColor: currentTheme.colors.border,
|
|
backgroundColor: currentTheme.colors.elevation1
|
|
}]}
|
|
value={score}
|
|
onChangeText={setScore}
|
|
keyboardType="numeric"
|
|
placeholder="0"
|
|
placeholderTextColor={currentTheme.colors.mediumEmphasis}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.updateButton, { backgroundColor: currentTheme.colors.primary }]}
|
|
onPress={handleUpdate}
|
|
disabled={isUpdating}
|
|
>
|
|
{isUpdating ? (
|
|
<ActivityIndicator color="white" />
|
|
) : (
|
|
<Text style={styles.updateButtonText}>Update MAL</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
</View>
|
|
</KeyboardAvoidingView>
|
|
</View>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: 20,
|
|
},
|
|
container: {
|
|
width: '100%',
|
|
maxWidth: 400,
|
|
},
|
|
modalContent: {
|
|
borderRadius: 16,
|
|
padding: 20,
|
|
maxHeight: '90%',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 20,
|
|
},
|
|
title: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
flex: 1,
|
|
marginRight: 10,
|
|
},
|
|
label: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
marginBottom: 8,
|
|
marginTop: 12,
|
|
},
|
|
statusGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
},
|
|
statusChip: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 8,
|
|
borderRadius: 20,
|
|
borderWidth: 1,
|
|
marginBottom: 4,
|
|
},
|
|
statusText: {
|
|
fontSize: 13,
|
|
fontWeight: '500',
|
|
},
|
|
inputRow: {
|
|
flexDirection: 'row',
|
|
gap: 16,
|
|
marginTop: 8,
|
|
},
|
|
inputGroup: {
|
|
flex: 1,
|
|
},
|
|
input: {
|
|
height: 44,
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 12,
|
|
fontSize: 16,
|
|
},
|
|
updateButton: {
|
|
height: 48,
|
|
borderRadius: 24,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
marginTop: 24,
|
|
marginBottom: 10,
|
|
},
|
|
updateButtonText: {
|
|
color: 'white',
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
},
|
|
});
|