tabView.tsx 12 KB

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