thêm toast, thêm logic cho phần ButtonCreateNewHaulOrTrip

This commit is contained in:
Tran Anh Tuan
2025-11-06 17:30:04 +07:00
parent 1ef83c9b22
commit aabd1109b2
15 changed files with 799 additions and 119 deletions

View File

@@ -7,14 +7,15 @@ import { Stack, useRouter } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { useEffect } from "react";
import "react-native-reanimated";
import Toast from "react-native-toast-message";
// import Toast from "react-native-toast-message";
import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider/gluestack-ui-provider";
import { toastConfig } from "@/config";
import { setRouterInstance } from "@/config/auth";
import "@/global.css";
import { useColorScheme } from "@/hooks/use-color-scheme";
import Toast from "react-native-toast-message";
import "../global.css";
export default function RootLayout() {
const colorScheme = useColorScheme();
const router = useRouter();
@@ -52,7 +53,7 @@ export default function RootLayout() {
/>
</Stack>
<StatusBar style="auto" />
<Toast />
<Toast config={toastConfig} visibilityTime={2000} topOffset={60} />
</ThemeProvider>
</GluestackUIProvider>
);

View File

@@ -1,17 +1,40 @@
import { queryGpsData } from "@/controller/DeviceController";
import {
queryStartNewHaul,
queryUpdateTripState,
} from "@/controller/TripController";
import {
showErrorToast,
showSuccessToast,
showWarningToast,
} from "@/services/toast_service";
import { useTrip } from "@/state/use-trip";
import { AntDesign } from "@expo/vector-icons";
import React, { useState } from "react";
import { Alert, StyleSheet, Text, TouchableOpacity, View } from "react-native";
import React, { useEffect, useState } from "react";
import { Alert, StyleSheet, View } from "react-native";
import IconButton from "./IconButton";
import CreateOrUpdateHaulModal from "./tripInfo/modal/CreateOrUpdateHaulModal";
interface StartButtonProps {
title?: string;
gpsData?: Model.GPSResponse;
onPress?: () => void;
}
const ButtonCreateNewHaulOrTrip: React.FC<StartButtonProps> = ({
title = "Bắt đầu mẻ lưới",
gpsData,
onPress,
}) => {
const [isStarted, setIsStarted] = useState(false);
const [isFinishHaulModalOpen, setIsFinishHaulModalOpen] = useState(false);
const { trip, getTrip } = useTrip();
useEffect(() => {
getTrip();
}, [trip === null]);
const checkHaulFinished = () => {
return trip?.fishing_logs?.some((h) => h.status === 0);
};
const handlePress = () => {
if (isStarted) {
@@ -53,24 +76,103 @@ const ButtonCreateNewHaulOrTrip: React.FC<StartButtonProps> = ({
}
};
const handleStartTrip = async (state: number, note?: string) => {
if (trip?.trip_status !== 2) {
showWarningToast("Chuyến đi đã được bắt đầu hoặc hoàn thành.");
return;
}
try {
const resp = await queryUpdateTripState({
status: state,
note: note || "",
});
if (resp.status === 200) {
showSuccessToast("Bắt đầu chuyến đi thành công!");
getTrip();
}
} catch (error) {
console.error("Error stating trip :", error);
showErrorToast("");
}
};
const createNewHaul = async () => {
if (trip?.fishing_logs?.some((f) => f.status === 0)) {
showWarningToast(
"Vui lòng kết thúc mẻ lưới hiện tại trước khi bắt đầu mẻ mới"
);
return;
}
if (!gpsData) {
const response = await queryGpsData();
gpsData = response.data;
}
try {
const body: Model.NewFishingLogRequest = {
trip_id: trip?.id || "",
start_at: new Date(),
start_lat: gpsData.lat,
start_lon: gpsData.lon,
weather_description: "Nắng đẹp",
};
const resp = await queryStartNewHaul(body);
if (resp.status === 200) {
showSuccessToast("Bắt đầu mẻ lưới mới thành công!");
getTrip();
} else {
showErrorToast("Tạo mẻ lưới mới thất bại!");
}
} catch (error) {
console.log(error);
// showErrorToast("Tạo mẻ lưới mới thất bại!");
}
};
// Không render gì nếu trip đã hoàn thành hoặc bị hủy
if (trip?.trip_status === 4 || trip?.trip_status === 5) {
return null;
}
return (
<TouchableOpacity
style={[styles.button, isStarted && styles.buttonActive]}
onPress={handlePress}
activeOpacity={0.8}
>
<View style={styles.content}>
<AntDesign
name={isStarted ? "close" : "plus"}
size={18}
color="#fff"
style={styles.icon}
/>
<Text style={styles.text}>
{isStarted ? "Kết thúc mẻ lưới" : title}
</Text>
</View>
</TouchableOpacity>
<View>
{trip?.trip_status === 2 ? (
<IconButton
icon={<AntDesign name="plus" />}
type="primary"
style={{ backgroundColor: "green", borderRadius: 10 }}
onPress={async () => handleStartTrip(3)}
>
Bắt đu chuyến đi
</IconButton>
) : checkHaulFinished() ? (
<IconButton
icon={<AntDesign name="plus" color={"white"} />}
type="primary"
style={{ borderRadius: 10 }}
onPress={() => setIsFinishHaulModalOpen(true)}
>
Kết thúc mẻ lưới
</IconButton>
) : (
<IconButton
icon={<AntDesign name="plus" color={"white"} />}
type="primary"
style={{ borderRadius: 10 }}
onPress={async () => {
createNewHaul();
}}
>
Bắt đu mẻ lưới
</IconButton>
)}
<CreateOrUpdateHaulModal
isVisible={isFinishHaulModalOpen}
onClose={function (): void {
setIsFinishHaulModalOpen(false);
}}
/>
</View>
);
};

159
components/IconButton.tsx Normal file
View File

@@ -0,0 +1,159 @@
import React from "react";
import {
ActivityIndicator,
GestureResponderEvent,
StyleProp,
StyleSheet,
Text,
TouchableOpacity,
View,
ViewStyle,
} from "react-native";
type ButtonType = "primary" | "default" | "dashed" | "text" | "link" | "danger";
type ButtonShape = "default" | "circle" | "round";
type ButtonSize = "small" | "middle" | "large";
export interface IconButtonProps {
type?: ButtonType;
shape?: ButtonShape;
size?: ButtonSize;
icon?: React.ReactNode; // render an icon component, e.g. <AntDesign name="plus" />
loading?: boolean;
disabled?: boolean;
onPress?: (e?: GestureResponderEvent) => void;
children?: React.ReactNode; // label text
style?: StyleProp<ViewStyle>;
block?: boolean; // full width
activeOpacity?: number;
}
/**
* IconButton
* A lightweight Button component inspired by Ant Design Button API, tuned for React Native.
* Accepts an `icon` prop as a React node for maximum flexibility.
*/
const IconButton: React.FC<IconButtonProps> = ({
type = "default",
shape = "default",
size = "middle",
icon,
loading = false,
disabled = false,
onPress,
children,
style,
block = false,
activeOpacity = 0.8,
}) => {
const sizeMap = {
small: { height: 32, fontSize: 14, paddingHorizontal: 10 },
middle: { height: 40, fontSize: 16, paddingHorizontal: 14 },
large: { height: 48, fontSize: 18, paddingHorizontal: 18 },
} as const;
const colors: Record<
ButtonType,
{ backgroundColor?: string; textColor: string; borderColor?: string }
> = {
primary: { backgroundColor: "#4ecdc4", textColor: "#fff" },
default: {
backgroundColor: "#f2f2f2",
textColor: "#111",
borderColor: "#e6e6e6",
},
dashed: {
backgroundColor: "#fff",
textColor: "#111",
borderColor: "#d9d9d9",
},
text: { backgroundColor: "transparent", textColor: "#111" },
link: { backgroundColor: "transparent", textColor: "#4ecdc4" },
danger: { backgroundColor: "#e74c3c", textColor: "#fff" },
};
const sz = sizeMap[size];
const color = colors[type];
const isCircle = shape === "circle";
const isRound = shape === "round";
const handlePress = (e: GestureResponderEvent) => {
if (disabled || loading) return;
onPress?.(e);
};
return (
<TouchableOpacity
activeOpacity={activeOpacity}
onPress={handlePress}
disabled={disabled || loading}
style={[
styles.button,
{
height: sz.height,
paddingHorizontal: isCircle ? 0 : sz.paddingHorizontal,
backgroundColor: color.backgroundColor ?? "transparent",
borderColor: color.borderColor ?? "transparent",
borderWidth: type === "dashed" ? 1 : color.borderColor ? 1 : 0,
width: isCircle ? sz.height : block ? "100%" : undefined,
borderRadius: isCircle ? sz.height / 2 : isRound ? 999 : 8,
opacity: disabled ? 0.6 : 1,
},
type === "dashed" ? { borderStyle: "dashed" } : null,
style,
]}
>
<View style={styles.content}>
{loading ? (
<ActivityIndicator
size={"small"}
color={color.textColor}
style={styles.iconContainer}
/>
) : icon ? (
<View style={styles.iconContainer}>{icon}</View>
) : null}
{children ? (
<Text
numberOfLines={1}
style={[
styles.text,
{
color: color.textColor,
fontSize: sz.fontSize,
marginLeft: icon || loading ? 6 : 0,
},
]}
>
{children}
</Text>
) : null}
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
borderRadius: 8,
borderColor: "transparent",
},
content: {
flexDirection: "row",
alignItems: "center",
},
iconContainer: {
alignItems: "center",
justifyContent: "center",
},
text: {
fontWeight: "600",
},
});
export default IconButton;

View File

@@ -2,10 +2,10 @@ import { convertToDMS, kmhToKnot } from "@/utils/geom";
import { MaterialIcons } from "@expo/vector-icons";
import { useEffect, useRef, useState } from "react";
import { Animated, TouchableOpacity, View } from "react-native";
import ButtonCreateNewHaulOrTrip from "../ButtonCreateNewHaulOrTrip";
import { Description } from "./Description";
type GPSInfoPanelProps = {
gpsData: Model.GPSResonse | undefined;
gpsData: Model.GPSResponse | undefined;
};
const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => {
@@ -43,13 +43,28 @@ const GPSInfoPanel = ({ gpsData }: GPSInfoPanelProps) => {
position: "absolute",
bottom: blockBottom,
left: 5,
width: 48,
height: 48,
backgroundColor: "blue",
// width: 48,
// height: 48,
// backgroundColor: "blue",
borderRadius: 4,
zIndex: 30,
}}
/>
>
<ButtonCreateNewHaulOrTrip gpsData={gpsData} />
{/* <TouchableOpacity
onPress={() => {
// showInfoToast("oad");
showWarningToast("This is a warning toast!");
}}
className="absolute top-2 right-2 z-10 bg-white rounded-full p-1"
>
<MaterialIcons
name={isExpanded ? "close" : "close"}
size={20}
color="#666"
/>
</TouchableOpacity> */}
</Animated.View>
<Animated.View
style={{

View File

@@ -284,7 +284,10 @@ const NetListTable: React.FC = () => {
{/* Modal chi tiết */}
<NetDetailModal
visible={modalVisible}
onClose={() => setModalVisible(false)}
onClose={() => {
console.log("OnCLose");
setModalVisible(false);
}}
netData={selectedNet}
/>
</View>

View File

@@ -0,0 +1,29 @@
import React from "react";
import { Modal, Text } from "react-native";
interface CreateOrUpdateHaulModalProps {
isVisible: boolean;
onClose: () => void;
haulData?: Model.FishingLog | null;
}
const CreateOrUpdateHaulModal: React.FC<CreateOrUpdateHaulModalProps> = ({
isVisible,
onClose,
haulData,
}) => {
const [isCreateMode, setIsCreateMode] = React.useState(!haulData);
return (
<Modal
visible={isVisible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={onClose}
>
<Text>{isCreateMode ? "Create Haul" : "Update Haul"}</Text>
</Modal>
);
};
export default CreateOrUpdateHaulModal;

View File

@@ -91,9 +91,9 @@ const NetDetailModal: React.FC<NetDetailModalProps> = ({
}
}, [visible]);
if (!netData) return null;
// if (!netData) return null;
const isCompleted = netData.trangThai === "Đã hoàn thành";
const isCompleted = netData?.trangThai === "Đã hoàn thành";
// Danh sách tên cá có sẵn
const fishNameOptions = [
@@ -210,7 +210,7 @@ const NetDetailModal: React.FC<NetDetailModalProps> = ({
const handleCancel = () => {
setIsEditing(false);
setEditableCatchList(netData.catchList || []);
setEditableCatchList(netData?.catchList || []);
};
const handleToggleExpanded = (index: number) => {
@@ -343,7 +343,10 @@ const NetDetailModal: React.FC<NetDetailModalProps> = ({
{/* Content */}
<ScrollView style={styles.content}>
{/* Thông tin chung */}
<InfoSection netData={netData} isCompleted={isCompleted} />
<InfoSection
netData={netData ?? undefined}
isCompleted={isCompleted}
/>
{/* Danh sách cá bắt được */}
<CatchSectionHeader totalCatch={totalCatch} />
@@ -372,7 +375,7 @@ const NetDetailModal: React.FC<NetDetailModalProps> = ({
/>
{/* Ghi chú */}
<NotesSection ghiChu={netData.ghiChu} />
<NotesSection ghiChu={netData?.ghiChu} />
</ScrollView>
</View>
</Modal>

View File

@@ -17,7 +17,7 @@ interface NetDetail {
}
interface InfoSectionProps {
netData: NetDetail;
netData?: NetDetail;
isCompleted: boolean;
}
@@ -25,6 +25,9 @@ export const InfoSection: React.FC<InfoSectionProps> = ({
netData,
isCompleted,
}) => {
if (!netData) {
return null;
}
const infoItems = [
{ label: "Số thứ tự", value: netData.stt },
{

View File

@@ -0,0 +1,177 @@
import { StyleSheet } from "react-native";
export default StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 8,
backgroundColor: "#f8f9fa",
borderBottomWidth: 1,
borderBottomColor: "#e9ecef",
},
title: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
},
closeButton: {
padding: 8,
},
closeButtonText: {
fontSize: 16,
color: "#007bff",
},
content: {
flex: 1,
padding: 16,
},
fieldGroup: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: "600",
color: "#333",
marginBottom: 4,
},
input: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 4,
padding: 8,
fontSize: 16,
backgroundColor: "#fff",
},
infoValue: {
fontSize: 16,
color: "#555",
paddingVertical: 8,
},
rowGroup: {
flexDirection: "row",
justifyContent: "space-between",
},
fishNameDropdown: {
// Custom styles if needed
},
optionsStatusFishList: {
// Custom styles if needed
},
optionsList: {
maxHeight: 150,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 4,
backgroundColor: "#fff",
position: "absolute",
top: 40,
left: 0,
right: 0,
zIndex: 1000,
},
selectButton: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 4,
padding: 8,
backgroundColor: "#fff",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
selectButtonText: {
fontSize: 16,
color: "#333",
},
optionItem: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: "#eee",
},
optionText: {
fontSize: 16,
color: "#333",
},
card: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 8,
padding: 12,
marginBottom: 12,
backgroundColor: "#f9f9f9",
},
removeButton: {
backgroundColor: "#dc3545",
padding: 8,
borderRadius: 4,
alignSelf: "flex-end",
marginTop: 8,
},
removeButtonText: {
color: "#fff",
fontSize: 14,
},
errorText: {
color: "#dc3545",
fontSize: 12,
marginTop: 4,
},
buttonGroup: {
flexDirection: "row",
justifyContent: "space-around",
marginTop: 16,
},
editButton: {
backgroundColor: "#007bff",
padding: 10,
borderRadius: 4,
},
editButtonText: {
color: "#fff",
fontSize: 16,
},
addButton: {
backgroundColor: "#28a745",
padding: 10,
borderRadius: 4,
},
addButtonText: {
color: "#fff",
fontSize: 16,
},
saveButton: {
backgroundColor: "#007bff",
padding: 10,
borderRadius: 4,
},
saveButtonText: {
color: "#fff",
fontSize: 16,
},
cancelButton: {
backgroundColor: "#6c757d",
padding: 10,
borderRadius: 4,
},
cancelButtonText: {
color: "#fff",
fontSize: 16,
},
addFishButton: {
backgroundColor: "#17a2b8",
padding: 10,
borderRadius: 4,
marginBottom: 16,
},
addFishButtonText: {
color: "#fff",
fontSize: 16,
},
});

View File

@@ -1,79 +0,0 @@
import Toast from "react-native-toast-message";
export enum ToastType {
SUCCESS = "success",
ERROR = "error",
WARNING = "error", // react-native-toast-message không có 'warning', dùng 'error'
INFO = "info",
}
/**
* Success toast
*/
export const showToastSuccess = (message: string, title?: string): void => {
Toast.show({
type: "success",
text1: title || "Success",
text2: message,
});
};
/**
* Error toast
*/
export const showToastError = (message: string, title?: string): void => {
Toast.show({
type: ToastType.ERROR,
text1: title || ToastType.ERROR,
text2: message,
});
};
/**
* Info toast
*/
export const showToastInfo = (message: string, title?: string): void => {
Toast.show({
type: ToastType.INFO,
text1: title || ToastType.INFO,
text2: message,
});
};
/**
* Warning toast
*/
export const showToastWarning = (message: string, title?: string): void => {
Toast.show({
type: ToastType.WARNING,
text1: title || ToastType.WARNING,
text2: message,
});
};
/**
* Default toast
*/
export const showToastDefault = (message: string, title?: string): void => {
Toast.show({
type: ToastType.INFO,
text1: title || ToastType.INFO,
text2: message,
});
};
/**
* Custom toast với type tùy chọn
*/
export const show = (
message: string,
type: ToastType,
title?: string
): void => {
const titleText = title || type.charAt(0).toUpperCase() + type.slice(1);
Toast.show({
type,
text1: titleText,
text2: message,
});
};

214
config/toast.tsx Normal file
View File

@@ -0,0 +1,214 @@
import { MaterialIcons } from "@expo/vector-icons";
import { View } from "react-native";
import {
BaseToast,
BaseToastProps,
SuccessToast,
} from "react-native-toast-message";
export const Colors: any = {
light: {
text: "#000",
back: "#ffffff",
},
dark: {
text: "#ffffff",
back: "#2B2D2E",
},
default: "#3498db",
info: "#3498db",
success: "#07bc0c",
warn: {
background: "#ffffff",
text: "black",
iconColor: "#f1c40f",
},
error: {
background: "#ffffff",
text: "black",
iconColor: "#e74c3c",
},
textDefault: "#4c4c4c",
textDark: "black",
};
export const toastConfig = {
success: (props: BaseToastProps) => (
<SuccessToast
{...props}
style={{
borderLeftColor: Colors.success,
backgroundColor: Colors.success,
borderRadius: 10,
}}
contentContainerStyle={{
paddingHorizontal: 10,
}}
text1Style={{
color: "white",
fontSize: 16,
fontWeight: "600",
}}
text2Style={{
color: "#f0f0f0",
}}
renderLeadingIcon={() => (
<View
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 40,
height: "100%",
}}
>
<MaterialIcons name="check-circle" size={30} color="white" />
</View>
)}
/>
),
default: (props: BaseToastProps) => (
<BaseToast
{...props}
style={{
borderLeftColor: Colors.default,
backgroundColor: Colors.default,
borderRadius: 10,
}}
contentContainerStyle={{
paddingHorizontal: 10,
}}
text1Style={{
color: "white",
fontSize: 16,
fontWeight: "600",
}}
text2Style={{
color: "#f0f0f0",
}}
renderLeadingIcon={() => (
<View
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 50,
height: "100%",
}}
>
<MaterialIcons name="info" size={30} color="white" />
</View>
)}
/>
),
info: (props: BaseToastProps) => (
<BaseToast
{...props}
style={{
borderLeftColor: Colors.info,
backgroundColor: Colors.info,
borderRadius: 10,
}}
contentContainerStyle={{
paddingHorizontal: 10,
}}
text1Style={{
color: "white",
fontSize: 16,
fontWeight: "600",
}}
text2Style={{
color: "#f0f0f0",
}}
renderLeadingIcon={() => (
<View
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 50,
height: "100%",
}}
>
<MaterialIcons name="info-outline" size={30} color="white" />
</View>
)}
/>
),
warn: (props: BaseToastProps) => (
<BaseToast
{...props}
style={{
borderLeftColor: Colors.warn.background,
backgroundColor: Colors.warn.background,
borderRadius: 10,
}}
contentContainerStyle={{
paddingHorizontal: 10,
}}
text1Style={{
color: Colors.warn.text,
fontSize: 16,
fontWeight: "600",
}}
text2Style={{
color: "#333",
}}
renderLeadingIcon={() => (
<View
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 50,
height: "100%",
}}
>
<MaterialIcons
name="warning"
size={30}
color={Colors.warn.iconColor}
/>
</View>
)}
/>
),
error: (props: BaseToastProps) => (
<BaseToast
{...props}
style={{
borderLeftColor: Colors.error.background,
backgroundColor: Colors.error.background,
borderRadius: 10,
}}
contentContainerStyle={{
paddingHorizontal: 10,
}}
text1Style={{
color: Colors.error.text,
fontSize: 16,
fontWeight: "600",
}}
text2Style={{
color: "#f0f0f0",
}}
renderLeadingIcon={() => (
<View
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 50,
height: "100%",
}}
>
<MaterialIcons
name="error"
size={30}
color={Colors.error.iconColor}
/>
</View>
)}
/>
),
};

View File

@@ -9,7 +9,7 @@ import {
import { transformEntityResponse } from "@/utils/tranform";
export async function queryGpsData() {
return api.get<Model.GPSResonse>(API_GET_GPS);
return api.get<Model.GPSResponse>(API_GET_GPS);
}
export async function queryAlarm() {

View File

@@ -1,6 +1,18 @@
import { api } from "@/config";
import { API_GET_TRIP } from "@/constants";
import {
API_GET_TRIP,
API_HAUL_HANDLE,
API_UPDATE_TRIP_STATUS,
} from "@/constants";
export async function queryTrip() {
return api.get<Model.Trip>(API_GET_TRIP);
}
export async function queryUpdateTripState(body: Model.TripUpdateStateRequest) {
return api.put(API_UPDATE_TRIP_STATUS, body);
}
export async function queryStartNewHaul(body: Model.NewFishingLogRequest) {
return api.put(API_HAUL_HANDLE, body);
}

View File

@@ -7,7 +7,7 @@ declare namespace Model {
token?: string;
}
interface GPSResonse {
interface GPSResponse {
lat: number;
lon: number;
s: number;
@@ -174,4 +174,16 @@ declare namespace Model {
fish_condition?: string;
gear_usage?: string;
}
interface NewFishingLogRequest {
trip_id: string;
start_at: Date; // ISO datetime
start_lat: number;
start_lon: number;
weather_description: string;
}
interface TripUpdateStateRequest {
status: number;
note?: string;
}
}

View File

@@ -0,0 +1,29 @@
import Toast from "react-native-toast-message";
export function showInfoToast(message: string) {
Toast.show({
type: "info",
text1: message,
});
}
export function showSuccessToast(message: string) {
Toast.show({
type: "success",
text1: message,
});
}
export function showErrorToast(message: string) {
Toast.show({
type: "error",
text1: message,
});
}
export function showWarningToast(message: string) {
Toast.show({
type: "warn",
text1: message,
});
}