thêm giao diện map và cập nhật nativewind
3
app.json
@@ -23,7 +23,8 @@
|
|||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"output": "static",
|
"output": "static",
|
||||||
"favicon": "./assets/images/favicon.png"
|
"favicon": "./assets/images/favicon.png",
|
||||||
|
"bundler": "metro"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"expo-router",
|
"expo-router",
|
||||||
|
|||||||
@@ -1,32 +1,125 @@
|
|||||||
import { showToastError } from "@/config";
|
import { showToastError } from "@/config";
|
||||||
import { fetchGpsData } from "@/controller/DeviceController";
|
import {
|
||||||
|
queryAlarm,
|
||||||
|
queryGpsData,
|
||||||
|
queryTrackPoints,
|
||||||
|
} from "@/controller/DeviceController";
|
||||||
|
import { getShipIcon } from "@/services/map_service";
|
||||||
|
import { Image as ExpoImage } from "expo-image";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { StyleSheet, Text, TouchableOpacity } from "react-native";
|
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||||
|
|
||||||
import MapView, { Marker } from "react-native-maps";
|
import MapView, { Circle, Marker } from "react-native-maps";
|
||||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const [gpsData, setGpsData] = useState<Model.GPSResonse | null>(null);
|
const [gpsData, setGpsData] = useState<Model.GPSResonse | null>(null);
|
||||||
|
const [alarmData, setAlarmData] = useState<Model.AlarmResponse | null>(null);
|
||||||
// useEffect(() => {
|
const [trackPoints, setTrackPoints] = useState<Model.ShipTrackPoint[] | null>(
|
||||||
// getGpsData();
|
null
|
||||||
// }, []);
|
);
|
||||||
|
|
||||||
const getGpsData = async () => {
|
const getGpsData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetchGpsData();
|
const response = await queryGpsData();
|
||||||
console.log("GpsData: ", response.data);
|
console.log("GpsData: ", response.data);
|
||||||
|
console.log(
|
||||||
|
"Heading value:",
|
||||||
|
response.data?.h,
|
||||||
|
"Type:",
|
||||||
|
typeof response.data?.h
|
||||||
|
);
|
||||||
setGpsData(response.data);
|
setGpsData(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Error fetching GPS data:", error);
|
||||||
showToastError("Lỗi", "Không thể lấy dữ liệu GPS");
|
showToastError("Lỗi", "Không thể lấy dữ liệu GPS");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAlarmData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await queryAlarm();
|
||||||
|
console.log("AlarmData: ", response.data);
|
||||||
|
setAlarmData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Alarm Data: ", error);
|
||||||
|
showToastError("Lỗi", "Không thể lấy dữ liệu báo động");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShipTrackPoints = async () => {
|
||||||
|
try {
|
||||||
|
const response = await queryTrackPoints();
|
||||||
|
console.log(
|
||||||
|
"TrackPoints Data: ",
|
||||||
|
response.data[response.data.length - 1]
|
||||||
|
);
|
||||||
|
setTrackPoints(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching TrackPoints Data: ", error);
|
||||||
|
showToastError("Lỗi", "Không thể lấy lịch sử di chuyển");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMapReady = () => {
|
||||||
|
console.log("Map loaded successfully!");
|
||||||
|
getGpsData();
|
||||||
|
getAlarmData();
|
||||||
|
getShipTrackPoints();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tính toán region để bao phủ cả GPS và track points
|
||||||
|
const getMapRegion = () => {
|
||||||
|
if (!gpsData && (!trackPoints || trackPoints.length === 0)) {
|
||||||
|
return {
|
||||||
|
latitude: 15.70581,
|
||||||
|
longitude: 116.152685,
|
||||||
|
latitudeDelta: 2,
|
||||||
|
longitudeDelta: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let minLat = gpsData?.lat ?? 90;
|
||||||
|
let maxLat = gpsData?.lat ?? -90;
|
||||||
|
let minLon = gpsData?.lon ?? 180;
|
||||||
|
let maxLon = gpsData?.lon ?? -180;
|
||||||
|
|
||||||
|
// Bao gồm track points
|
||||||
|
if (trackPoints) {
|
||||||
|
trackPoints.forEach((point) => {
|
||||||
|
minLat = Math.min(minLat, point.lat);
|
||||||
|
maxLat = Math.max(maxLat, point.lat);
|
||||||
|
minLon = Math.min(minLon, point.lon);
|
||||||
|
maxLon = Math.max(maxLon, point.lon);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const latDelta = Math.max(maxLat - minLat, 0.01) * 1.2; // Padding 20%
|
||||||
|
const lonDelta = Math.max(maxLon - minLon, 0.01) * 1.2;
|
||||||
|
|
||||||
|
console.log("Map region:", {
|
||||||
|
minLat,
|
||||||
|
maxLat,
|
||||||
|
minLon,
|
||||||
|
maxLon,
|
||||||
|
latDelta,
|
||||||
|
lonDelta,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
latitude: (minLat + maxLat) / 2,
|
||||||
|
longitude: (minLon + maxLon) / 2,
|
||||||
|
latitudeDelta: latDelta,
|
||||||
|
longitudeDelta: lonDelta,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider style={styles.container}>
|
<SafeAreaProvider style={styles.container}>
|
||||||
<MapView
|
<MapView
|
||||||
|
onMapReady={handleMapReady}
|
||||||
|
onPoiClick={(point) => {
|
||||||
|
console.log("Poi clicked: ", point.nativeEvent);
|
||||||
|
}}
|
||||||
style={styles.map}
|
style={styles.map}
|
||||||
initialRegion={{
|
initialRegion={{
|
||||||
latitude: 15.70581,
|
latitude: 15.70581,
|
||||||
@@ -34,20 +127,65 @@ export default function HomeScreen() {
|
|||||||
latitudeDelta: 2,
|
latitudeDelta: 2,
|
||||||
longitudeDelta: 2,
|
longitudeDelta: 2,
|
||||||
}}
|
}}
|
||||||
mapType="none"
|
region={getMapRegion()}
|
||||||
|
// userInterfaceStyle="dark"
|
||||||
|
showsBuildings={false}
|
||||||
|
showsIndoors={false}
|
||||||
|
loadingEnabled={true}
|
||||||
|
mapType="standard"
|
||||||
>
|
>
|
||||||
|
{trackPoints &&
|
||||||
|
trackPoints.length > 0 &&
|
||||||
|
trackPoints.map((point, index) => {
|
||||||
|
// console.log(`Rendering circle ${index}:`, point);
|
||||||
|
return (
|
||||||
|
<Circle
|
||||||
|
key={index}
|
||||||
|
center={{
|
||||||
|
latitude: point.lat,
|
||||||
|
longitude: point.lon,
|
||||||
|
}}
|
||||||
|
zIndex={50}
|
||||||
|
radius={20} // Tăng từ 50 → 1000m
|
||||||
|
fillColor="rgba(241, 12, 65, 0.8)" // Tăng opacity từ 0.06 → 0.8
|
||||||
|
strokeColor="rgba(221, 240, 15, 0.8)"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{gpsData && (
|
{gpsData && (
|
||||||
<Marker
|
<Marker
|
||||||
coordinate={{
|
coordinate={{
|
||||||
latitude: gpsData.lat,
|
latitude: gpsData.lat,
|
||||||
longitude: gpsData.lon,
|
longitude: gpsData.lon,
|
||||||
}}
|
}}
|
||||||
title="Device Location"
|
title="Tàu của mình"
|
||||||
description={`Lat: ${gpsData.lat}, Lon: ${gpsData.lon}`}
|
zIndex={100}
|
||||||
/>
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
rotate: `${
|
||||||
|
typeof gpsData.h === "number" && !isNaN(gpsData.h)
|
||||||
|
? gpsData.h
|
||||||
|
: 0
|
||||||
|
}deg`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExpoImage
|
||||||
|
source={getShipIcon(alarmData?.level || 0, gpsData.fishing)}
|
||||||
|
style={{ width: 32, height: 32 }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Marker>
|
||||||
)}
|
)}
|
||||||
</MapView>
|
</MapView>
|
||||||
<TouchableOpacity style={styles.button} onPress={getGpsData}>
|
<TouchableOpacity style={styles.button} onPress={handleMapReady}>
|
||||||
<Text style={styles.buttonText}>Get GPS Data</Text>
|
<Text style={styles.buttonText}>Get GPS Data</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Toast from "react-native-toast-message";
|
|||||||
|
|
||||||
import { setRouterInstance } from "@/config/auth";
|
import { setRouterInstance } from "@/config/auth";
|
||||||
import { useColorScheme } from "@/hooks/use-color-scheme";
|
import { useColorScheme } from "@/hooks/use-color-scheme";
|
||||||
|
import "../global.css";
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export default function LoginScreen() {
|
|||||||
console.log("Login thành công với data:", response.data);
|
console.log("Login thành công với data:", response.data);
|
||||||
if (response?.data.token) {
|
if (response?.data.token) {
|
||||||
// Lưu token vào storage nếu cần (thêm logic này sau)
|
// Lưu token vào storage nếu cần (thêm logic này sau)
|
||||||
console.log("Login Token:", response.data.token);
|
console.log("Login Token ");
|
||||||
|
|
||||||
await setStorageItem(TOKEN, response.data.token);
|
await setStorageItem(TOKEN, response.data.token);
|
||||||
console.log("Token:", response.data.token);
|
console.log("Token:", response.data.token);
|
||||||
|
|||||||
BIN
assets/icons/alarm_icon.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/icons/exclamation.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
assets/icons/marker.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/icons/ship_alarm.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
assets/icons/ship_alarm_2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/icons/ship_alarm_fishing.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/icons/ship_online.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets/icons/ship_online_fishing.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
assets/icons/ship_undefine.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/icons/ship_warning.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
assets/icons/ship_warning_fishing.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
assets/icons/sos_icon.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
assets/icons/warning_icon.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
9
babel.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = function (api) {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
presets: [
|
||||||
|
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||||
|
"nativewind/babel",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
import { StyleSheet, Text, type TextProps } from 'react-native';
|
import { StyleSheet, Text, type TextProps } from "react-native";
|
||||||
|
|
||||||
import { useThemeColor } from '@/hooks/use-theme-color';
|
import { useThemeColor } from "@/hooks/use-theme-color";
|
||||||
|
|
||||||
export type ThemedTextProps = TextProps & {
|
export type ThemedTextProps = TextProps & {
|
||||||
lightColor?: string;
|
lightColor?: string;
|
||||||
darkColor?: string;
|
darkColor?: string;
|
||||||
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
|
type?: "default" | "title" | "defaultSemiBold" | "subtitle" | "link";
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ThemedText({
|
export function ThemedText({
|
||||||
style,
|
style,
|
||||||
|
className = "",
|
||||||
lightColor,
|
lightColor,
|
||||||
darkColor,
|
darkColor,
|
||||||
type = 'default',
|
type = "default",
|
||||||
...rest
|
...rest
|
||||||
}: ThemedTextProps) {
|
}: ThemedTextProps) {
|
||||||
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
|
const color = useThemeColor({ light: lightColor, dark: darkColor }, "text");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
|
className={className}
|
||||||
style={[
|
style={[
|
||||||
{ color },
|
{ color },
|
||||||
type === 'default' ? styles.default : undefined,
|
type === "default" ? styles.default : undefined,
|
||||||
type === 'title' ? styles.title : undefined,
|
type === "title" ? styles.title : undefined,
|
||||||
type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
|
type === "defaultSemiBold" ? styles.defaultSemiBold : undefined,
|
||||||
type === 'subtitle' ? styles.subtitle : undefined,
|
type === "subtitle" ? styles.subtitle : undefined,
|
||||||
type === 'link' ? styles.link : undefined,
|
type === "link" ? styles.link : undefined,
|
||||||
style,
|
style,
|
||||||
]}
|
]}
|
||||||
{...rest}
|
{...rest}
|
||||||
@@ -41,20 +44,20 @@ const styles = StyleSheet.create({
|
|||||||
defaultSemiBold: {
|
defaultSemiBold: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
lineHeight: 24,
|
lineHeight: 24,
|
||||||
fontWeight: '600',
|
fontWeight: "600",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
lineHeight: 32,
|
lineHeight: 32,
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
link: {
|
link: {
|
||||||
lineHeight: 30,
|
lineHeight: 30,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: '#0a7ea4',
|
color: "#0a7ea4",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
import { View, type ViewProps } from 'react-native';
|
import { View, type ViewProps } from "react-native";
|
||||||
|
|
||||||
import { useThemeColor } from '@/hooks/use-theme-color';
|
import { useThemeColor } from "@/hooks/use-theme-color";
|
||||||
|
|
||||||
export type ThemedViewProps = ViewProps & {
|
export type ThemedViewProps = ViewProps & {
|
||||||
lightColor?: string;
|
lightColor?: string;
|
||||||
darkColor?: string;
|
darkColor?: string;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
export function ThemedView({
|
||||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
style,
|
||||||
|
className = "",
|
||||||
|
lightColor,
|
||||||
|
darkColor,
|
||||||
|
...otherProps
|
||||||
|
}: ThemedViewProps) {
|
||||||
|
const backgroundColor = useThemeColor(
|
||||||
|
{ light: lightColor, dark: darkColor },
|
||||||
|
"background"
|
||||||
|
);
|
||||||
|
|
||||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
return (
|
||||||
|
<View
|
||||||
|
className={className}
|
||||||
|
style={[{ backgroundColor }, style]}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ const api: AxiosInstance = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("🚀 Axios baseURL:", api.defaults.baseURL);
|
||||||
|
|
||||||
// Interceptor cho request (thêm token nếu cần)
|
// Interceptor cho request (thêm token nếu cần)
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
async (config) => {
|
async (config) => {
|
||||||
// Thêm auth token nếu có
|
// Thêm auth token nếu có
|
||||||
const token = await getStorageItem(TOKEN);
|
const token = await getStorageItem(TOKEN);
|
||||||
console.log("Request Token: ", token);
|
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `${token}`;
|
config.headers.Authorization = `${token}`;
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,14 @@ api.interceptors.response.use(
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
// Xử lý lỗi chung, ví dụ: redirect login nếu 401
|
|
||||||
|
if (!error.response) {
|
||||||
|
const networkErrorMsg =
|
||||||
|
error.message || "Network error - please check connection";
|
||||||
|
showToastError("Lỗi kết nối", networkErrorMsg);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
const { status, statusText, data } = error.response;
|
const { status, statusText, data } = error.response;
|
||||||
|
|
||||||
// Ưu tiên: codeMessage → backend message → statusText
|
// Ưu tiên: codeMessage → backend message → statusText
|
||||||
@@ -62,8 +69,10 @@ api.interceptors.response.use(
|
|||||||
data?.message ||
|
data?.message ||
|
||||||
statusText ||
|
statusText ||
|
||||||
"Unknown error";
|
"Unknown error";
|
||||||
showToastError(errMsg);
|
|
||||||
if (error.response?.status === 401) {
|
showToastError(`Lỗi ${status}`, errMsg);
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
// handle401();
|
// handle401();
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { api } from "@/config";
|
import { api } from "@/config";
|
||||||
import { API_GET_GPS } from "@/constants";
|
import {
|
||||||
|
API_GET_ALARMS,
|
||||||
|
API_GET_GPS,
|
||||||
|
API_PATH_SHIP_TRACK_POINTS,
|
||||||
|
} from "@/constants";
|
||||||
|
|
||||||
export async function fetchGpsData() {
|
export async function queryGpsData() {
|
||||||
return api.get<Model.GPSResonse>(API_GET_GPS);
|
return api.get<Model.GPSResonse>(API_GET_GPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function queryAlarm() {
|
||||||
|
return api.get<Model.AlarmResponse>(API_GET_ALARMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryTrackPoints() {
|
||||||
|
return api.get<Model.ShipTrackPoint[]>(API_PATH_SHIP_TRACK_POINTS);
|
||||||
|
}
|
||||||
|
|||||||
19
controller/typings.d.ts
vendored
@@ -14,4 +14,23 @@ declare namespace Model {
|
|||||||
h: number;
|
h: number;
|
||||||
fishing: boolean;
|
fishing: boolean;
|
||||||
}
|
}
|
||||||
|
interface Alarm {
|
||||||
|
name: string;
|
||||||
|
t: number; // timestamp (epoch seconds)
|
||||||
|
level: number;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlarmResponse {
|
||||||
|
alarms: Alarm[];
|
||||||
|
level: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShipTrackPoint {
|
||||||
|
time: number;
|
||||||
|
lon: number;
|
||||||
|
lat: number;
|
||||||
|
s: number;
|
||||||
|
h: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
global.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
6
metro.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const { getDefaultConfig } = require("expo/metro-config");
|
||||||
|
const { withNativeWind } = require("nativewind/metro");
|
||||||
|
|
||||||
|
const config = getDefaultConfig(__dirname);
|
||||||
|
|
||||||
|
module.exports = withNativeWind(config, { input: "./global.css" });
|
||||||
1
nativewind-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="nativewind/types" />
|
||||||
772
package-lock.json
generated
@@ -29,13 +29,14 @@
|
|||||||
"expo-symbols": "~1.0.7",
|
"expo-symbols": "~1.0.7",
|
||||||
"expo-system-ui": "~6.0.8",
|
"expo-system-ui": "~6.0.8",
|
||||||
"expo-web-browser": "~15.0.8",
|
"expo-web-browser": "~15.0.8",
|
||||||
|
"nativewind": "^4.2.1",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-gesture-handler": "~2.28.0",
|
"react-native-gesture-handler": "~2.28.0",
|
||||||
"react-native-maps": "^1.20.1",
|
"react-native-maps": "^1.20.1",
|
||||||
"react-native-reanimated": "~4.1.1",
|
"react-native-reanimated": "~4.1.1",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "5.4.0",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-toast-message": "^2.3.3",
|
"react-native-toast-message": "^2.3.3",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
@@ -45,6 +46,8 @@
|
|||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
"eslint-config-expo": "~10.0.0",
|
"eslint-config-expo": "~10.0.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|||||||
30
services/map_service.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import shipAlarmIcon from "../assets/icons/ship_alarm.png";
|
||||||
|
import shipAlarmFishingIcon from "../assets/icons/ship_alarm_fishing.png";
|
||||||
|
import shipOnlineIcon from "../assets/icons/ship_online.png";
|
||||||
|
import shipOnlineFishingIcon from "../assets/icons/ship_online_fishing.png";
|
||||||
|
import shipUndefineIcon from "../assets/icons/ship_undefine.png";
|
||||||
|
import shipWarningIcon from "../assets/icons/ship_warning.png";
|
||||||
|
import shipWarningFishingIcon from "../assets/icons/ship_warning_fishing.png";
|
||||||
|
import shipSosIcon from "../assets/icons/sos_icon.png";
|
||||||
|
|
||||||
|
export const getShipIcon = (type: number, isFishing: boolean) => {
|
||||||
|
console.log("type, isFishing", type, isFishing);
|
||||||
|
|
||||||
|
if (type === 1 && !isFishing) {
|
||||||
|
return shipWarningIcon;
|
||||||
|
} else if (type === 2 && !isFishing) {
|
||||||
|
return shipAlarmIcon;
|
||||||
|
} else if (type === 0 && !isFishing) {
|
||||||
|
return shipOnlineIcon;
|
||||||
|
} else if (type === 1 && isFishing) {
|
||||||
|
return shipWarningFishingIcon;
|
||||||
|
} else if (type === 2 && isFishing) {
|
||||||
|
return shipAlarmFishingIcon;
|
||||||
|
} else if (type === 0 && isFishing) {
|
||||||
|
return shipOnlineFishingIcon;
|
||||||
|
} else if (type === 3) {
|
||||||
|
return shipSosIcon;
|
||||||
|
} else {
|
||||||
|
return shipUndefineIcon;
|
||||||
|
}
|
||||||
|
};
|
||||||
13
tailwind.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./app/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"./components/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"./screens/**/*.{js,jsx,ts,tsx}",
|
||||||
|
],
|
||||||
|
presets: [require("nativewind/preset")],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".expo/types/**/*.ts",
|
".expo/types/**/*.ts",
|
||||||
"expo-env.d.ts"
|
"expo-env.d.ts",
|
||||||
|
"nativewind-env.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||