push notifications not working for some reason
This commit is contained in:
@@ -56,7 +56,7 @@ public class SecurityConfig {
|
|||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
|
||||||
config.setAllowedOrigins(List.of(
|
config.setAllowedOrigins(List.of(
|
||||||
"http://localhost:3000",
|
"http://localhost:8081",
|
||||||
"https://etf-oglasi.ksan.dev"
|
"https://etf-oglasi.ksan.dev"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ public class NotificationService {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
firebaseMessaging.send(message);
|
String id = firebaseMessaging.send(message);
|
||||||
|
System.out.println("Send:" + id);
|
||||||
} catch (FirebaseMessagingException ex) {
|
} catch (FirebaseMessagingException ex) {
|
||||||
|
|
||||||
|
ex.printStackTrace();
|
||||||
if (ex.getMessagingErrorCode()
|
if (ex.getMessagingErrorCode()
|
||||||
== MessagingErrorCode.UNREGISTERED) {
|
== MessagingErrorCode.UNREGISTERED) {
|
||||||
deviceTokenRepo.delete(token);
|
deviceTokenRepo.delete(token);
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// components/UpdatePrompt.tsx
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Modal that appears when a newer build is available.
|
||||||
|
// "Update now" opens the Gitea release page in the browser.
|
||||||
|
// "Later" dismisses it for the current session (it reappears next launch).
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Linking,
|
||||||
|
Modal,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import type { UpdateInfo } from '../hooks/useUpdatecheck';
|
||||||
|
import { version as currentVersion } from '../version.json';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
updateInfo: UpdateInfo;
|
||||||
|
onDismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdatePrompt({ updateInfo, onDismiss }: Props) {
|
||||||
|
function openRelease() {
|
||||||
|
Linking.openURL(updateInfo.releaseUrl).catch(() => {
|
||||||
|
// If the device can't open the URL, just dismiss so the app isn't stuck.
|
||||||
|
onDismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={onDismiss}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.title}>Update available</Text>
|
||||||
|
|
||||||
|
<Text style={styles.body}>
|
||||||
|
A new version of the app is available.{'\n'}
|
||||||
|
You are on build <Text style={styles.bold}>{currentVersion}</Text>,
|
||||||
|
the latest is build{' '}
|
||||||
|
<Text style={styles.bold}>{updateInfo.latestVersion}</Text>.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<Pressable
|
||||||
|
style={[styles.btn, styles.btnSecondary]}
|
||||||
|
onPress={onDismiss}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Remind me later"
|
||||||
|
>
|
||||||
|
<Text style={styles.btnSecondaryText}>Later</Text>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
style={[styles.btn, styles.btnPrimary]}
|
||||||
|
onPress={openRelease}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Open the release page to update"
|
||||||
|
>
|
||||||
|
<Text style={styles.btnPrimaryText}>Update now</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 380,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 24,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.15,
|
||||||
|
shadowRadius: 12,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#111',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#444',
|
||||||
|
lineHeight: 22,
|
||||||
|
marginBottom: 24,
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#111',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
btn: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
btnPrimary: {
|
||||||
|
backgroundColor: '#2563EB',
|
||||||
|
},
|
||||||
|
btnPrimaryText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
|
btnSecondary: {
|
||||||
|
backgroundColor: '#F3F4F6',
|
||||||
|
},
|
||||||
|
btnSecondaryText: {
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const GITEA_URL = 'https://git.ksan.dev';
|
||||||
|
|
||||||
|
export const GITEA_OWNER = 'ksan';
|
||||||
|
|
||||||
|
export const GITEA_REPO = 'etf-oglasi';
|
||||||
|
|
||||||
|
|
||||||
|
export const RELEASE_TAG_PREFIX = 'mobile-v';
|
||||||
|
|
||||||
|
export const UPDATE_CHECK_TIMEOUT_MS = 8_000;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import messaging from "@react-native-firebase/messaging";
|
||||||
|
import notifee, { AndroidImportance, EventType } from "@notifee/react-native";
|
||||||
|
|
||||||
|
|
||||||
|
async function displayLocalNotification(title?: string, body?: string) {
|
||||||
|
if (!title && !body) return;
|
||||||
|
|
||||||
|
const channelId = await notifee.createChannel({
|
||||||
|
id: "default",
|
||||||
|
name: "General",
|
||||||
|
importance: AndroidImportance.HIGH,
|
||||||
|
});
|
||||||
|
|
||||||
|
await notifee.displayNotification({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
android: {
|
||||||
|
channelId,
|
||||||
|
pressAction: { id: "default" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function usePushNotifications() {
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeMessage = messaging().onMessage(async remoteMessage => {
|
||||||
|
const title = remoteMessage.data?.title as string | undefined;
|
||||||
|
const body = remoteMessage.data?.body as string | undefined;
|
||||||
|
await displayLocalNotification(title, body);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribeNotifee = notifee.onForegroundEvent(({ type, detail }) => {
|
||||||
|
if (type === EventType.PRESS) {
|
||||||
|
console.log("Notification tapped:", detail.notification);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribeMessage();
|
||||||
|
unsubscribeNotifee();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
GITEA_URL,
|
||||||
|
GITEA_OWNER,
|
||||||
|
GITEA_REPO,
|
||||||
|
RELEASE_TAG_PREFIX,
|
||||||
|
UPDATE_CHECK_TIMEOUT_MS,
|
||||||
|
} from '../config/gitea';
|
||||||
|
import { version as currentVersion } from '../version.json';
|
||||||
|
|
||||||
|
export interface UpdateInfo {
|
||||||
|
/** Build number of the latest release (same unit as currentVersion). */
|
||||||
|
latestVersion: number;
|
||||||
|
/** Direct URL the user can open to read the release notes / download the APK. */
|
||||||
|
releaseUrl: string;
|
||||||
|
/** Tag name as stored on Gitea, e.g. "mobile-v42". */
|
||||||
|
tagName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUpdateCheckResult {
|
||||||
|
/** True while the API call is in flight. */
|
||||||
|
checking: boolean;
|
||||||
|
/** Populated once the check completes and a newer build exists. */
|
||||||
|
updateInfo: UpdateInfo | null;
|
||||||
|
/** Non-null when the check failed (network error, timeout, bad response). */
|
||||||
|
error: Error | null;
|
||||||
|
/** Re-run the check manually (e.g. from a "check again" button). */
|
||||||
|
recheck: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Gitea releases API ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
interface GiteaRelease {
|
||||||
|
id: number;
|
||||||
|
tag_name: string;
|
||||||
|
name: string;
|
||||||
|
html_url: string;
|
||||||
|
draft: boolean;
|
||||||
|
prerelease: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatestRelease(): Promise<GiteaRelease> {
|
||||||
|
const url = `${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/latest`;
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), UPDATE_CHECK_TIMEOUT_MS);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { signal: controller.signal });
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Gitea API returned HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await res.json()) as GiteaRelease;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse the integer build number out of a tag like "mobile-v42". Returns NaN if it can't. */
|
||||||
|
function parseBuildNumber(tagName: string): number {
|
||||||
|
if (!tagName.startsWith(RELEASE_TAG_PREFIX)) return NaN;
|
||||||
|
return parseInt(tagName.slice(RELEASE_TAG_PREFIX.length), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useUpdatecheck(): UseUpdateCheckResult {
|
||||||
|
const [checking, setChecking] = useState(false);
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
// A counter we bump to re-trigger the effect without changing the dep array shape.
|
||||||
|
const [trigger, setTrigger] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
setChecking(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const release = await fetchLatestRelease();
|
||||||
|
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
|
// Skip drafts and pre-releases.
|
||||||
|
if (release.draft || release.prerelease) {
|
||||||
|
setUpdateInfo(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = parseBuildNumber(release.tag_name);
|
||||||
|
|
||||||
|
if (isNaN(latestVersion)) {
|
||||||
|
// The tag doesn't follow our scheme — ignore it silently.
|
||||||
|
setUpdateInfo(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestVersion > currentVersion) {
|
||||||
|
setUpdateInfo({
|
||||||
|
latestVersion,
|
||||||
|
releaseUrl: release.html_url,
|
||||||
|
tagName: release.tag_name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setUpdateInfo(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (cancelled) return;
|
||||||
|
// Don't surface AbortError as a real error — it's just a timeout.
|
||||||
|
if (err instanceof Error && err.name === 'AbortError') {
|
||||||
|
setError(new Error('Update check timed out. Check your connection.'));
|
||||||
|
} else {
|
||||||
|
setError(err instanceof Error ? err : new Error(String(err)));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!cancelled) setChecking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [trigger]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
checking,
|
||||||
|
updateInfo,
|
||||||
|
error,
|
||||||
|
recheck: () => setTrigger(t => t + 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
+80
-61
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
import React from "react";
|
import React, {useState} from "react";
|
||||||
import { useColorScheme } from "react-native";
|
import { useColorScheme } from "react-native";
|
||||||
import { NavigationContainer, DefaultTheme, DarkTheme } from "@react-navigation/native";
|
import { NavigationContainer, DefaultTheme, DarkTheme } from "@react-navigation/native";
|
||||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||||
@@ -16,7 +16,11 @@ import {SafeAreaProvider} from "react-native-safe-area-context";
|
|||||||
import AuthGate from "@/screens/AuthGate";
|
import AuthGate from "@/screens/AuthGate";
|
||||||
import {AuthProvider, useAuth} from "@/context/AuthContext";
|
import {AuthProvider, useAuth} from "@/context/AuthContext";
|
||||||
import messaging, {setBackgroundMessageHandler} from "@react-native-firebase/messaging";
|
import messaging, {setBackgroundMessageHandler} from "@react-native-firebase/messaging";
|
||||||
import {getMessaging} from "@firebase/messaging";
|
|
||||||
|
import { useUpdatecheck } from "./hooks/useUpdatecheck";
|
||||||
|
import { UpdatePrompt } from './components/UpdatePrompt';
|
||||||
|
import { usePushNotifications } from "./hooks/usePushNotifications";
|
||||||
|
|
||||||
messaging().setBackgroundMessageHandler(async () => {});
|
messaging().setBackgroundMessageHandler(async () => {});
|
||||||
// Must be called before any navigator renders
|
// Must be called before any navigator renders
|
||||||
enableScreens();
|
enableScreens();
|
||||||
@@ -45,6 +49,7 @@ function ProfileTab() {
|
|||||||
return user ? <Profile /> : <AuthGate />;
|
return user ? <Profile /> : <AuthGate />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const scheme = useColorScheme();
|
const scheme = useColorScheme();
|
||||||
const dark = scheme === "dark";
|
const dark = scheme === "dark";
|
||||||
@@ -54,72 +59,86 @@ export default function App() {
|
|||||||
? { ...DarkTheme, colors: { ...DarkTheme.colors, background: "#0E0D0C" } }
|
? { ...DarkTheme, colors: { ...DarkTheme.colors, background: "#0E0D0C" } }
|
||||||
: { ...DefaultTheme, colors: { ...DefaultTheme.colors, background: "#FDFAF5" } };
|
: { ...DefaultTheme, colors: { ...DefaultTheme.colors, background: "#FDFAF5" } };
|
||||||
|
|
||||||
|
const { updateInfo } = useUpdatecheck();
|
||||||
|
const [updateDismissed, setUpdateDismissed] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
usePushNotifications();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<NavigationContainer theme={navTheme}>
|
<NavigationContainer theme={navTheme}>
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
screenOptions={({ route }) => ({
|
screenOptions={({ route }) => ({
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
|
|
||||||
tabBarStyle: {
|
tabBarStyle: {
|
||||||
backgroundColor: tab.bg,
|
backgroundColor: tab.bg,
|
||||||
borderTopColor: tab.border,
|
borderTopColor: tab.border,
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
height: 60,
|
height: 60,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
paddingTop: 6,
|
paddingTop: 6,
|
||||||
},
|
},
|
||||||
|
|
||||||
tabBarActiveTintColor: tab.active,
|
tabBarActiveTintColor: tab.active,
|
||||||
tabBarInactiveTintColor: tab.inactive,
|
tabBarInactiveTintColor: tab.inactive,
|
||||||
|
|
||||||
tabBarLabelStyle: {
|
tabBarLabelStyle: {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
},
|
},
|
||||||
|
|
||||||
tabBarIcon: ({ focused, color, size }) => {
|
tabBarIcon: ({ focused, color, size }) => {
|
||||||
const icons: Record<
|
const icons: Record<
|
||||||
string,
|
string,
|
||||||
{ active: keyof typeof Ionicons.glyphMap; inactive: keyof typeof Ionicons.glyphMap }
|
{ active: keyof typeof Ionicons.glyphMap; inactive: keyof typeof Ionicons.glyphMap }
|
||||||
> = {
|
> = {
|
||||||
"Subscribed": { active: "bookmark", inactive: "bookmark-outline" },
|
"Subscribed": { active: "bookmark", inactive: "bookmark-outline" },
|
||||||
"Discover": { active: "compass", inactive: "compass-outline" },
|
"Discover": { active: "compass", inactive: "compass-outline" },
|
||||||
"Profile": { active: "person-circle", inactive: "person-circle-outline" },
|
"Profile": { active: "person-circle", inactive: "person-circle-outline" },
|
||||||
};
|
};
|
||||||
const set = icons[route.name];
|
const set = icons[route.name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={focused ? set.active : set.inactive}
|
name={focused ? set.active : set.inactive}
|
||||||
size={focused ? size + 1 : size}
|
size={focused ? size + 1 : size}
|
||||||
color={color}
|
color={color}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name="Subscribed"
|
name="Subscribed"
|
||||||
component={SubscribedFeed}
|
component={SubscribedFeed}
|
||||||
options={{ title: "My Feed" }}
|
options={{ title: "My Feed" }}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Discover"
|
||||||
|
component={AllFeed}
|
||||||
|
options={{ title: "Discover" }}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Profile"
|
||||||
|
component={ProfileTab}
|
||||||
|
options={{ title: "Profile" }}
|
||||||
|
/>
|
||||||
|
</Tab.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
|
||||||
|
{/* Overlays the entire app, including the nav bar */}
|
||||||
|
{updateInfo && !updateDismissed && (
|
||||||
|
<UpdatePrompt
|
||||||
|
updateInfo={updateInfo}
|
||||||
|
onDismiss={() => setUpdateDismissed(true)}
|
||||||
/>
|
/>
|
||||||
<Tab.Screen
|
)}
|
||||||
name="Discover"
|
|
||||||
component={AllFeed}
|
|
||||||
options={{ title: "Discover" }}
|
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name="Profile"
|
|
||||||
component={ProfileTab}
|
|
||||||
options={{ title: "Profile" }}
|
|
||||||
/>
|
|
||||||
</Tab.Navigator>
|
|
||||||
</NavigationContainer>
|
|
||||||
</SafeAreaProvider>
|
|
||||||
|
|
||||||
</AuthProvider>
|
</SafeAreaProvider>
|
||||||
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+10
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
"@notifee/react-native": "^9.1.8",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-firebase/app": "^24.0.0",
|
"@react-native-firebase/app": "^24.0.0",
|
||||||
"@react-native-firebase/messaging": "^24.0.0",
|
"@react-native-firebase/messaging": "^24.0.0",
|
||||||
@@ -3583,6 +3584,15 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@notifee/react-native": {
|
||||||
|
"version": "9.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@notifee/react-native/-/react-native-9.1.8.tgz",
|
||||||
|
"integrity": "sha512-Az/dueoPerJsbbjRxu8a558wKY+gONUrfoy3Hs++5OqbeMsR0dYe6P+4oN6twrLFyzAhEA1tEoZRvQTFDRmvQg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@protobufjs/aspromise": {
|
"node_modules/@protobufjs/aspromise": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
|
"@notifee/react-native": "^9.1.8",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-firebase/app": "^24.0.0",
|
"@react-native-firebase/app": "^24.0.0",
|
||||||
"@react-native-firebase/messaging": "^24.0.0",
|
"@react-native-firebase/messaging": "^24.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user