From 8736467e9fdffba1e216c6e9a10ea689261b0f18 Mon Sep 17 00:00:00 2001 From: Ivan Miziuk Date: Mon, 13 Apr 2026 04:48:06 -0700 Subject: [PATCH] Run on background --- AlarmApp/.bundle/config | 2 - AlarmApp/.gitignore | 24 - AlarmApp/AlarmScheduler.tsx | 224 +++++++++ AlarmApp/App.tsx | 284 +++++------ AlarmApp/Gemfile | 16 - AlarmApp/android/settings.gradle | 1 + AlarmApp/find.py | 32 ++ AlarmApp/index.js | 7 + AlarmApp/ios/.xcode.env | 11 - .../ios/AlarmApp.xcodeproj/project.pbxproj | 475 ------------------ .../xcshareddata/xcschemes/AlarmApp.xcscheme | 88 ---- AlarmApp/ios/AlarmApp/AppDelegate.swift | 48 -- .../AppIcon.appiconset/Contents.json | 53 -- .../AlarmApp/Images.xcassets/Contents.json | 6 - AlarmApp/ios/AlarmApp/Info.plist | 59 --- AlarmApp/ios/AlarmApp/LaunchScreen.storyboard | 47 -- AlarmApp/ios/AlarmApp/PrivacyInfo.xcprivacy | 37 -- AlarmApp/ios/Podfile | 34 -- AlarmApp/package-lock.json | 56 +-- AlarmApp/package.json | 5 +- AlarmApp/remove_bloatware.py | 335 ++++++++++++ AlarmApp/scripts/install_depends | 1 + 22 files changed, 753 insertions(+), 1092 deletions(-) delete mode 100644 AlarmApp/.bundle/config create mode 100644 AlarmApp/AlarmScheduler.tsx delete mode 100644 AlarmApp/Gemfile create mode 100644 AlarmApp/find.py delete mode 100644 AlarmApp/ios/.xcode.env delete mode 100644 AlarmApp/ios/AlarmApp.xcodeproj/project.pbxproj delete mode 100644 AlarmApp/ios/AlarmApp.xcodeproj/xcshareddata/xcschemes/AlarmApp.xcscheme delete mode 100644 AlarmApp/ios/AlarmApp/AppDelegate.swift delete mode 100644 AlarmApp/ios/AlarmApp/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 AlarmApp/ios/AlarmApp/Images.xcassets/Contents.json delete mode 100644 AlarmApp/ios/AlarmApp/Info.plist delete mode 100644 AlarmApp/ios/AlarmApp/LaunchScreen.storyboard delete mode 100644 AlarmApp/ios/AlarmApp/PrivacyInfo.xcprivacy delete mode 100644 AlarmApp/ios/Podfile create mode 100644 AlarmApp/remove_bloatware.py diff --git a/AlarmApp/.bundle/config b/AlarmApp/.bundle/config deleted file mode 100644 index 848943b..0000000 --- a/AlarmApp/.bundle/config +++ /dev/null @@ -1,2 +0,0 @@ -BUNDLE_PATH: "vendor/bundle" -BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/AlarmApp/.gitignore b/AlarmApp/.gitignore index de99955..b99bc7d 100644 --- a/AlarmApp/.gitignore +++ b/AlarmApp/.gitignore @@ -2,26 +2,6 @@ # .DS_Store -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -**/.xcode.env.local - # Android/IntelliJ # build/ @@ -56,10 +36,6 @@ yarn-error.log # Bundle artifact *.jsbundle -# Ruby / CocoaPods -**/Pods/ -/vendor/bundle/ - # Temporary files created by Metro to check the health of the file watcher .metro-health-check* diff --git a/AlarmApp/AlarmScheduler.tsx b/AlarmApp/AlarmScheduler.tsx new file mode 100644 index 0000000..1eeb3e8 --- /dev/null +++ b/AlarmApp/AlarmScheduler.tsx @@ -0,0 +1,224 @@ +import notifee, { + TriggerType, + TimestampTrigger, + AndroidImportance, + AndroidCategory, + EventType, + Event, +} from '@notifee/react-native'; +import { Platform, PermissionsAndroid, Linking, Alert } from 'react-native'; + +// ========================== // +// CONSTANTS +// ========================== // +const CHANNEL_ID = 'alarm_channel'; +const CHANNEL_NAME = 'Alarm Notifications'; + +// ========================== // +// TYPES +// ========================== // +export interface AlarmSet { + id: string; + start: string; // ISO string for precise reconstruction + end: string; // ISO string + interval: number; // minutes + count: number; // num of individual alarms in the set + active: boolean; + notificationIds: string[]; // track scheduled notifee trigger IDs +} + + +// ========================== // +// CHANNEL SETUP +// ========================== // +/** + * Creates the Android notification channel with alarm-level importance. + * Must be called once before scheduling any notifications. + */ +export async function createAlarmChannel(): Promise { + await notifee.createChannel({ + id: CHANNEL_ID, + name: CHANNEL_NAME, + importance: AndroidImportance.HIGH, + sound: 'default', + }); +} + + +// ========================== // +// PERMISSIONS +// ========================== // +/** + * Requests all permissions needed for reliable alarm delivery: + * - Notification permission (Android 13+) + * - Exact alarm permission (Android 12+) + * - Battery optimization exemption (prevents doze from killing triggers) + * + * Returns true only if ALL required permissions are granted. + */ +export async function requestAlarmPermissions(): Promise { + if (Platform.OS !== 'android') return true; + + // 1. POST_NOTIFICATIONS (Android 13+ / API 33+) + if (Platform.Version >= 33) { + const notifPerm = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS + ); + if (notifPerm !== PermissionsAndroid.RESULTS.GRANTED) { + Alert.alert( + 'Notification Permission Required', + 'Alarm Flow needs notification permission to alert you. Please enable it in Settings.', + [ + { text: 'Open Settings', onPress: () => Linking.openSettings() }, + { text: 'Cancel', style: 'cancel' }, + ] + ); + return false; + } + } + + // 2. SCHEDULE_EXACT_ALARM (Android 12+ / API 31+) + if (Platform.Version >= 31) { + const exactAlarmSettings = await notifee.getNotificationSettings(); + // android.alarm is 1 when allowed, 2 when denied + if (exactAlarmSettings.android.alarm !== 1) { + Alert.alert( + 'Exact Alarm Permission Required', + 'Alarm Flow needs the "Alarms & reminders" permission to fire alarms on time.', + [ + { + text: 'Open Settings', + onPress: () => notifee.openAlarmPermissionSettings(), + }, + { text: 'Cancel', style: 'cancel' }, + ] + ); + return false; + } + } + + // 3. Battery optimization exemption + const batteryOptimized = await notifee.isBatteryOptimizationEnabled(); + if (batteryOptimized) { + Alert.alert( + 'Battery Optimization', + 'For reliable alarms, please disable battery optimization for Alarm Flow.', + [ + { + text: 'Open Settings', + onPress: () => notifee.openBatteryOptimizationSettings(), + }, + { text: 'Skip', style: 'cancel' }, + ] + ); + // Not a hard block — alarms may still work, just less reliably + } + + return true; +} + + +// ========================== // +// SCHEDULING +// ========================== // +/** + * Schedules OS-level trigger notifications for every alarm in a set. + * Each alarm becomes an independent Notifee TimestampTrigger that fires + * even when the app is closed or the screen is locked. + * + * Returns the array of notification IDs so they can be stored with the AlarmSet. + */ +export async function scheduleAlarmSet( + startDate: Date, + endDate: Date, + intervalMinutes: number +): Promise<{ count: number; notificationIds: string[] }> { + const intervalMs = intervalMinutes * 60 * 1000; + const notificationIds: string[] = []; + let current = new Date(startDate); + const now = Date.now(); + + let index = 0; + while (current <= endDate) { + // Only schedule future alarms (skip times that already passed) + if (current.getTime() > now) { + const trigger: TimestampTrigger = { + type: TriggerType.TIMESTAMP, + timestamp: current.getTime(), + alarmManager: { + allowWhileIdle: true, // fires during Doze mode + }, + }; + + const timeLabel = current.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + }); + + const id = await notifee.createTriggerNotification( + { + title: 'Alarm Flow', + body: `Alarm #${index + 1} — ${timeLabel}`, + android: { + channelId: CHANNEL_ID, + importance: AndroidImportance.HIGH, + category: AndroidCategory.ALARM, + sound: 'default', + fullScreenAction: { id: 'default' }, // wake screen + pressAction: { id: 'default' }, + }, + }, + trigger + ); + + notificationIds.push(id); + } + + index++; + current = new Date(current.getTime() + intervalMs); + } + + return { count: index, notificationIds }; +} + + +// ========================== // +// CANCELLATION +// ========================== // +/** + * Cancels all scheduled trigger notifications for a given alarm set. + */ +export async function cancelAlarmSet(notificationIds: string[]): Promise { + for (const id of notificationIds) { + await notifee.cancelTriggerNotification(id); + } +} + +/** + * Cancels every trigger notification managed by Notifee. + */ +export async function cancelAllAlarms(): Promise { + await notifee.cancelTriggerNotifications(); +} + + +// ========================== // +// BACKGROUND EVENT HANDLER +// ========================== // +/** + * Must be registered at the top level (index.js) so the OS can + * deliver events even when the JS runtime has been killed. + */ +export function onBackgroundEvent(): (event: Event) => Promise { + return async ({ type, detail }: Event) => { + // When user dismisses or presses the notification, clean up + if ( + type === EventType.DISMISSED || + type === EventType.PRESS + ) { + if (detail.notification?.id) { + await notifee.cancelNotification(detail.notification.id); + } + } + }; +} diff --git a/AlarmApp/App.tsx b/AlarmApp/App.tsx index 0d96b8f..fb6a621 100644 --- a/AlarmApp/App.tsx +++ b/AlarmApp/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { View, @@ -12,177 +12,184 @@ import { } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; -import styles from "./styles.js" +import styles from './styles.js'; // local -import SOUND from "./Sound.tsx" import { - SECOND, - get_interval_time -} from "./Time.tsx" - - -interface AlarmSet { - id: string; - start: string; - end: string; - interval: number; // minutes - count: number; // num of alarms - active: boolean; -} - + AlarmSet, + createAlarmChannel, + requestAlarmPermissions, + scheduleAlarmSet, + cancelAlarmSet, +} from './AlarmScheduler'; -function check_time(current_time: Date, alarm_time: Date): boolean { - let CT: Date = current_time; // current time - let AT: Date = alarm_time; // alarm time - // alarm should go off when both times match - if(CT.getHours() === AT.getHours()){ - if(CT.getMinutes() === AT.getMinutes()){ - return true; - } - } - - // the alarm should NOT go off when they are different - return false; -} +const STORAGE_KEY = 'ALARMS'; export default function App() { - const [alarms, setAlarms] = useState([]); - const [startTime, setStartTime] = useState(new Date()); - const [endTime, setEndTime] = useState(() => { + const [alarms, setAlarms] = useState([]); + const [startTime, setStartTime] = useState(new Date()); + const [endTime, setEndTime] = useState(() => { const d = new Date(); d.setHours(d.getHours() + 1); return d; }); - const [intervalMinutes, setIntervalMinutes] = useState(10); - const [showStartPicker, setShowStartPicker] = useState(false); - const [showEndPicker, setShowEndPicker] = useState(false); + const [intervalMinutes, setIntervalMinutes] = useState(10); + const [showStartPicker, setShowStartPicker] = useState(false); + const [showEndPicker, setShowEndPicker] = useState(false); const [showIntervalPicker, setShowIntervalPicker] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); - //guard against overlapping alarms - const lastFiredRef = React.useRef>({}); - // check every second + // ─── INIT: channel + permissions + load persisted alarms ─── useEffect(() => { - const interval = setInterval(() => { - const now = new Date(); - const nowStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - - for (const set of alarms) { - if (!set.active) continue; - - //fire only once per minute to avoid overlapping alarms - if (now.getSeconds() !== 0) continue; + const init = async () => { + // Create the notification channel (no-op on iOS) + await createAlarmChannel(); - if (nowStr === set.end) { + // Request all OS-level permissions + await requestAlarmPermissions(); - //checks the date so that alarm can fire each day - const minuteKey = `${now.toDateString()} ${now.getHours()}:${now.getMinutes()}`; - - //guards against overlapping alarms and re-firing within the same minute - if (lastFiredRef.current[set.id] !== minuteKey) { - lastFiredRef.current[set.id] = minuteKey; - Alert.alert("Alarm!!!!", `Alarm set ended at ${set.end}`); - } + // Load persisted alarms from disk + try { + const stored = await AsyncStorage.getItem(STORAGE_KEY); + if (stored !== null) { + setAlarms(JSON.parse(stored)); } + } catch (e) { + console.log('Failed to load alarms', e); + } finally { + setIsLoaded(true); } - }, 1000); + }; - return () => clearInterval(interval); - }, [alarms]); + init(); + }, []); - const [isLoaded, setIsLoaded] = useState(false); - // save alarms + // ─── PERSIST alarms to disk whenever they change ─── useEffect(() => { if (!isLoaded) return; - const saveAlarms = async () => { - try { // save to alarms set - await AsyncStorage.setItem('ALARMS', JSON.stringify(alarms)); + const persist = async () => { + try { + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(alarms)); } catch (e) { console.log('Failed to save alarms', e); } }; - saveAlarms(); + persist(); }, [alarms, isLoaded]); - // load alarms - useEffect(() => { - const loadAlarms = async () => { - try { // load stored alarms set - const stored = await AsyncStorage.getItem('ALARMS'); - if (stored !== null) { - setAlarms(JSON.parse(stored)); - } - } catch (e) { - console.log('Failed to load alarms', e); - } finally { - setIsLoaded(true); - } - }; - loadAlarms(); - }, []); + // ─── CREATE alarm set (OS-level scheduling) ─── + const CreateIntervalAlarms = useCallback(async () => { + // Re-check permissions before scheduling + const allowed = await requestAlarmPermissions(); + if (!allowed) return; - const CreateIntervalAlarms = () => { - let current = new Date(startTime); - const end = new Date(endTime); - const intervalMs = intervalMinutes * 60 * 1000; - let count = 0; + try { + const { count, notificationIds } = await scheduleAlarmSet( + startTime, + endTime, + intervalMinutes + ); - while (current <= end) { - count++; - current = new Date(current.getTime() + intervalMs); + const newAlarmSet: AlarmSet = { + id: Date.now().toString(), + start: startTime.toISOString(), + end: endTime.toISOString(), + interval: intervalMinutes, + count, + active: true, + notificationIds, + }; + + setAlarms((prev) => [...prev, newAlarmSet]); + + const startLabel = startTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + const endLabel = endTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + Alert.alert( + 'Alarms Scheduled', + `${notificationIds.length} alarm(s) set from ${startLabel} to ${endLabel}.\nThey will fire even if the app is closed.` + ); + } catch (e) { + console.log('Failed to schedule alarms', e); + Alert.alert('Error', 'Could not schedule alarms. Check permissions and try again.'); } + }, [startTime, endTime, intervalMinutes]); - // {/*set alarm*/} //should not be in CreateIntervalAlarms - const newAlarmSet: AlarmSet = { - id: Date.now().toString(), - start: startTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - end: endTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - interval: intervalMinutes, - count, - active: true, - }; - setAlarms((prev) => [...prev, newAlarmSet]); - Alert.alert('Alarms Created!', `${count} alarms would be scheduled!`); - }; + // ─── TOGGLE alarm set on/off ─── + const toggleAlarmSet = useCallback(async (id: string) => { + const target = alarms.find((a) => a.id === id); + if (!target) return; -// Issues here - something with ID string/integers. - const toggleAlarmSet = (id: string) => { - setAlarms((prev) => - prev.map((a) => (a.id === id ? { ...a, active: !a.active } : a)) - ); - }; + if (target.active) { + // Deactivate — cancel OS triggers + await cancelAlarmSet(target.notificationIds); + setAlarms((prev) => + prev.map((a) => + a.id === id ? { ...a, active: false, notificationIds: [] } : a + ) + ); + } else { + // Reactivate — re-schedule from stored times + const allowed = await requestAlarmPermissions(); + if (!allowed) return; + + const startDate = new Date(target.start); + const endDate = new Date(target.end); + const { notificationIds } = await scheduleAlarmSet( + startDate, + endDate, + target.interval + ); + setAlarms((prev) => + prev.map((a) => + a.id === id ? { ...a, active: true, notificationIds } : a + ) + ); + } + }, [alarms]); -const confirmDeleteAlarmSet = (id: string) => { + // ─── DELETE alarm set ─── + const confirmDeleteAlarmSet = useCallback((id: string) => { Alert.alert( - "Delete alarm set?", - "This will remove entire batch.", - [ - { - text: "Cancel", style: "cancel"}, - { - text: "Delete", - style: "destructive", - onPress: () => { - setAlarms(prev => prev.filter(a => a.id !== id)); - }, - }, - ], - {cancelable: true} - ); + 'Delete alarm set?', + 'This will remove entire batch.', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Delete', + style: 'destructive', + onPress: async () => { + const target = alarms.find((a) => a.id === id); + if (target) { + await cancelAlarmSet(target.notificationIds); + } + setAlarms((prev) => prev.filter((a) => a.id !== id)); + }, + }, + ], + { cancelable: true } + ); + }, [alarms]); - }; + + // ─── HELPERS ─── + const formatTime = (date: Date | string): string => { + const d = typeof date === 'string' ? new Date(date) : date; + return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + }; const intervalOptions = [1, 2, 3, 5, 10, 15, 20, 30]; + + // ─── RENDER ─── return ( Alarm Flow @@ -192,17 +199,14 @@ const confirmDeleteAlarmSet = (id: string) => { data={alarms} keyExtractor={(item) => item.id} renderItem={({ item }) => ( - + toggleAlarmSet(item.id)} /> - {/* summarized alarm text */} - Start Time: {item.start} {'\n'} - End Time: {item.end} {'\n'} + Start Time: {formatTime(item.start)} {'\n'} + End Time: {formatTime(item.end)} {'\n'} Interval: {item.interval} min @@ -215,27 +219,23 @@ const confirmDeleteAlarmSet = (id: string) => { )} - ListEmptyComponent={No alarms yet} + ListEmptyComponent={ + No alarms yet + } /> - {/*Start time picker*/} + {/* Time controls */} Start Time setShowStartPicker(true)}> - - {startTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - + {formatTime(startTime)} - {/* end time picker */} End Time setShowEndPicker(true)}> - - {endTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - + {formatTime(endTime)} - {/* interval picker */} Interval setShowIntervalPicker(true)} @@ -300,4 +300,4 @@ const confirmDeleteAlarmSet = (id: string) => { )} ); -} \ No newline at end of file +} diff --git a/AlarmApp/Gemfile b/AlarmApp/Gemfile deleted file mode 100644 index 6a4c5f1..0000000 --- a/AlarmApp/Gemfile +++ /dev/null @@ -1,16 +0,0 @@ -source 'https://rubygems.org' - -# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version -ruby ">= 2.6.10" - -# Exclude problematic versions of cocoapods and activesupport that causes build failures. -gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' -gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' -gem 'xcodeproj', '< 1.26.0' -gem 'concurrent-ruby', '< 1.3.4' - -# Ruby 3.4.0 has removed some libraries from the standard library. -gem 'bigdecimal' -gem 'logger' -gem 'benchmark' -gem 'mutex_m' diff --git a/AlarmApp/android/settings.gradle b/AlarmApp/android/settings.gradle index f2b8bc3..62b76a5 100644 --- a/AlarmApp/android/settings.gradle +++ b/AlarmApp/android/settings.gradle @@ -6,6 +6,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url("$rootDir/../node_modules/@notifee/react-native/android/libs") } } } diff --git a/AlarmApp/find.py b/AlarmApp/find.py new file mode 100644 index 0000000..f50e033 --- /dev/null +++ b/AlarmApp/find.py @@ -0,0 +1,32 @@ +import os + +def find_settings_gradle(): + """ + Locates the android/settings.gradle file required for fixing + the Notifee build error. + """ + # Define the target relative path from the root + target_rel_path = os.path.join("android", "settings.gradle") + + # Check if we are already in the root or need to search + current_dir = os.getcwd() + + # Check common locations (Current Dir or child AlarmApp dir) + possible_paths = [ + os.path.abspath(target_rel_path), + os.path.abspath(os.path.join("AlarmApp", target_rel_path)) + ] + + for path in possible_paths: + if os.path.exists(path): + return path + + # Fallback: Walk the directory tree if structure is nested + for root, dirs, files in os.walk(current_dir): + if "settings.gradle" in files and "android" in root: + return os.path.join(root, "settings.gradle") + + return "File not found. Ensure you are running this from the project root." + +if __name__ == "__main__": + print(find_settings_gradle()) \ No newline at end of file diff --git a/AlarmApp/index.js b/AlarmApp/index.js index 9b73932..a8e0a6f 100644 --- a/AlarmApp/index.js +++ b/AlarmApp/index.js @@ -3,7 +3,14 @@ */ import { AppRegistry } from 'react-native'; +import notifee from '@notifee/react-native'; import App from './App'; import { name as appName } from './app.json'; +import { onBackgroundEvent } from './AlarmScheduler'; + +// Register the background handler BEFORE registerComponent. +// This lets Notifee wake the JS runtime and handle alarm events +// (dismiss, press) even when the app was force-closed. +notifee.onBackgroundEvent(onBackgroundEvent()); AppRegistry.registerComponent(appName, () => App); diff --git a/AlarmApp/ios/.xcode.env b/AlarmApp/ios/.xcode.env deleted file mode 100644 index 3d5782c..0000000 --- a/AlarmApp/ios/.xcode.env +++ /dev/null @@ -1,11 +0,0 @@ -# This `.xcode.env` file is versioned and is used to source the environment -# used when running script phases inside Xcode. -# To customize your local environment, you can create an `.xcode.env.local` -# file that is not versioned. - -# NODE_BINARY variable contains the PATH to the node executable. -# -# Customize the NODE_BINARY variable here. -# For example, to use nvm with brew, add the following line -# . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) diff --git a/AlarmApp/ios/AlarmApp.xcodeproj/project.pbxproj b/AlarmApp/ios/AlarmApp.xcodeproj/project.pbxproj deleted file mode 100644 index 0bde638..0000000 --- a/AlarmApp/ios/AlarmApp.xcodeproj/project.pbxproj +++ /dev/null @@ -1,475 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 0C80B921A6F3F58F76C31292 /* libPods-AlarmApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-AlarmApp.a */; }; - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; - 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 13B07F961A680F5B00A75B9A /* AlarmApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlarmApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = AlarmApp/Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = AlarmApp/Info.plist; sourceTree = ""; }; - 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = AlarmApp/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 3B4392A12AC88292D35C810B /* Pods-AlarmApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AlarmApp.debug.xcconfig"; path = "Target Support Files/Pods-AlarmApp/Pods-AlarmApp.debug.xcconfig"; sourceTree = ""; }; - 5709B34CF0A7D63546082F79 /* Pods-AlarmApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AlarmApp.release.xcconfig"; path = "Target Support Files/Pods-AlarmApp/Pods-AlarmApp.release.xcconfig"; sourceTree = ""; }; - 5DCACB8F33CDC322A6C60F78 /* libPods-AlarmApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AlarmApp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = AlarmApp/AppDelegate.swift; sourceTree = ""; }; - 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = AlarmApp/LaunchScreen.storyboard; sourceTree = ""; }; - ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0C80B921A6F3F58F76C31292 /* libPods-AlarmApp.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 13B07FAE1A68108700A75B9A /* AlarmApp */ = { - isa = PBXGroup; - children = ( - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 761780EC2CA45674006654EE /* AppDelegate.swift */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, - 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, - ); - name = AlarmApp; - sourceTree = ""; - }; - 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { - isa = PBXGroup; - children = ( - ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 5DCACB8F33CDC322A6C60F78 /* libPods-AlarmApp.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 832341AE1AAA6A7D00B99B32 /* Libraries */ = { - isa = PBXGroup; - children = ( - ); - name = Libraries; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* AlarmApp */, - 832341AE1AAA6A7D00B99B32 /* Libraries */, - 83CBBA001A601CBA00E9B192 /* Products */, - 2D16E6871FA4F8E400B85C8A /* Frameworks */, - BBD78D7AC51CEA395F1C20DB /* Pods */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - usesTabs = 0; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* AlarmApp.app */, - ); - name = Products; - sourceTree = ""; - }; - BBD78D7AC51CEA395F1C20DB /* Pods */ = { - isa = PBXGroup; - children = ( - 3B4392A12AC88292D35C810B /* Pods-AlarmApp.debug.xcconfig */, - 5709B34CF0A7D63546082F79 /* Pods-AlarmApp.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 13B07F861A680F5B00A75B9A /* AlarmApp */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "AlarmApp" */; - buildPhases = ( - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = AlarmApp; - productName = AlarmApp; - productReference = 13B07F961A680F5B00A75B9A /* AlarmApp.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1210; - TargetAttributes = { - 13B07F861A680F5B00A75B9A = { - LastSwiftMigration = 1120; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "AlarmApp" */; - compatibilityVersion = "Xcode 12.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* AlarmApp */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/.xcode.env.local", - "$(SRCROOT)/.xcode.env", - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; - }; - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-AlarmApp-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AlarmApp/Pods-AlarmApp-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-AlarmApp.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = AlarmApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = AlarmApp; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-AlarmApp.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = AlarmApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = AlarmApp; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = ( - /usr/lib/swift, - "$(inherited)", - ); - LIBRARY_SEARCH_PATHS = ( - "\"$(SDKROOT)/usr/lib/swift\"", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-DFOLLY_NO_CONFIG", - "-DFOLLY_MOBILE=1", - "-DFOLLY_USE_LIBCPP=1", - "-DFOLLY_CFG_NO_COROUTINES=1", - "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - SDKROOT = iphoneos; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++20"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = ( - /usr/lib/swift, - "$(inherited)", - ); - LIBRARY_SEARCH_PATHS = ( - "\"$(SDKROOT)/usr/lib/swift\"", - "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", - "\"$(inherited)\"", - ); - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CPLUSPLUSFLAGS = ( - "$(OTHER_CFLAGS)", - "-DFOLLY_NO_CONFIG", - "-DFOLLY_MOBILE=1", - "-DFOLLY_USE_LIBCPP=1", - "-DFOLLY_CFG_NO_COROUTINES=1", - "-DFOLLY_HAVE_CLOCK_GETTIME=1", - ); - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "AlarmApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "AlarmApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/AlarmApp/ios/AlarmApp.xcodeproj/xcshareddata/xcschemes/AlarmApp.xcscheme b/AlarmApp/ios/AlarmApp.xcodeproj/xcshareddata/xcschemes/AlarmApp.xcscheme deleted file mode 100644 index 3fac0da..0000000 --- a/AlarmApp/ios/AlarmApp.xcodeproj/xcshareddata/xcschemes/AlarmApp.xcscheme +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AlarmApp/ios/AlarmApp/AppDelegate.swift b/AlarmApp/ios/AlarmApp/AppDelegate.swift deleted file mode 100644 index 0526022..0000000 --- a/AlarmApp/ios/AlarmApp/AppDelegate.swift +++ /dev/null @@ -1,48 +0,0 @@ -import UIKit -import React -import React_RCTAppDelegate -import ReactAppDependencyProvider - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - - var reactNativeDelegate: ReactNativeDelegate? - var reactNativeFactory: RCTReactNativeFactory? - - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) -> Bool { - let delegate = ReactNativeDelegate() - let factory = RCTReactNativeFactory(delegate: delegate) - delegate.dependencyProvider = RCTAppDependencyProvider() - - reactNativeDelegate = delegate - reactNativeFactory = factory - - window = UIWindow(frame: UIScreen.main.bounds) - - factory.startReactNative( - withModuleName: "AlarmApp", - in: window, - launchOptions: launchOptions - ) - - return true - } -} - -class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { - override func sourceURL(for bridge: RCTBridge) -> URL? { - self.bundleURL() - } - - override func bundleURL() -> URL? { -#if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") -#else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") -#endif - } -} diff --git a/AlarmApp/ios/AlarmApp/Images.xcassets/AppIcon.appiconset/Contents.json b/AlarmApp/ios/AlarmApp/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 8121323..0000000 --- a/AlarmApp/ios/AlarmApp/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/AlarmApp/ios/AlarmApp/Images.xcassets/Contents.json b/AlarmApp/ios/AlarmApp/Images.xcassets/Contents.json deleted file mode 100644 index 2d92bd5..0000000 --- a/AlarmApp/ios/AlarmApp/Images.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/AlarmApp/ios/AlarmApp/Info.plist b/AlarmApp/ios/AlarmApp/Info.plist deleted file mode 100644 index 3d1f4d6..0000000 --- a/AlarmApp/ios/AlarmApp/Info.plist +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - AlarmApp - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - NSAppTransportSecurity - - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - NSLocationWhenInUseUsageDescription - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/AlarmApp/ios/AlarmApp/LaunchScreen.storyboard b/AlarmApp/ios/AlarmApp/LaunchScreen.storyboard deleted file mode 100644 index e3fc7f1..0000000 --- a/AlarmApp/ios/AlarmApp/LaunchScreen.storyboard +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AlarmApp/ios/AlarmApp/PrivacyInfo.xcprivacy b/AlarmApp/ios/AlarmApp/PrivacyInfo.xcprivacy deleted file mode 100644 index 41b8317..0000000 --- a/AlarmApp/ios/AlarmApp/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,37 +0,0 @@ - - - - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons - - C617.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategorySystemBootTime - NSPrivacyAccessedAPITypeReasons - - 35F9.1 - - - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/AlarmApp/ios/Podfile b/AlarmApp/ios/Podfile deleted file mode 100644 index 4aa4012..0000000 --- a/AlarmApp/ios/Podfile +++ /dev/null @@ -1,34 +0,0 @@ -# Resolve react_native_pods.rb with node to allow for hoisting -require Pod::Executable.execute_command('node', ['-p', - 'require.resolve( - "react-native/scripts/react_native_pods.rb", - {paths: [process.argv[1]]}, - )', __dir__]).strip - -platform :ios, min_ios_version_supported -prepare_react_native_project! - -linkage = ENV['USE_FRAMEWORKS'] -if linkage != nil - Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green - use_frameworks! :linkage => linkage.to_sym -end - -target 'AlarmApp' do - config = use_native_modules! - - use_react_native!( - :path => config[:reactNativePath], - # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/.." - ) - - post_install do |installer| - react_native_post_install( - installer, - config[:reactNativePath], - :mac_catalyst_enabled => false, - # :ccache_enabled => true - ) - end -end diff --git a/AlarmApp/package-lock.json b/AlarmApp/package-lock.json index 3063589..2aea2f4 100644 --- a/AlarmApp/package-lock.json +++ b/AlarmApp/package-lock.json @@ -10,12 +10,11 @@ "license": "ISC", "dependencies": { "24": "^0.0.0", + "@notifee/react-native": "^9.1.6", "@react-native-async-storage/async-storage": "^2.1.0", "@react-native-community/datetimepicker": "^8.6.0", - "@react-native/new-app-screen": "0.84.0", "react": "19.2.3", "react-native": "0.84.0", - "react-native-safe-area-context": "^5.5.2", "react-native-sound": "^0.13.0", "react-native-system-setting": "^1.7.6" }, @@ -25,7 +24,6 @@ "@babel/runtime": "^7.25.0", "@react-native-community/cli": "^20.1.2", "@react-native-community/cli-platform-android": "20.1.0", - "@react-native-community/cli-platform-ios": "^20.1.2", "@react-native/babel-preset": "0.84.0", "@react-native/eslint-config": "0.84.0", "@react-native/metro-config": "0.84.0", @@ -2692,6 +2690,15 @@ "node": ">= 8" } }, + "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/@react-native-async-storage/async-storage": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.0.tgz", @@ -3572,25 +3579,6 @@ "node": ">= 20.19.4" } }, - "node_modules/@react-native/new-app-screen": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@react-native/new-app-screen/-/new-app-screen-0.84.0.tgz", - "integrity": "sha512-feHjWBw4ehetsAmCwGnXEzecVGpai3Qv/ZTJyd+8KhRdQvg1a+RtyR4LNvR1vXdB0OC+gKleWN4okG2bdpt3kA==", - "license": "MIT", - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@types/react": "^19.1.0", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@react-native/normalize-colors": { "version": "0.84.0", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.0.tgz", @@ -6692,20 +6680,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -10363,16 +10337,6 @@ } } }, - "node_modules/react-native-safe-area-context": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", - "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-sound": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.13.0.tgz", diff --git a/AlarmApp/package.json b/AlarmApp/package.json index 4813b62..9a9c58d 100644 --- a/AlarmApp/package.json +++ b/AlarmApp/package.json @@ -9,19 +9,17 @@ "main": "index.js", "scripts": { "android": "react-native run-android", - "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", "test": "jest" }, "dependencies": { "24": "^0.0.0", + "@notifee/react-native": "^9.1.6", "@react-native-async-storage/async-storage": "^2.1.0", "@react-native-community/datetimepicker": "^8.6.0", - "@react-native/new-app-screen": "0.84.0", "react": "19.2.3", "react-native": "0.84.0", - "react-native-safe-area-context": "^5.5.2", "react-native-sound": "^0.13.0", "react-native-system-setting": "^1.7.6" }, @@ -31,7 +29,6 @@ "@babel/runtime": "^7.25.0", "@react-native-community/cli": "^20.1.2", "@react-native-community/cli-platform-android": "20.1.0", - "@react-native-community/cli-platform-ios": "^20.1.2", "@react-native/babel-preset": "0.84.0", "@react-native/eslint-config": "0.84.0", "@react-native/metro-config": "0.84.0", diff --git a/AlarmApp/remove_bloatware.py b/AlarmApp/remove_bloatware.py new file mode 100644 index 0000000..123a564 --- /dev/null +++ b/AlarmApp/remove_bloatware.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python3 +""" +remove_bloatware.py — AlarmApp Bloatware Removal Tool (Android / Windows) +=========================================================================== + +Analyzes the AlarmApp React Native project and strips out unused dependencies, +the entire iOS directory, dead template code, and other cruft left behind by +the React Native CLI scaffolding. + +Usage (Windows): + python remove_bloatware.py [--dry-run] [path\\to\\AlarmApp] + +Flags: + --dry-run Show what would be changed without modifying any files. + +If no path is given, the current working directory is used. +""" + +import argparse +import json +import os +import shutil +import sys +from pathlib import Path + + +# ────────────────────────────────────────────────────────────────── +# Configuration — everything we want to remove lives here so it's +# easy to audit or extend. +# ────────────────────────────────────────────────────────────────── + +# npm packages declared in package.json but never imported in source. +UNUSED_DEPS = [ + "@react-native/new-app-screen", # default RN welcome screen — unused + "react-native-safe-area-context", # declared but never imported +] + +# npm devDependencies that are iOS-only and not needed for Android builds. +IOS_ONLY_DEV_DEPS = [ + "@react-native-community/cli-platform-ios", +] + +# npm scripts that only apply to iOS. +IOS_ONLY_SCRIPTS = [ + "ios", +] + +# Files to delete outright. +FILES_TO_DELETE = [ + "package-lock.json", # stale lockfile — regenerated by npm install + "Gemfile", # Ruby/Bundler — only needed for CocoaPods (iOS) +] + +# Directories to delete entirely. +DIRS_TO_DELETE = [ + "ios", # entire Xcode / CocoaPods tree — Android only + ".bundle", # Ruby bundler config — iOS only +] + + +# ────────────────────────────────────────────────────────────────── +# Helpers +# ────────────────────────────────────────────────────────────────── + +class Logger: + """Minimal coloured logger (ANSI codes work in Windows Terminal, + PowerShell 7+, and VS Code terminal).""" + GREEN = "\033[92m" + YELLOW = "\033[93m" + RED = "\033[91m" + CYAN = "\033[96m" + RESET = "\033[0m" + + def __init__(self, dry_run: bool = False): + self.dry_run = dry_run + self.actions: list[str] = [] + # Enable ANSI escape sequences on older Windows consoles + if sys.platform == "win32": + os.system("") + + def _prefix(self) -> str: + return f"{self.YELLOW}[DRY RUN]{self.RESET} " if self.dry_run else "" + + def info(self, msg: str): + print(f"{self._prefix()}{self.CYAN}i{self.RESET} {msg}") + + def done(self, msg: str): + self.actions.append(msg) + print(f"{self._prefix()}{self.GREEN}+{self.RESET} {msg}") + + def skip(self, msg: str): + print(f"{self._prefix()}{self.YELLOW}-{self.RESET} {msg}") + + def warn(self, msg: str): + print(f"{self._prefix()}{self.RED}!{self.RESET} {msg}") + + def summary(self): + n = len(self.actions) + word = "would be" if self.dry_run else "were" + print(f"\n{'='*60}") + print(f" {n} change(s) {word} applied.") + if self.dry_run: + print(f" Re-run without --dry-run to apply them.") + print(f"{'='*60}") + + +def read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def write_text(path: Path, content: str, log: Logger): + if not log.dry_run: + path.write_text(content, encoding="utf-8") + + +# ────────────────────────────────────────────────────────────────── +# Step 1 — Remove unused npm dependencies from package.json +# ────────────────────────────────────────────────────────────────── + +def clean_package_json(root: Path, log: Logger): + pkg_path = root / "package.json" + if not pkg_path.exists(): + log.warn("package.json not found — skipping dependency cleanup.") + return + + log.info("Scanning package.json for unused & iOS-only dependencies ...") + pkg = json.loads(read_text(pkg_path)) + removed = [] + + # Remove unused dependencies + for section in ("dependencies", "devDependencies"): + deps: dict = pkg.get(section, {}) + for name in UNUSED_DEPS: + if name in deps: + del deps[name] + removed.append(f"{name} (from {section})") + + # Remove iOS-only devDependencies + dev_deps: dict = pkg.get("devDependencies", {}) + for name in IOS_ONLY_DEV_DEPS: + if name in dev_deps: + del dev_deps[name] + removed.append(f"{name} (iOS-only, from devDependencies)") + + # Remove iOS-only scripts + scripts: dict = pkg.get("scripts", {}) + for name in IOS_ONLY_SCRIPTS: + if name in scripts: + del scripts[name] + removed.append(f"script '{name}' (iOS-only)") + + if removed: + content = json.dumps(pkg, indent=2) + "\n" + write_text(pkg_path, content, log) + for r in removed: + log.done(f"Removed {r}") + else: + log.skip("No unused dependencies found in package.json.") + + +# ────────────────────────────────────────────────────────────────── +# Step 2 — Delete unnecessary files and directories +# ────────────────────────────────────────────────────────────────── + +def delete_files(root: Path, log: Logger): + log.info("Checking for files to delete ...") + for rel in FILES_TO_DELETE: + target = root / rel + if target.exists(): + if not log.dry_run: + target.unlink() + log.done(f"Deleted {rel}") + else: + log.skip(f"{rel} already absent.") + + +def delete_dirs(root: Path, log: Logger): + log.info("Checking for directories to delete ...") + for rel in DIRS_TO_DELETE: + target = root / rel + if target.exists() and target.is_dir(): + if not log.dry_run: + shutil.rmtree(target) + log.done(f"Deleted {rel}\\ directory") + else: + log.skip(f"{rel}\\ already absent.") + + +# ────────────────────────────────────────────────────────────────── +# Step 3 — Clean .gitignore (remove iOS / Xcode / CocoaPods entries) +# ────────────────────────────────────────────────────────────────── + +def clean_gitignore(root: Path, log: Logger): + gi_path = root / ".gitignore" + if not gi_path.exists(): + log.skip(".gitignore not found — skipping.") + return + + log.info("Cleaning iOS/Xcode/CocoaPods entries from .gitignore ...") + content = read_text(gi_path) + lines = content.splitlines() + cleaned = [] + removed_count = 0 + + in_xcode_block = False + in_ruby_block = False + + for line in lines: + stripped = line.strip() + + # Detect Xcode block start + if stripped == "# Xcode": + in_xcode_block = True + removed_count += 1 + continue + + # Detect Ruby / CocoaPods block start + if stripped == "# Ruby / CocoaPods": + in_ruby_block = True + removed_count += 1 + continue + + # Inside Xcode block — skip until next section header + if in_xcode_block: + if stripped.startswith("# ") and stripped not in ("# Xcode", "#"): + in_xcode_block = False + # fall through to keep this line (it's the next section) + else: + removed_count += 1 + continue + + # Inside Ruby/CocoaPods block — skip until blank line or next section + if in_ruby_block: + if stripped == "" or (stripped.startswith("# ") and stripped != "# Ruby / CocoaPods"): + in_ruby_block = False + if stripped == "": + continue # eat the trailing blank line + # fall through for next section header + else: + removed_count += 1 + continue + + cleaned.append(line) + + if removed_count > 0: + new_content = "\n".join(cleaned).strip() + "\n" + write_text(gi_path, new_content, log) + log.done(f"Removed {removed_count} iOS/Xcode/CocoaPods lines from .gitignore") + else: + log.skip("No iOS entries found in .gitignore.") + + +# ────────────────────────────────────────────────────────────────── +# Step 4 — Verify no dangling imports remain +# ────────────────────────────────────────────────────────────────── + +def verify_no_dangling_imports(root: Path, log: Logger): + """Scan .ts/.tsx/.js/.jsx files for imports of removed packages.""" + log.info("Verifying no source files import removed packages ...") + problems = [] + for ext in ("*.ts", "*.tsx", "*.js", "*.jsx"): + for path in root.rglob(ext): + if "node_modules" in str(path): + continue + try: + src = read_text(path) + except (UnicodeDecodeError, PermissionError): + continue + for pkg in UNUSED_DEPS: + if pkg in src: + problems.append((path.relative_to(root), pkg)) + + if problems: + log.warn("Dangling imports detected — manual cleanup needed:") + for fpath, pkg in problems: + log.warn(f" {fpath} still references '{pkg}'") + else: + log.done("No source files reference removed packages.") + + +# ────────────────────────────────────────────────────────────────── +# Main +# ────────────────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser( + description="Strip bloatware from the AlarmApp React Native project (Android / Windows).", + ) + parser.add_argument( + "project_dir", + nargs="?", + default=".", + help="Path to the AlarmApp root directory (default: current dir).", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Preview changes without modifying any files.", + ) + args = parser.parse_args() + + root = Path(args.project_dir).resolve() + if not (root / "package.json").exists(): + print(f"Error: {root} does not look like a React Native project " + f"(no package.json found).", file=sys.stderr) + sys.exit(1) + + log = Logger(dry_run=args.dry_run) + + print() + print("============================================================") + print(" AlarmApp - Bloatware Removal Tool (Android) ") + print("============================================================") + print(f" Project : {root}") + print(f" Mode : {'DRY RUN' if args.dry_run else 'LIVE'}") + print() + + clean_package_json(root, log) + delete_files(root, log) + delete_dirs(root, log) + clean_gitignore(root, log) + verify_no_dangling_imports(root, log) + + log.summary() + + if not args.dry_run: + print("\n Next steps:") + print(f" 1. cd {root}") + print( " 2. npm install # regenerate a clean lockfile") + print( " 3. npm run android # build & launch on emulator/device") + print() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/AlarmApp/scripts/install_depends b/AlarmApp/scripts/install_depends index c425a61..e9be2ce 100755 --- a/AlarmApp/scripts/install_depends +++ b/AlarmApp/scripts/install_depends @@ -15,6 +15,7 @@ FIX_INSTALL="npm audit fix" depends_list=( \ "react-native-sound" \ "react-native-system-setting" \ + "@notifee/react-native" \ )