diff --git a/.easignore b/.easignore new file mode 100644 index 0000000..0998532 --- /dev/null +++ b/.easignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example + +x.ts +x.sh +/android +/ios \ No newline at end of file diff --git a/.gitignore b/.gitignore index d2805ed..e9af40e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ app-example x.ts x.sh + +google-services.json +mobile-darmasaba-firebase-adminsdk-fbsvc-f5abb292b5.json diff --git a/android/app/build.gradle b/android/app/build.gradle index c65619c..c83b335 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,9 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" +apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy") +apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle") +apply from: new File(["node", "--print", "require.resolve('expo-updates/package.json')"].execute(null, rootDir).text.trim(), "../scripts/create-manifest-android.gradle") def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() diff --git a/android/settings.gradle b/android/settings.gradle index a097d31..f4a9478 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -36,3 +36,9 @@ useExpoModules() include ':app' includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) + +apply from: new File(["node", "--print", "require.resolve('expo-modules-core/package.json')"].execute(null, rootDir).text.trim(), "../gradle.groovy"); +includeUnimodulesProjects() + +apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); +applyNativeModulesSettingsGradle(settings) diff --git a/app.json b/app.json index c4e2199..a620a94 100644 --- a/app.json +++ b/app.json @@ -10,14 +10,18 @@ "newArchEnabled": true, "ios": { "supportsTablet": true, - "bundleIdentifier": "mobiledarmasaba.app" + "bundleIdentifier": "mobiledarmasaba.app", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } }, "android": { "package": "mobiledarmasaba.app", "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "googleServicesFile": "./google-services.json" }, "web": { "bundler": "metro", diff --git a/bun.lockb b/bun.lockb index e93878d..424248a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lib/registerForPushNotificationsAsync.ts b/lib/registerForPushNotificationsAsync.ts new file mode 100644 index 0000000..616328d --- /dev/null +++ b/lib/registerForPushNotificationsAsync.ts @@ -0,0 +1,43 @@ +import Constants from 'expo-constants'; +import * as Device from 'expo-device'; +import * as Notifications from 'expo-notifications'; +import { Platform } from "react-native"; + +export async function registerForPushNotificationsAsync() { + if (Platform.OS === 'android') { + await Notifications.setNotificationChannelAsync('default', { + name: 'default', + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250, 250, 250, 250, 250, 500], + lightColor: '#FF231F7C', + }); + } + + 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') { + throw new Error('Permission not granted') + } + + const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.expoConfig?.extra?.projectId + if (!projectId) { + throw new Error('Project ID not found') + } + + try { + const pushTokenString = (await Notifications.getExpoPushTokenAsync({ projectId })).data + console.log(pushTokenString) + return pushTokenString + } catch (error) { + throw new Error(`Error getting push token: ${error}`) + } + }else{ + throw new Error('Must use physical device for Push Notifications') + } +} diff --git a/package.json b/package.json index 4f7d372..5e4e03f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "expo-blur": "~14.1.4", "expo-clipboard": "^7.1.4", "expo-constants": "~17.1.6", + "expo-dev-client": "~5.2.0", + "expo-device": "~7.1.4", "expo-document-picker": "^13.1.5", "expo-file-system": "^18.1.10", "expo-font": "~13.3.1", @@ -39,6 +41,8 @@ "expo-linear-gradient": "~14.1.4", "expo-linking": "~7.1.5", "expo-media-library": "~17.1.6", + "expo-modules-core": "~2.4.0", + "expo-notifications": "~0.31.3", "expo-router": "~5.0.7", "expo-sharing": "^13.1.5", "expo-splash-screen": "~0.30.8", diff --git a/providers/NotificationProvider.tsx b/providers/NotificationProvider.tsx new file mode 100644 index 0000000..9ae06dc --- /dev/null +++ b/providers/NotificationProvider.tsx @@ -0,0 +1,89 @@ +import { registerForPushNotificationsAsync } from "@/lib/registerForPushNotificationsAsync"; +import { Subscription } from "expo-modules-core"; +import * as Notifications from "expo-notifications"; +import React, { + createContext, + ReactNode, + useContext, + useEffect, + useRef, + useState, +} from "react"; + +interface NotificationContextType { + expoPushToken: string | null; + notification: Notifications.Notification | null; + error: Error | null; +} + +const NotificationContext = createContext( + undefined +); + +export const useNotification = () => { + const context = useContext(NotificationContext); + if (context === undefined) { + throw new Error( + "useNotification must be used within a NotificationProvider" + ); + } + return context; +}; + +interface NotificationProviderProps { + children: ReactNode; +} + +export const NotificationProvider: React.FC = ({ + children, +}) => { + const [expoPushToken, setExpoPushToken] = useState(null); + const [notification, setNotification] = + useState(null); + const [error, setError] = useState(null); + + const notificationListener = useRef(); + const responseListener = useRef(); + + useEffect(() => { + registerForPushNotificationsAsync().then( + (token) => setExpoPushToken(token), + (error) => setError(error) + ); + + notificationListener.current = + Notifications.addNotificationReceivedListener((notification) => { + console.log("🔔 Notification Received: ", notification); + setNotification(notification); + }); + + responseListener.current = + Notifications.addNotificationResponseReceivedListener((response) => { + console.log( + "🔔 Notification Response: ", + JSON.stringify(response, null, 2), + JSON.stringify(response.notification.request.content.data, null, 2) + ); + // Handle the notification response here + }); + + return () => { + if (notificationListener.current) { + Notifications.removeNotificationSubscription( + notificationListener.current + ); + } + if (responseListener.current) { + Notifications.removeNotificationSubscription(responseListener.current); + } + }; + }, []); + + return ( + + {children} + + ); +}; \ No newline at end of file