tabView.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. //the size of the TabView will follow its parent-container's size.
  2. import * as React from 'react';
  3. import * as Location from 'expo-location';
  4. import {
  5. View,
  6. Text,
  7. useWindowDimensions,
  8. StyleSheet,
  9. Image,
  10. ImageSourcePropType,
  11. ActivityIndicator,
  12. Pressable
  13. } from 'react-native';
  14. import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
  15. import { FlashList } from '@shopify/flash-list';
  16. import { useEffect, useState } from 'react';
  17. import { calculateDistance } from './distanceCalculator';
  18. import { router } from 'expo-router';
  19. export interface TabItem {
  20. imgURL: ImageSourcePropType;
  21. date: string;
  22. time: string;
  23. chargeStationName: string;
  24. chargeStationAddress: string;
  25. stationLat: string | number;
  26. stationLng: string | number;
  27. distance: string;
  28. format_order_id: string;
  29. }
  30. interface TabViewComponentProps {
  31. titles: string[];
  32. tabItems: TabItem[];
  33. completedReservationTabItems: TabItem[];
  34. isLoading?: boolean;
  35. }
  36. const TabViewComponent: React.FC<TabViewComponentProps> = ({
  37. titles,
  38. isLoading,
  39. tabItems,
  40. completedReservationTabItems
  41. }) => {
  42. const layout = useWindowDimensions();
  43. const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
  44. useEffect(() => {
  45. const getCurrentLocation = async () => {
  46. let { status } = await Location.requestForegroundPermissionsAsync();
  47. if (status !== 'granted') {
  48. console.error('Permission to access location was denied');
  49. return;
  50. }
  51. let location = await Location.getLastKnownPositionAsync({});
  52. setCurrentLocation(location);
  53. };
  54. getCurrentLocation();
  55. }, []);
  56. // Memoize the route components
  57. // const FirstRoute = React.useMemo(
  58. // () =>
  59. // React.memo(() => (
  60. // <View style={{ flex: 1, backgroundColor: 'white' }}>
  61. // {isLoading ? (
  62. // <View className="items-center justify-center flex-1">
  63. // <ActivityIndicator color="#34657b" />
  64. // </View>
  65. // ) : (
  66. // <FlashList
  67. // nestedScrollEnabled={true}
  68. // data={tabItems}
  69. // renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
  70. // estimatedItemSize={10}
  71. // />
  72. // )}
  73. // </View>
  74. // )),
  75. // [isLoading, tabItems]
  76. // );
  77. //i unmemoed it
  78. const FirstRoute = () => (
  79. <View style={{ flex: 1, backgroundColor: 'white' }}>
  80. {isLoading ? (
  81. <View className="items-center justify-center flex-1">
  82. <ActivityIndicator color="#34657b" />
  83. </View>
  84. ) : (
  85. <FlashList
  86. nestedScrollEnabled={true}
  87. data={tabItems}
  88. renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
  89. estimatedItemSize={10}
  90. />
  91. )}
  92. </View>
  93. );
  94. const SecondRoute = React.useMemo(
  95. () =>
  96. React.memo(() => (
  97. <View style={{ flex: 1, backgroundColor: 'white' }}>
  98. <FlashList
  99. nestedScrollEnabled={true}
  100. data={completedReservationTabItems}
  101. renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
  102. estimatedItemSize={10}
  103. />
  104. </View>
  105. )),
  106. [completedReservationTabItems]
  107. );
  108. // Use useCallback for renderScene
  109. // const renderScene = React.useCallback(
  110. // SceneMap({
  111. // firstRoute: FirstRoute,
  112. // secondRoute: SecondRoute
  113. // }),
  114. // [FirstRoute, SecondRoute]
  115. // );
  116. //uncallbacked it
  117. const renderScene = SceneMap({
  118. firstRoute: FirstRoute,
  119. secondRoute: SecondRoute
  120. });
  121. // const routes = React.useMemo(
  122. // () => [
  123. // { key: 'firstRoute', title: titles[0] },
  124. // { key: 'secondRoute', title: titles[1] }
  125. // ],
  126. // [titles]
  127. // );
  128. const routes = [
  129. { key: 'firstRoute', title: titles[0] },
  130. { key: 'secondRoute', title: titles[1] }
  131. ];
  132. const [index, setIndex] = React.useState(0);
  133. // const renderTabBar = React.useCallback(
  134. // (props: any) => (
  135. // <TabBar
  136. // {...props}
  137. // renderLabel={({ route, focused }) => (
  138. // <Text
  139. // style={{
  140. // color: focused ? '#025c72' : '#888888',
  141. // fontWeight: focused ? '900' : 'thin',
  142. // fontSize: 20
  143. // }}
  144. // >
  145. // {route.title}
  146. // </Text>
  147. // )}
  148. // indicatorStyle={{
  149. // backgroundColor: '#025c72'
  150. // }}
  151. // style={{
  152. // backgroundColor: 'white',
  153. // borderColor: '#DBE4E8',
  154. // elevation: 0,
  155. // marginHorizontal: 15,
  156. // borderBottomWidth: 0.5
  157. // }}
  158. // />
  159. // ),
  160. // []
  161. // );
  162. const renderTabBar = (props: any) => (
  163. <TabBar
  164. {...props}
  165. renderLabel={({ route, focused }) => (
  166. <Text
  167. style={{
  168. color: focused ? '#025c72' : '#888888',
  169. fontWeight: focused ? '900' : 'thin',
  170. fontSize: 20
  171. }}
  172. >
  173. {route.title}
  174. </Text>
  175. )}
  176. indicatorStyle={{
  177. backgroundColor: '#025c72'
  178. }}
  179. style={{
  180. backgroundColor: 'white',
  181. borderColor: '#DBE4E8',
  182. elevation: 0,
  183. marginHorizontal: 15,
  184. borderBottomWidth: 0.5
  185. }}
  186. />
  187. );
  188. return (
  189. <TabView
  190. navigationState={{ index, routes }}
  191. renderScene={renderScene}
  192. onIndexChange={setIndex}
  193. initialLayout={{ width: layout.width }}
  194. renderTabBar={renderTabBar}
  195. lazy
  196. />
  197. );
  198. };
  199. // Separate memoedTabItem component
  200. // const TabItem = React.memo(
  201. // ({ item, currentLocation }: { item: TabItem; currentLocation: Location.LocationObject | null }) => {
  202. // const [distance, setDistance] = useState<number | null>(null);
  203. // useEffect(() => {
  204. // const getDistance = async () => {
  205. // if (currentLocation) {
  206. // const result = await calculateDistance(
  207. // Number(item.stationLat),
  208. // Number(item.stationLng),
  209. // currentLocation
  210. // );
  211. // setDistance(result);
  212. // }
  213. // };
  214. // getDistance();
  215. // }, [currentLocation, item.stationLat, item.stationLng]);
  216. // const formatDistance = (distanceInMeters: number): string => {
  217. // if (distanceInMeters < 1000) {
  218. // return `${Math.round(distanceInMeters)}米`;
  219. // } else {
  220. // const distanceInKm = distanceInMeters / 1000;
  221. // return `${distanceInKm.toFixed(1)}公里`;
  222. // }
  223. // };
  224. // return (
  225. // <View style={styles.container}>
  226. // <Image style={styles.image} source={item.imgURL} />
  227. // <View style={styles.textContainer}>
  228. // <Text
  229. // style={{
  230. // fontWeight: '700',
  231. // color: '#02677D',
  232. // fontSize: 20
  233. // }}
  234. // >
  235. // {`${item.date} - ${item.time}`}
  236. // </Text>
  237. // <Text
  238. // style={{
  239. // fontWeight: '400',
  240. // fontSize: 16,
  241. // color: '#222222'
  242. // }}
  243. // >
  244. // {item.chargeStationName}
  245. // </Text>
  246. // <Text
  247. // style={{
  248. // fontWeight: '400',
  249. // fontSize: 16,
  250. // color: '#888888'
  251. // }}
  252. // >
  253. // {item.chargeStationAddress}
  254. // </Text>
  255. // </View>
  256. // <Text
  257. // style={{
  258. // fontWeight: '400',
  259. // fontSize: 16,
  260. // color: '#888888',
  261. // marginTop: 20,
  262. // marginLeft: 16
  263. // }}
  264. // >
  265. // {distance !== null ? formatDistance(distance) : ''}
  266. // </Text>
  267. // </View>
  268. // );
  269. // }
  270. // );
  271. const TabItem = ({ item, currentLocation }: { item: TabItem; currentLocation: Location.LocationObject | null }) => {
  272. const [distance, setDistance] = useState<number | null>(null);
  273. useEffect(() => {
  274. const getDistance = async () => {
  275. if (currentLocation) {
  276. const result = await calculateDistance(
  277. Number(item.stationLat),
  278. Number(item.stationLng),
  279. currentLocation
  280. );
  281. setDistance(result);
  282. }
  283. };
  284. getDistance();
  285. }, [currentLocation, item.stationLat, item.stationLng]);
  286. const formatDistance = (distanceInMeters: number): string => {
  287. if (distanceInMeters < 1000) {
  288. return `${Math.round(distanceInMeters)}米`;
  289. } else {
  290. const distanceInKm = distanceInMeters / 1000;
  291. return `${distanceInKm.toFixed(1)}公里`;
  292. }
  293. };
  294. return (
  295. <Pressable
  296. onPress={() => {
  297. console.log(item.format_order_id);
  298. }}
  299. >
  300. <View style={styles.container}>
  301. <Image style={styles.image} source={item.imgURL} />
  302. <View style={styles.textContainer}>
  303. <Text
  304. style={{
  305. fontWeight: '700',
  306. color: '#02677D',
  307. fontSize: 20
  308. }}
  309. >
  310. {`${item.date} - ${item.time}`}
  311. </Text>
  312. <Text
  313. style={{
  314. fontWeight: '400',
  315. fontSize: 16,
  316. color: '#222222'
  317. }}
  318. >
  319. {item.chargeStationName}
  320. </Text>
  321. <Text
  322. style={{
  323. fontWeight: '400',
  324. fontSize: 16,
  325. color: '#888888'
  326. }}
  327. >
  328. {item.chargeStationAddress}
  329. </Text>
  330. </View>
  331. <Text
  332. style={{
  333. fontWeight: '400',
  334. fontSize: 16,
  335. color: '#888888',
  336. marginTop: 20,
  337. marginLeft: 16
  338. }}
  339. >
  340. {distance !== null ? formatDistance(distance) : ''}
  341. </Text>
  342. </View>
  343. </Pressable>
  344. );
  345. };
  346. export default TabViewComponent;
  347. const styles = StyleSheet.create({
  348. container: { flexDirection: 'row' },
  349. image: { width: 100, height: 100, margin: 15, borderRadius: 10 },
  350. textContainer: { flexDirection: 'column', gap: 8, marginTop: 20 }
  351. });