notificationTabViewComponent.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // component/global/notificationTabViewComponent.tsx
  2. //the size of the TabView will follow its parent-container's size.
  3. import * as React from 'react';
  4. import { View, Text, useWindowDimensions, StyleSheet, ScrollView, ActivityIndicator, Pressable } from 'react-native';
  5. import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
  6. import { formatToChineseDateTime } from '../../util/lib';
  7. import { useCallback, useMemo, useEffect, useState } from 'react';
  8. import { notificationStorage } from '../notificationStorage';
  9. import { router, useFocusEffect } from 'expo-router';
  10. import { useTranslation } from '../../util/hooks/useTranslation';
  11. export interface TabItem {
  12. title: string;
  13. description: string;
  14. date: string;
  15. }
  16. interface TabViewComponentProps {
  17. titles: string[];
  18. reservationAfter2025: any;
  19. passingThisPromotionToBell: any;
  20. }
  21. const NotificationRow = ({
  22. promotionObj,
  23. index,
  24. viewedNotifications
  25. }: {
  26. promotionObj: any;
  27. index: number;
  28. viewedNotifications: any;
  29. }) => {
  30. const { t, getCurrentLanguageConfig } = useTranslation(); // 使用翻译钩子
  31. const isEn = getCurrentLanguageConfig()?.code === 'en';
  32. const isViewed = viewedNotifications.some(
  33. (notification: any) => notification.id === promotionObj.id && notification.type === 'promotion'
  34. );
  35. const handlePress = async () => {
  36. // Mark promotion as viewed
  37. await notificationStorage.markAsViewed(promotionObj.id, 'promotion');
  38. router.push({
  39. pathname: '/notificationDetailPage',
  40. params: { promotion: JSON.stringify(promotionObj) }
  41. });
  42. };
  43. return (
  44. <Pressable
  45. onPress={handlePress}
  46. style={{ opacity: isViewed ? 0.5 : 1 }}
  47. className={`flex flex-col w-full bg-white space-y-2 p-2 ${index % 2 === 0 ? 'bg-[#e7f2f8]' : 'bg-white'}`}
  48. >
  49. <View className="flex flex-row justify-between">
  50. <Text className="font-[500] text-base">{isEn?promotionObj.title_en:promotionObj.title}</Text>
  51. <Text>{formatToChineseDateTime(promotionObj.createdAt)}</Text>
  52. </View>
  53. <View>
  54. <Text numberOfLines={2} ellipsizeMode="tail" className="text-sm">
  55. {isEn?promotionObj.text_en:promotionObj.text}
  56. </Text>
  57. </View>
  58. </Pressable>
  59. );
  60. };
  61. const ReservationRow = ({
  62. reservationObj,
  63. index,
  64. viewedNotifications
  65. }: {
  66. reservationObj: any;
  67. index: number;
  68. viewedNotifications: any;
  69. }) => {
  70. const { t } = useTranslation();
  71. const isViewed = viewedNotifications.some(
  72. (notification: any) => notification?.id === reservationObj?.id && notification.type === 'reservation'
  73. );
  74. const handlePress = async () => {
  75. await notificationStorage.markAsViewed(reservationObj.id, 'reservation');
  76. if (reservationObj.status.id === '7') {
  77. router.push({ pathname: '/chargingPage' });
  78. } else {
  79. router.push({ pathname: '/chargingDetailPage', params: { promotion: JSON.stringify(reservationObj) } });
  80. }
  81. };
  82. const title =
  83. reservationObj.status.id === '8' || reservationObj.status.id === '13'
  84. ? t('notifications.chargingIn.charging_completed')
  85. : reservationObj.status.id === '7'
  86. ? t('notifications.chargingIn.charging_in_progress')
  87. : '';
  88. const text =
  89. reservationObj.status.id === '8' || reservationObj.status.id === '13'
  90. ? t('notifications.chargingIn.charging_completed_message')
  91. : reservationObj.status.id === '7'
  92. ? t('notifications.chargingIn.charging_in_progress_message', { soc: reservationObj.Soc })
  93. : '';
  94. return (
  95. <Pressable
  96. onPress={handlePress}
  97. style={{ opacity: isViewed ? 0.5 : 1 }}
  98. className={`flex flex-col w-full bg-white space-y-2 p-2 ${index % 2 === 0 ? 'bg-[#e7f2f8]' : 'bg-white'}`}
  99. >
  100. <View className="flex flex-row justify-between">
  101. <Text className={`font-[400] text-base ${reservationObj.status.id === '7' ? 'font-[700]' : ''}`}>
  102. {title}
  103. </Text>
  104. <Text>{formatToChineseDateTime(reservationObj.createdAt)}</Text>
  105. </View>
  106. <View>
  107. <Text numberOfLines={2} ellipsizeMode="tail" className="text-sm">
  108. {text}
  109. </Text>
  110. </View>
  111. </Pressable>
  112. );
  113. };
  114. const FirstRoute = ({ loading, reservationAfter2025 }: { loading: boolean; reservationAfter2025: any }) => {
  115. const { t } = useTranslation();
  116. const [viewedNotifications, setViewedNotifications] = useState([]);
  117. useFocusEffect(
  118. useCallback(() => {
  119. const getViewedNotifications = async () => {
  120. const notifications = await notificationStorage.getViewedNotifications();
  121. setViewedNotifications(notifications as any);
  122. };
  123. getViewedNotifications();
  124. }, [])
  125. );
  126. return (
  127. <ScrollView
  128. style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
  129. contentContainerStyle={{ paddingBottom: 150 }}
  130. >
  131. <View className="flex-1 flex-col">
  132. {loading ? (
  133. <View className="items-center justify-center">
  134. <ActivityIndicator />
  135. </View>
  136. ) : (
  137. <View>
  138. <View>
  139. {reservationAfter2025.length === 0 ? (
  140. <Text className="pl-4">{t('notifications.chargingIn.no_charging_info')}</Text>
  141. ) : (
  142. reservationAfter2025
  143. .sort(
  144. (a: any, b: any) =>
  145. new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  146. )
  147. .filter(
  148. (reservationObj: any) =>
  149. reservationObj.status.id === '7' ||
  150. reservationObj.status.id === '8' ||
  151. reservationObj.status.id === '13'
  152. )
  153. .slice(0, 30)
  154. .map((reservationObj: any, index: any) => (
  155. <ReservationRow
  156. reservationObj={reservationObj}
  157. index={index}
  158. key={reservationObj.id}
  159. viewedNotifications={viewedNotifications}
  160. />
  161. ))
  162. )}
  163. </View>
  164. </View>
  165. )}
  166. </View>
  167. </ScrollView>
  168. );
  169. };
  170. const SecondRoute = ({ promotions, loading }: { promotions: TabItem[]; loading: boolean }) => {
  171. const { t } = useTranslation();
  172. const [viewedNotifications, setViewedNotifications] = useState([]);
  173. useFocusEffect(
  174. useCallback(() => {
  175. const getViewedNotifications = async () => {
  176. const notifications = await notificationStorage.getViewedNotifications();
  177. setViewedNotifications(notifications as any);
  178. };
  179. getViewedNotifications();
  180. }, [])
  181. );
  182. return (
  183. <ScrollView
  184. style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
  185. contentContainerStyle={{ paddingBottom: 120 }}
  186. >
  187. <View className="flex-1 flex-col">
  188. {loading ? (
  189. <View className="items-center justify-center">
  190. <ActivityIndicator />
  191. </View>
  192. ) : (
  193. <View>
  194. <View>
  195. {promotions.length === 0 ? (
  196. <Text className="pl-4">{t('notifications.no_promotions')}</Text>
  197. ) : (
  198. promotions
  199. .sort(
  200. (a: any, b: any) =>
  201. new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  202. )
  203. .slice(0, 30)
  204. .map((promotionObj: any, index: any) => (
  205. <NotificationRow
  206. promotionObj={promotionObj}
  207. index={index}
  208. key={promotionObj.id}
  209. viewedNotifications={viewedNotifications}
  210. />
  211. ))
  212. )}
  213. </View>
  214. </View>
  215. )}
  216. </View>
  217. </ScrollView>
  218. );
  219. };
  220. const NotificationTabView: React.FC<TabViewComponentProps> = ({
  221. titles,
  222. reservationAfter2025,
  223. passingThisPromotionToBell
  224. }) => {
  225. const layout = useWindowDimensions();
  226. const [loading, setLoading] = useState(true);
  227. const renderScene = useCallback(
  228. ({ route }: { route: any }) => {
  229. switch (route.key) {
  230. case 'firstRoute':
  231. return <FirstRoute loading={loading} reservationAfter2025={reservationAfter2025} />;
  232. case 'secondRoute':
  233. return <SecondRoute promotions={passingThisPromotionToBell} loading={loading} />;
  234. default:
  235. return null;
  236. }
  237. },
  238. [reservationAfter2025, passingThisPromotionToBell]
  239. );
  240. useEffect(() => {
  241. setLoading(false)
  242. }, [reservationAfter2025, passingThisPromotionToBell]);
  243. const [routes] = React.useState([
  244. { key: 'firstRoute', title: titles[0] },
  245. { key: 'secondRoute', title: titles[1] }
  246. ]);
  247. const [index, setIndex] = React.useState(0);
  248. const renderTabBar = (props: any) => (
  249. <TabBar
  250. {...props}
  251. indicatorStyle={{
  252. backgroundColor: '#025c72'
  253. }}
  254. style={{
  255. backgroundColor: 'white',
  256. borderColor: '#DBE4E8',
  257. elevation: 0,
  258. marginHorizontal: 15,
  259. borderBottomWidth: 0.5
  260. }}
  261. />
  262. );
  263. return (
  264. <TabView
  265. navigationState={{ index, routes }}
  266. renderTabBar={renderTabBar}
  267. renderScene={renderScene}
  268. onIndexChange={setIndex}
  269. initialLayout={{ width: layout.width }}
  270. commonOptions={{
  271. label: ({ route, focused }) => (
  272. <Text
  273. style={{
  274. color: focused ? '#025c72' : '#888888',
  275. fontWeight: focused ? '900' : 'thin',
  276. fontSize: 20
  277. }}
  278. >
  279. {route.title}
  280. </Text>
  281. )
  282. }}
  283. lazy
  284. />
  285. );
  286. };
  287. export default NotificationTabView;
  288. const styles = StyleSheet.create({
  289. container: { flexDirection: 'row' },
  290. image: { width: 100, height: 100, margin: 15, borderRadius: 10 },
  291. textContainer: { flexDirection: 'column', gap: 8, marginTop: 20 },
  292. floatingButton: {
  293. elevation: 5,
  294. shadowColor: '#000',
  295. shadowOffset: { width: 0, height: 2 },
  296. shadowOpacity: 0.25,
  297. shadowRadius: 3.84
  298. }
  299. });