Browse Source

feat: 添加跳转whatsapp

zengkunsen 4 months ago
parent
commit
ca5f8d8435

+ 2 - 1
app.json

@@ -1,6 +1,6 @@
 {
   "expo": {
-    "name": "Crazycharge",
+    "name": "Easy Charger",
     "slug": "template",
     "version": "1.3.5",
     "orientation": "portrait",
@@ -57,6 +57,7 @@
       "package": "hk.ayf.crazycharge",
       "versionCode": 30,
       "googleServicesFile": "./google-services.json",
+      "cleartextTrafficPermitted": true,
       "usesCleartextTraffic": true,
       "config": {
         "googleMaps": {

+ 87 - 87
app/hooks/usePushNotifications.ts

@@ -1,87 +1,87 @@
-import { useEffect, useState, useRef } from 'react';
-import * as Notifications from 'expo-notifications';
-import * as Device from 'expo-device';
-import Constants from 'expo-constants';
-import { Platform } from 'react-native';
-
-export interface PushNotificationState {
-    notification?: Notifications.Notification;
-    expoPushToken?: Notifications.ExpoPushToken;
-}
-
-export const usePushNotifications = (): PushNotificationState => {
-    const [expoPushToken, setExpoPushToken] = useState<Notifications.ExpoPushToken | undefined>();
-    const [notification, setNotification] = useState<Notifications.Notification | undefined>();
-
-    const notificationListener = useRef<Notifications.Subscription>();
-    const responseListener = useRef<Notifications.Subscription>();
-
-    useEffect(() => {
-        Notifications.setNotificationHandler({
-            handleNotification: async () => ({
-                shouldPlaySound: true,
-                shouldShowAlert: true,
-                shouldSetBadge: true,
-                vibrate: true,
-                shouldShowWhenInForeground: true
-            })
-        });
-
-        registerForPushNotificationAsync().then((token) => {
-            setExpoPushToken(token);
-        });
-
-        notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
-            setNotification(notification);
-        });
-
-        responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
-            console.log(response);
-        });
-
-        return () => {
-            Notifications.removeNotificationSubscription(notificationListener.current!);
-            Notifications.removeNotificationSubscription(responseListener.current!);
-        };
-    }, []);
-
-    return {
-        expoPushToken,
-        notification
-    };
-};
-
-async function registerForPushNotificationAsync() {
-    let token;
-
-    // Check if the device is a physical device, because this only works for physical devices not simulators
-    if (Device.isDevice) {
-        const { status: existingStatus } = await Notifications.getPermissionsAsync();
-        let finalStatus = existingStatus;
-        if (existingStatus !== 'granted') {
-            const { status } = await Notifications.requestPermissionsAsync();
-            finalStatus = status;
-        }
-        if (finalStatus !== 'granted') {
-            alert('需要通知權限才能接收充電狀態更新。請前往「設定」>「通知」中開啟 CrazyCharge 的通知權限。');
-            return;
-        }
-        //if we have permission, then get the token
-        token = await Notifications.getExpoPushTokenAsync({
-            projectId: Constants.expoConfig?.extra?.eas?.projectId
-        });
-
-        if (Platform.OS === 'android') {
-            Notifications.setNotificationChannelAsync('default', {
-                name: 'default',
-                importance: Notifications.AndroidImportance.MAX,
-                vibrationPattern: [0, 250, 250, 250],
-                lightColor: '#FF231F7C'
-            });
-        }
-        // console.log('token', token);
-        return token;
-    } else {
-        console.log('Must use physical device for Push Notifications');
-    }
-}
+import { useEffect, useState, useRef } from 'react';
+import * as Notifications from 'expo-notifications';
+import * as Device from 'expo-device';
+import Constants from 'expo-constants';
+import { Platform } from 'react-native';
+
+export interface PushNotificationState {
+    notification?: Notifications.Notification;
+    expoPushToken?: Notifications.ExpoPushToken;
+}
+
+export const usePushNotifications = (): PushNotificationState => {
+    const [expoPushToken, setExpoPushToken] = useState<Notifications.ExpoPushToken | undefined>();
+    const [notification, setNotification] = useState<Notifications.Notification | undefined>();
+
+    const notificationListener = useRef<Notifications.Subscription>();
+    const responseListener = useRef<Notifications.Subscription>();
+
+    useEffect(() => {
+        Notifications.setNotificationHandler({
+            handleNotification: async () => ({
+                shouldPlaySound: true,
+                shouldShowAlert: true,
+                shouldSetBadge: true,
+                vibrate: true,
+                shouldShowWhenInForeground: true
+            })
+        });
+
+        registerForPushNotificationAsync().then((token) => {
+            setExpoPushToken(token);
+        });
+
+        notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
+            setNotification(notification);
+        });
+
+        responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => {
+            console.log(response);
+        });
+
+        return () => {
+            Notifications.removeNotificationSubscription(notificationListener.current!);
+            Notifications.removeNotificationSubscription(responseListener.current!);
+        };
+    }, []);
+
+    return {
+        expoPushToken,
+        notification
+    };
+};
+
+async function registerForPushNotificationAsync() {
+    let token;
+
+    // Check if the device is a physical device, because this only works for physical devices not simulators
+    if (Device.isDevice) {
+        const { status: existingStatus } = await Notifications.getPermissionsAsync();
+        let finalStatus = existingStatus;
+        if (existingStatus !== 'granted') {
+            const { status } = await Notifications.requestPermissionsAsync();
+            finalStatus = status;
+        }
+        if (finalStatus !== 'granted') {
+            alert('需要通知權限才能接收充電狀態更新。請前往「設定」>「通知」中開啟 CrazyCharge 的通知權限。');
+            return;
+        }
+        //if we have permission, then get the token
+        token = await Notifications.getExpoPushTokenAsync({
+            projectId: Constants.expoConfig?.extra?.eas?.projectId
+        });
+
+        if (Platform.OS === 'android') {
+            Notifications.setNotificationChannelAsync('default', {
+                name: 'default',
+                importance: Notifications.AndroidImportance.MAX,
+                vibrationPattern: [0, 250, 250, 250],
+                lightColor: '#FF231F7C'
+            });
+        }
+        // console.log('token', token);
+        return token;
+    } else {
+        console.log('Must use physical device for Push Notifications');
+    }
+}

+ 5 - 4
component/accountPages/accountMainPageComponent.tsx

@@ -20,6 +20,7 @@ import {
 import { usePushNotifications } from '../../app/hooks/usePushNotifications';
 import useUserInfoStore from '../../providers/userinfo_store';
 import { authenticationService } from '../../service/authService';
+import { handleGoWhatsApp } from '../../util/index'
 
 const AccountMainPageComponent = () => {
     const { user, logout } = useAuth();
@@ -84,7 +85,7 @@ const AccountMainPageComponent = () => {
             }
         ]);
     };
-
+    
     return (
         <SafeAreaView className="flex-1 bg-white dark:bg-[#05181C]" edges={['top', 'left', 'right']}>
             <ScrollView className="flex-1 mx-[5%]" showsVerticalScrollIndicator={false}>
@@ -154,9 +155,9 @@ const AccountMainPageComponent = () => {
                     </View>
 
                     <View className="h-0.5  bg-[#f4f4f4] dark:bg-[#5E6C70]" />
-                    <View className=" py-4 ">
+                    <View className="py-4">
                         <Pressable
-                            onPress={() => router.push('/assistancePage')}
+                            onPress={() => handleGoWhatsApp()}
                             className="flex-row items-center"
                             hitSlop={{
                                 top: 10,
@@ -166,7 +167,7 @@ const AccountMainPageComponent = () => {
                             }}
                         >
                             <QuestionMarkIconSvg isDark={colorScheme == 'dark'} />
-                            <Text className="text-lg pl-2 text-black dark:text-white">排除解難</Text>
+                            <Text className="text-lg pl-2 text-black dark:text-white">WhatsApp</Text>
                         </Pressable>
                     </View>
                     <View className="h-0.5  bg-[#f4f4f4] dark:bg-[#5E6C70]" />

+ 15 - 3
component/global/SVG.tsx

@@ -59,7 +59,14 @@ export const MyBookingIconSvg = () => (
         />
     </Svg>
 );
-
+export const MyWalletSvg = () => (
+    <Svg width="24" height="24" viewBox="0 0 48 48" fill="none">
+        <Path
+            d="M12 38.9999C10.0692 38.9999 8.41987 38.3159 7.0519 36.948C5.68397 35.58 5 33.9307 5 32V16C5 14.0692 5.68397 12.4199 7.0519 11.0519C8.41987 9.68397 10.0692 9 12 9H36C37.9307 9 39.58 9.68397 40.948 11.0519C42.3159 12.4199 42.9999 14.0692 42.9999 16V32C42.9999 33.9307 42.3159 35.58 40.948 36.948C39.58 38.3159 37.9307 38.9999 36 38.9999H12ZM12 16.5H36C36.7589 16.5 37.475 16.609 38.1481 16.8269C38.8211 17.0449 39.4384 17.3757 40 17.8193V16C40 14.9 39.6083 13.9583 38.825 13.175C38.0416 12.3916 37.1 12 36 12H12C10.9 12 9.95828 12.3916 9.17495 13.175C8.39162 13.9583 7.99995 14.9 7.99995 16V17.8193C8.56148 17.3757 9.17878 17.0449 9.85185 16.8269C10.525 16.609 11.241 16.5 12 16.5ZM8.18455 22.4807L30.6461 27.9384C30.9205 28.0051 31.198 28.0083 31.4788 27.9481C31.7596 27.8878 32.0141 27.7641 32.2423 27.5769L39.4423 21.5269C39.1141 20.9243 38.6442 20.4358 38.0327 20.0615C37.4211 19.6871 36.7436 19.4999 36 19.4999H12C11.0564 19.4999 10.2371 19.7762 9.54225 20.3288C8.84738 20.8814 8.39482 21.5987 8.18455 22.4807Z"
+            fill="white"
+        />
+    </Svg>
+);
 export const HomeIconSvg = () => (
     <Svg width="120" height="120" viewBox="0 0 120 120" fill="none">
         <Path
@@ -336,7 +343,12 @@ export const WalletSvg = ({ isDark }: { isDark: boolean }) => (
         />
     </Svg>
 );
-
+export const WhatsAppSvg = () => (
+    <Svg viewBox="0 0 1024 1024" p-id="4986" width="30" height="30" fill="#004E5F">
+        <Path d="M636.016 556.576q7.424 0 55.712 25.152t51.136 30.272q1.152 2.848 1.152 8.576 0 18.848-9.728 43.424-9.152 22.272-40.576 37.44t-58.272 15.136q-32.576 0-108.576-35.424-56-25.728-97.152-67.424t-84.576-105.728q-41.152-61.152-40.576-110.848l0-4.576q1.728-52 42.272-90.272 13.728-12.576 29.728-12.576 3.424 0 10.272 0.864t10.848 0.864q10.848 0 15.136 3.712t8.864 15.712q4.576 11.424 18.848 50.272t14.272 42.848q0 12-19.712 32.864t-19.712 26.56q0 4 2.848 8.576 19.424 41.728 58.272 78.272 32 30.272 86.272 57.728 6.848 4 12.576 4 8.576 0 30.848-27.712t29.728-27.712zM520.016 859.424q72.576 0 139.136-28.576t114.56-76.576 76.576-114.56 28.576-139.136-28.576-139.136-76.576-114.56-114.56-76.576-139.136-28.576-139.136 28.576-114.56 76.576-76.576 114.56-28.576 139.136q0 116 68.576 210.272l-45.152 133.152 138.272-44q90.272 59.424 197.152 59.424zM520.016 69.728q87.424 0 167.136 34.272t137.44 92 92 137.44 34.272 167.136-34.272 167.136-92 137.44-137.44 92-167.136 34.272q-111.424 0-208.576-53.728l-238.272 76.576 77.728-231.424q-61.728-101.728-61.728-222.272 0-87.424 34.272-167.136t92-137.44 137.44-92 167.136-34.272z" p-id="4987">
+        </Path>
+    </Svg>
+);
 export const MyCarSvg = () => (
     <Svg width="48" height="48" viewBox="0 0 48 48" fill="none">
         <Path
@@ -355,7 +367,7 @@ export const ActivitySvg = ({ isDark }: { isDark: boolean }) => (
     </Svg>
 );
 export const DarkModeSvg = ({ isDark }: { isDark: boolean }) => (
-    <Svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <Svg width="24" height="24" viewBox="0 0 24 24" fill="none">
         <Path
             d="M14.3187 1L11.6562 2.58438L12.0219 3.20172L13.6719 2.21969L13.1937 5.28578L13.4891 5.78266L16.25 4.13875L15.8844 3.52141L14.1359 4.55922L14.6094 1.49453L14.3187 1ZM7.39531 2.59281C5.76196 3.5015 4.40077 4.82971 3.4523 6.44029C2.50383 8.05086 2.00248 9.88543 2 11.7545C2 14.5393 3.10625 17.21 5.07538 19.1792C7.04451 21.1483 9.71523 22.2545 12.5 22.2545C14.7703 22.2533 16.979 21.5164 18.7952 20.1541C20.6113 18.7918 21.9369 16.8775 22.5734 14.6983C21.6639 15.6615 20.5676 16.4294 19.3514 16.9548C18.1353 17.4803 16.8248 17.7524 15.5 17.7545C12.9141 17.7545 10.4342 16.7273 8.60571 14.8988C6.77723 13.0703 5.75 10.5904 5.75 8.00453C5.75164 6.07768 6.32418 4.19451 7.39531 2.59281ZM18.8563 5.03266L18.5328 5.80609L20.6 6.66859L17.3984 8.35141L17.1406 8.97016L20.5953 10.4139L20.9188 9.64047L18.7297 8.72641L21.9313 7.04359L22.1891 6.42484L18.8563 5.03266ZM13.4656 8.03266L8.88125 9.67328L9.26094 10.7373L12.1016 9.72016L10.4422 14.3092L10.7469 15.1577L15.5 13.4608L15.1203 12.3967L12.1109 13.4748L13.7703 8.88578L13.4656 8.03266Z"
             fill={isDark ? '#36DFFF' : '#02677D'}

+ 14 - 8
component/homePage/homePage.tsx

@@ -19,7 +19,9 @@ import {
     BellIconSvg,
     HomeIconSvg,
     MyBookingIconSvg,
-    MyVehicleIconSvg,
+    WhatsAppSvg,
+    WalletSvg,
+    MyWalletSvg,
     QrCodeIconSvg,
     VipCodeIconSvg
 } from '../global/SVG';
@@ -32,7 +34,7 @@ import useUserInfoStore from '../../providers/userinfo_store';
 import NormalInput from '../global/normal_input';
 import { usePushNotifications } from '../../app/hooks/usePushNotifications';
 import { notificationStorage } from '../notificationStorage';
-
+import { handleGoWhatsApp } from '../../util/index'
 interface HomePageProps {}
 
 const HomePage: React.FC<HomePageProps> = () => {
@@ -58,7 +60,6 @@ const HomePage: React.FC<HomePageProps> = () => {
                 const response = await authenticationService.getUserInfo();
                 //if success, set user ID,
                 if (response) {
-                    console.log('ddddddddd', response.data)
                     setNotifySessionID(response.data.notify_session_id);
                     setUserID(response.data.id);
                     //after setting id, also check if the user has a valid license plate, if not, show message
@@ -246,7 +247,7 @@ const HomePage: React.FC<HomePageProps> = () => {
 
                     // Count unread promotions
                     if (results[1].status === 'fulfilled') {
-                        const unreadPromotions = results[1].value.filter((p) => {
+                        const unreadPromotions = results[1].value.filter((p: any) => {
                             return !viewedNotifications.some((vn) => vn.id === p.id);
                         });
                         totalUnread += unreadPromotions.length;
@@ -409,7 +410,6 @@ const HomePage: React.FC<HomePageProps> = () => {
                             <View className="flex-row justify-between mr-[10%]">
                                 <Text className="text-lg text-left pb-1">你好!</Text>
                                 <View className="relative">
-                                    {/* <Pressable onPress={() => router.push('notificationPage')}> */}
                                     <Pressable
                                         onPress={() =>
                                             router.push({
@@ -422,18 +422,24 @@ const HomePage: React.FC<HomePageProps> = () => {
                                             })
                                         }
                                         disabled={isLoadingReservations}
-                                        className="w-10  items-center justify-center"
+                                        className="z-20 w-10 items-center justify-center"
                                         hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
                                     >
                                         <View className="w-6 h-6">
                                             <BellIconSvg />
                                         </View>
+                                         
                                         {unreadCount > 0 && (
                                             <View className="absolute -top-2 -right-[0.5] bg-red-500 rounded-full w-5 h-5 items-center justify-center">
                                                 <Text className="text-white text-xs font-bold">{unreadCount}</Text>
                                             </View>
                                         )}
                                     </Pressable>
+                                    <Pressable className='z-10 top-9 right-0' onPress={() => handleGoWhatsApp()}>
+                                        <View className="w-8 h-8">
+                                            <WhatsAppSvg />
+                                        </View>
+                                    </Pressable>
                                 </View>
                             </View>
                             <Text className="text-4xl font-light ">{user?.nickname}</Text>
@@ -495,8 +501,8 @@ const HomePage: React.FC<HomePageProps> = () => {
                                 onPress={() => router.push('accountMainPage')}
                                 title={
                                     <View className="flex flex-row space-x-2 items-center">
-                                        <MyVehicleIconSvg />
-                                        <Text className="text-white font-bold text-lg ml-2">我的帳戶</Text>
+                                        <MyWalletSvg/>
+                                        <Text className="text-white font-bold text-lg ml-2">錢包</Text>
                                     </View>
                                 }
                                 extendedStyle={{

+ 4 - 10
ios/Crazycharge.xcodeproj/project.pbxproj

@@ -24,7 +24,7 @@
 		3A4D513D75FFB5A393591B15 /* Pods-Crazycharge.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Crazycharge.release.xcconfig"; path = "Target Support Files/Pods-Crazycharge/Pods-Crazycharge.release.xcconfig"; sourceTree = "<group>"; };
 		3EEFA35F043450A69E33C60B /* Pods-Crazycharge.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Crazycharge.debug.xcconfig"; path = "Target Support Files/Pods-Crazycharge/Pods-Crazycharge.debug.xcconfig"; sourceTree = "<group>"; };
 		441EFC59C4AFE8E2E7B1D07E /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Crazycharge/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
-		993C2D5F4006A960A3EBF78A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Crazycharge/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
+		993C2D5F4006A960A3EBF78A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Crazycharge/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
 		AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Crazycharge/SplashScreen.storyboard; sourceTree = "<group>"; };
 		BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
 		ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -119,7 +119,6 @@
 				3EEFA35F043450A69E33C60B /* Pods-Crazycharge.debug.xcconfig */,
 				3A4D513D75FFB5A393591B15 /* Pods-Crazycharge.release.xcconfig */,
 			);
-			name = Pods;
 			path = Pods;
 			sourceTree = "<group>";
 		};
@@ -356,6 +355,7 @@
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = Crazycharge/Crazycharge.entitlements;
 				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = 3BTGCKA5W3;
 				ENABLE_BITCODE = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
@@ -469,10 +469,7 @@
 				LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					" ",
-				);
+				OTHER_LDFLAGS = "$(inherited)  ";
 				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
 				SDKROOT = iphoneos;
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -527,10 +524,7 @@
 				);
 				LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
 				MTL_ENABLE_DEBUG_INFO = NO;
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					" ",
-				);
+				OTHER_LDFLAGS = "$(inherited)  ";
 				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
 				SDKROOT = iphoneos;
 				USE_HERMES = true;

+ 15 - 0
util/index.ts

@@ -0,0 +1,15 @@
+import { Linking, Alert } from 'react-native';
+
+export const handleGoWhatsApp = async () => {
+  const phoneWithCountryCode = '8613365413560'; // 不带 "+",如 +60 改成 60(马来西亚)
+  const message = 'Hello!';
+
+  const url = `whatsapp://send?phone=${phoneWithCountryCode}&text=${encodeURIComponent(message)}`;
+
+  const supported = await Linking.canOpenURL(url);
+  if (supported) {
+    await Linking.openURL(url);
+  } else {
+    Alert.alert('WhatsApp 未安裝', '請先安裝 WhatsApp 應用');
+  }
+};