upd: push notifikasi

Deskripsi:
- blm selesai

NO Issues
This commit is contained in:
amel
2025-06-17 17:40:54 +08:00
parent c5a3a80462
commit 94901e8ec1
9 changed files with 197 additions and 2 deletions

43
.easignore Normal file
View File

@@ -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

3
.gitignore vendored
View File

@@ -39,3 +39,6 @@ app-example
x.ts x.ts
x.sh x.sh
google-services.json
mobile-darmasaba-firebase-adminsdk-fbsvc-f5abb292b5.json

View File

@@ -1,6 +1,9 @@
apply plugin: "com.android.application" apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android" apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react" 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() def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()

View File

@@ -36,3 +36,9 @@ useExpoModules()
include ':app' 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()) 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)

View File

@@ -10,14 +10,18 @@
"newArchEnabled": true, "newArchEnabled": true,
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"bundleIdentifier": "mobiledarmasaba.app" "bundleIdentifier": "mobiledarmasaba.app",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
}, },
"android": { "android": {
"package": "mobiledarmasaba.app", "package": "mobiledarmasaba.app",
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png", "foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
} },
"googleServicesFile": "./google-services.json"
}, },
"web": { "web": {
"bundler": "metro", "bundler": "metro",

BIN
bun.lockb

Binary file not shown.

View File

@@ -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')
}
}

View File

@@ -30,6 +30,8 @@
"expo-blur": "~14.1.4", "expo-blur": "~14.1.4",
"expo-clipboard": "^7.1.4", "expo-clipboard": "^7.1.4",
"expo-constants": "~17.1.6", "expo-constants": "~17.1.6",
"expo-dev-client": "~5.2.0",
"expo-device": "~7.1.4",
"expo-document-picker": "^13.1.5", "expo-document-picker": "^13.1.5",
"expo-file-system": "^18.1.10", "expo-file-system": "^18.1.10",
"expo-font": "~13.3.1", "expo-font": "~13.3.1",
@@ -39,6 +41,8 @@
"expo-linear-gradient": "~14.1.4", "expo-linear-gradient": "~14.1.4",
"expo-linking": "~7.1.5", "expo-linking": "~7.1.5",
"expo-media-library": "~17.1.6", "expo-media-library": "~17.1.6",
"expo-modules-core": "~2.4.0",
"expo-notifications": "~0.31.3",
"expo-router": "~5.0.7", "expo-router": "~5.0.7",
"expo-sharing": "^13.1.5", "expo-sharing": "^13.1.5",
"expo-splash-screen": "~0.30.8", "expo-splash-screen": "~0.30.8",

View File

@@ -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<NotificationContextType | undefined>(
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<NotificationProviderProps> = ({
children,
}) => {
const [expoPushToken, setExpoPushToken] = useState<string | null>(null);
const [notification, setNotification] =
useState<Notifications.Notification | null>(null);
const [error, setError] = useState<Error | null>(null);
const notificationListener = useRef<Subscription>();
const responseListener = useRef<Subscription>();
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 (
<NotificationContext.Provider
value={{ expoPushToken, notification, error }}
>
{children}
</NotificationContext.Provider>
);
};