cập nhật animation hiển thị modal, call API edit

This commit is contained in:
2025-12-22 22:47:08 +07:00
parent 67e9fc22a3
commit afc6acbfe2
16 changed files with 530 additions and 216 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useRef, useEffect } from "react";
import {
View,
Text,
@@ -7,6 +7,8 @@ import {
StyleSheet,
Platform,
ScrollView,
Animated,
Dimensions,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import StatusDropdown from "./StatusDropdown";
@@ -71,6 +73,47 @@ export default function FilterModal({
const [endDate, setEndDate] = useState<Date | null>(null);
const [selectedShip, setSelectedShip] = useState<ShipOption | null>(null);
// Animation values
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(Dimensions.get('window').height)).current;
// Handle animation when modal visibility changes
useEffect(() => {
if (visible) {
// Open animation: fade overlay + slide content up
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}),
]).start();
}
}, [visible, fadeAnim, slideAnim]);
const handleClose = () => {
// Close animation: fade overlay + slide content down
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: Dimensions.get('window').height,
duration: 250,
useNativeDriver: true,
}),
]).start(() => {
onClose();
});
};
const handleReset = () => {
setStatus(null);
setStartDate(null);
@@ -79,8 +122,22 @@ export default function FilterModal({
};
const handleApply = () => {
onApply({ status, startDate, endDate, selectedShip });
onClose();
// Close animation then apply
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: Dimensions.get('window').height,
duration: 250,
useNativeDriver: true,
}),
]).start(() => {
onApply({ status, startDate, endDate, selectedShip });
onClose();
});
};
const hasFilters =
@@ -128,23 +185,26 @@ export default function FilterModal({
return (
<Modal
visible={visible}
animationType="fade"
animationType="none"
transparent
onRequestClose={onClose}
onRequestClose={handleClose}
>
<TouchableOpacity
style={styles.overlay}
activeOpacity={1}
onPress={onClose}
>
<Animated.View style={[styles.overlay, { opacity: fadeAnim }]}>
<TouchableOpacity
style={[styles.modalContainer, themedStyles.modalContainer]}
style={styles.overlayTouchable}
activeOpacity={1}
onPress={(e) => e.stopPropagation()}
onPress={handleClose}
/>
<Animated.View
style={[
styles.modalContainer,
themedStyles.modalContainer,
{ transform: [{ translateY: slideAnim }] }
]}
>
{/* Header */}
<View style={[styles.header, themedStyles.header]}>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
<Ionicons name="close" size={24} color={colors.text} />
</TouchableOpacity>
<Text style={[styles.title, themedStyles.title]}>{t("diary.filter")}</Text>
@@ -218,8 +278,8 @@ export default function FilterModal({
<Text style={styles.applyButtonText}>{t("diary.apply")}</Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
</TouchableOpacity>
</Animated.View>
</Animated.View>
</Modal>
);
}
@@ -231,6 +291,13 @@ const styles = StyleSheet.create({
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "flex-end",
},
overlayTouchable: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
modalContainer: {
borderTopLeftRadius: 24,
borderTopRightRadius: 24,