| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- //the size of the TabView will follow its parent-container's size.
- import * as React from 'react';
- import * as Location from 'expo-location';
- import {
- View,
- Text,
- useWindowDimensions,
- Dimensions,
- StyleSheet,
- Image,
- ImageSourcePropType,
- ActivityIndicator,
- Pressable
- } from 'react-native';
- import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
- import { FlashList } from '@shopify/flash-list';
- import { useEffect, useState } from 'react';
- import { calculateDistance } from './distanceCalculator';
- import { router } from 'expo-router';
- export interface TabItem {
- imgURL: ImageSourcePropType;
- date: string;
- time: string;
- chargeStationName: string;
- chargeStationAddress: string;
- stationLat: string | number;
- stationLng: string | number;
- distance: string;
- format_order_id: string;
- actual_total_power?: number;
- actual_end_time?: string;
- actual_fee?: number;
- current_price?: number;
- }
- interface TabViewComponentProps {
- titles: string[];
- tabItems: TabItem[];
- completedReservationTabItems: TabItem[];
- isLoading?: boolean;
- }
- const TabViewComponent: React.FC<TabViewComponentProps> = ({
- titles,
- isLoading,
- tabItems,
- completedReservationTabItems
- }) => {
- const layout = useWindowDimensions();
- const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
- useEffect(() => {
- const getCurrentLocation = async () => {
- let { status } = await Location.requestForegroundPermissionsAsync();
- if (status !== 'granted') {
- console.error('Permission to access location was denied');
- return;
- }
- let location = await Location.getLastKnownPositionAsync({});
- setCurrentLocation(location);
- };
- getCurrentLocation();
- }, []);
- // 修复 FirstRoute 组件
- const FirstRoute = ({ tabItems, isLoading, currentLocation }: {
- tabItems: TabItem[];
- isLoading?: boolean;
- currentLocation: Location.LocationObject | null
- }) => (
- <View style={{ flex: 1, backgroundColor: 'white' }}>
- {isLoading ? (
- <View className="items-center justify-center flex-1">
- <ActivityIndicator color="#34657b" />
- </View>
- ) : (
- <FlashList
- nestedScrollEnabled={true}
- data={tabItems.filter((item) => item?.actual_total_power && item?.actual_total_power !== 0)}
- renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
- keyExtractor={(item, index) => index.toString()}
- />
- )}
- </View>
- );
- // 修复 SecondRoute 组件
- const SecondRoute = ({ completedReservationTabItems, currentLocation }: {
- completedReservationTabItems: TabItem[];
- currentLocation: Location.LocationObject | null
- }) => (
- <View style={{ flex: 1, backgroundColor: 'white' }}>
- <FlashList
- nestedScrollEnabled={true}
- data={completedReservationTabItems.filter(
- (item) => item.actual_total_power && item.actual_total_power > 1
- )}
- renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
- keyExtractor={(item, index) => index.toString()}
- />
- </View>
- );
- // 更新 renderScene
- const renderScene = SceneMap({
- firstRoute: () => <FirstRoute tabItems={tabItems} isLoading={isLoading} currentLocation={currentLocation} />,
- secondRoute: () => <SecondRoute completedReservationTabItems={completedReservationTabItems} currentLocation={currentLocation} />
- });
- const routes = [
- { key: 'firstRoute', title: titles[0] },
- { key: 'secondRoute', title: titles[1] }
- ];
- const [index, setIndex] = React.useState(0);
- const renderTabBar = (props: any) => (
- <TabBar
- {...props}
- activeColor ='#025c72'
- inactiveColor='#888888'
- indicatorStyle={{
- backgroundColor: '#025c72',
- }}
- style={{
- backgroundColor: 'white',
- borderColor: '#DBE4E8',
- elevation: 0,
- marginHorizontal: 15,
- borderBottomWidth: 0.5
- }}
- labelStyle={{
- fontSize: 20, // 文字大小
- fontWeight: 700, // 字重
- }}
- />
- );
- return (
- <TabView
- navigationState={{ index, routes }}
- renderTabBar={renderTabBar}
- renderScene={renderScene}
- onIndexChange={setIndex}
- initialLayout={{ width: layout.width }}
- commonOptions={{
- labelStyle: {
- fontSize: 20, // 文字大小
- fontWeight: 700, // 字重
- }
- }}
- lazy
- />
- );
- };
- const TabItem = ({ item, currentLocation }: { item: TabItem; currentLocation: Location.LocationObject | null }) => {
- const [distance, setDistance] = useState<number | null>(null);
- useEffect(() => {
- const getDistance = async () => {
- if (currentLocation) {
- const result = await calculateDistance(
- Number(item.stationLat),
- Number(item.stationLng),
- currentLocation
- );
- setDistance(result);
- }
- };
- getDistance();
- }, [currentLocation, item.stationLat, item.stationLng]);
- const formatDistance = (distanceInMeters: number): string => {
- if (distanceInMeters < 1000) {
- return `${Math.round(distanceInMeters)}米`;
- } else {
- const distanceInKm = distanceInMeters / 1000;
- return `${distanceInKm.toFixed(1)}公里`;
- }
- };
- return (
- <Pressable
- onPress={() => {
- console.log(item.format_order_id);
- }}
- >
- <View style={styles.container}>
- <Image style={styles.image} source={item.imgURL} />
- <View className="flex flex-col gap-2 mr-2">
- <Text
- style={{
- fontWeight: '700',
- color: '#02677D',
- fontSize: 16
- }}
- >
- {`${item.date}日 - ${item.time}至${item.actual_end_time}`}
- </Text>
- <View className="flex flex-row justify-between space-x-2">
- <Text
- style={{
- fontWeight: '400',
- fontSize: 14,
- color: '#222222'
- }}
- >
- 已充電度數:{' '}
- {item.actual_total_power
- ? item.actual_total_power % 1 === 0
- ? item.actual_total_power
- : item.actual_total_power.toFixed(1)
- : ''}
- </Text>
- <Text
- style={{
- fontWeight: '400',
- fontSize: 14,
- color: '#222222'
- }}
- >
- 應付金額:{' '}
- {item.actual_fee !== undefined && item.actual_fee !== null
- ? item.actual_fee <= 0
- ? '$0'
- : item.actual_fee % 1 === 0
- ? `$${item.actual_fee}`
- : `$${item.actual_fee.toFixed(1)}`
- : ''}
- </Text>
- </View>
- <View className="flex flex-row space-x-2">
- <Text
- style={{
- fontWeight: '400',
- fontSize: 14,
- color: '#222222'
- }}
- >
- 每度電金額: ${item.current_price}
- </Text>
- </View>
- <Text
- style={{
- fontWeight: '400',
- fontSize: 14,
- color: '#888888'
- }}
- >
- {item.chargeStationName}
- </Text>
- </View>
- </View>
- </Pressable>
- );
- };
- export default TabViewComponent;
- const styles = StyleSheet.create({
- container: {
- flexDirection: 'row',
- width: '100%',
- flex: 1,
- alignItems: 'center'
- },
- image: {
- width: 100,
- height: 100,
- margin: 15,
- borderRadius: 10
- },
- textContainer: {
- flex: 1,
- flexDirection: 'column',
- gap: 8,
- marginTop: 20,
- marginRight: 8 // Add right margin to prevent text from touching the edge
- }
- });
|