Compare commits

...

25 commits

Author SHA1 Message Date
e6f23b83be
fix(ios): BGTaskSchedulerPermittedIdentifiers + prebuild wip 2025-07-19 11:51:22 +02:00
f11f9d8371 chore(release): 1.11.1 2025-07-18 11:18:02 +02:00
4aff7380ce chore(ios): install script 2025-07-18 10:56:19 +02:00
7501c04bbd
chore(release): 1.11.0 2025-07-12 08:24:42 +02:00
3aebd4cd1c
docs: adb udev rules 2025-07-10 07:28:33 +02:00
aea3a26096
fix(notification): android background-geolocation-lost 2025-07-09 22:29:51 +02:00
b635f29f45
fix: wip 2025-07-09 22:25:57 +02:00
fd081d46a6
feat(anchor): bglost notification scroll to permissions + wip 2025-07-09 17:50:07 +02:00
c1b220f007
feat: optout sentry reporting 2025-07-08 18:45:12 +02:00
754e14946c
fix: menu paramètres 2025-07-08 16:44:16 +02:00
48b663799d
fix: title_permission_required 2025-07-08 13:56:11 +02:00
894d26dad1
fix: message_permission_required 2025-07-08 13:51:13 +02:00
7f30ef9abf
fix: force location sync storage effect on interval 2025-07-06 18:58:17 +02:00
65b86bbb13 chore(release): 1.10.9 2025-07-06 13:45:53 +02:00
23ecee5061 fix(ios): upgrade bundling 2025-07-06 13:45:41 +02:00
9bc7e3b7cb chore(release): 1.10.8 2025-07-04 10:25:55 +02:00
dbfcc1e405 chore(release): 1.10.7 2025-07-04 10:24:17 +02:00
732dc3df7b fix: bg location force sync all 12 hours 2025-07-04 10:22:59 +02:00
6d5f5ec82d chore(release): 1.10.7 2025-07-04 10:09:41 +02:00
8b1c5291d4 fix(ios): up ios version for rn compat 2025-07-04 10:08:17 +02:00
eaa8197bc3 chore(release): 1.10.6 2025-07-04 09:38:44 +02:00
1b67de2507 Revert "fix(android): foreground service"
This reverts commit 0ac28515df.
2025-07-04 09:38:28 +02:00
b22c1b7e75 chore(release): 1.10.5 2025-07-04 09:10:56 +02:00
2fa7b4839a fix: bg location lost notif 2025-07-04 09:02:18 +02:00
4e2ea42195 fix: theming 2025-07-03 17:53:40 +02:00
44 changed files with 2970 additions and 2550 deletions

View file

@ -2,6 +2,93 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [1.11.1](https://github.com/alerte-secours/as-app/compare/v1.11.0...v1.11.1) (2025-07-18)
## [1.11.0](https://github.com/alerte-secours/as-app/compare/v1.10.9...v1.11.0) (2025-07-12)
### Features
* **anchor:** bglost notification scroll to permissions + wip ([fd081d4](https://github.com/alerte-secours/as-app/commit/fd081d46a657059353bfb1e6022b68eedaee4e1a))
* optout sentry reporting ([c1b220f](https://github.com/alerte-secours/as-app/commit/c1b220f0078db8d07b3d58a7ac146d8af159a17d))
### Bug Fixes
* force location sync storage effect on interval ([7f30ef9](https://github.com/alerte-secours/as-app/commit/7f30ef9abf11bbdade5c3db3bfd7269dc212b39c))
* menu paramètres ([754e149](https://github.com/alerte-secours/as-app/commit/754e14946c0fa397558e5983a39c082c9072fe5c))
* message_permission_required ([894d26d](https://github.com/alerte-secours/as-app/commit/894d26dad12c4736cde827232764ed5c8d77df4e))
* **notification:** android background-geolocation-lost ([aea3a26](https://github.com/alerte-secours/as-app/commit/aea3a2609639f01b1d7aa414c67e3bdb99a1a47c))
* title_permission_required ([48b6637](https://github.com/alerte-secours/as-app/commit/48b663799d4bb4ef69671f0291a77a65418e2544))
* wip ([b635f29](https://github.com/alerte-secours/as-app/commit/b635f29f45928210bc9a9a8c5d201d91a6b48a07))
## [1.10.9](https://github.com/alerte-secours/as-app/compare/v1.10.7...v1.10.9) (2025-07-06)
### Bug Fixes
* bg location force sync all 12 hours ([732dc3d](https://github.com/alerte-secours/as-app/commit/732dc3df7b6ab635c70d02d657d066e6c72a49c4))
* **ios:** upgrade bundling ([23ecee5](https://github.com/alerte-secours/as-app/commit/23ecee5061e5e7324407223e7c1ce9a815f96521))
## [1.10.8](https://github.com/alerte-secours/as-app/compare/v1.10.7...v1.10.8) (2025-07-04)
### Bug Fixes
* bg location force sync all 12 hours ([732dc3d](https://github.com/alerte-secours/as-app/commit/732dc3df7b6ab635c70d02d657d066e6c72a49c4))
## [1.10.7](https://github.com/alerte-secours/as-app/compare/v1.10.1...v1.10.7) (2025-07-04)
### Bug Fixes
* **android:** foreground service ([0ac2851](https://github.com/alerte-secours/as-app/commit/0ac28515dff752af30e45ff2f88d52da46c591aa))
* back to stateless refresh (sync endpoint) ([6af5875](https://github.com/alerte-secours/as-app/commit/6af58755c1b3e8c7be0d117d6fc07278406a0459))
* **battery-opti-disable:** integrate permissions view ([d9b5d10](https://github.com/alerte-secours/as-app/commit/d9b5d10684446a01873810382e501e45cd19da1b))
* **battery-opti-disable:** wip ([7082161](https://github.com/alerte-secours/as-app/commit/7082161b7f004b1052adbce2cf3ac9f74b2eee03))
* bettery optimization glitch ([b4b7441](https://github.com/alerte-secours/as-app/commit/b4b7441bacc3728d684a6e2219c84b91c75461af))
* bg location lost notif ([2fa7b48](https://github.com/alerte-secours/as-app/commit/2fa7b4839aedec61a5fdf2e5292440b3b0bb9c6b))
* don't handle refresh in headless mode anymore ([b10ff5a](https://github.com/alerte-secours/as-app/commit/b10ff5a6e735df6323d70a3c5b0d651d1cbd01cc))
* **headless-task:** wip ([c947d49](https://github.com/alerte-secours/as-app/commit/c947d4915ab19377cd2263a86bc9f902c41defdc))
* **headless-task:** wip ([6c290f2](https://github.com/alerte-secours/as-app/commit/6c290f21b4d57513aa90dabcc68e9a7b074a6430))
* **headless:** async-storage in memory first ([9f6452d](https://github.com/alerte-secours/as-app/commit/9f6452d5e368c82e0f93afd06c354756dd7913db))
* **headless:** secure store in memory first ([4280820](https://github.com/alerte-secours/as-app/commit/4280820e0169c4f9e36793d3f0ad460e9bde558f))
* **headless:** use axios instead of apollo for auth ([6444801](https://github.com/alerte-secours/as-app/commit/644480182d8da2ca1197c919262d5a5df0817d25))
* **headless:** use fetch instead of axios for auth ([09ea8cd](https://github.com/alerte-secours/as-app/commit/09ea8cd5634f46c1daeeb24ad4230ee508bd8293))
* import typo generateAlertEmergencyInfoContent ([d780fb4](https://github.com/alerte-secours/as-app/commit/d780fb4190acc24ceb9811682f8684ffb232a716))
* improve error handling ([010aa2c](https://github.com/alerte-secours/as-app/commit/010aa2c2fc0cf80c6afd3eb4fe954f94c94b8bea))
* **ios:** up ios version for rn compat ([8b1c529](https://github.com/alerte-secours/as-app/commit/8b1c5291d4ce0b042d745f76fc6dc62cf8bd6ca4))
* known keys ([be0cd62](https://github.com/alerte-secours/as-app/commit/be0cd62cb943fb4d74243f78df2fb1fc29e97e76))
* memoryAsyncStorage ([6e290bd](https://github.com/alerte-secours/as-app/commit/6e290bdb6997dd06ee8d674087368ad68b97a881))
* prevent race condition ([6a77336](https://github.com/alerte-secours/as-app/commit/6a773367d49ef66a8a896ba18e63c660f30eb143))
* **profile:** button enregistrer should be greysed when stored or no change ([9cfb40e](https://github.com/alerte-secours/as-app/commit/9cfb40e510584567400a6ea6f4871b345d723ac6))
* re-enable expo-updates ([16332cb](https://github.com/alerte-secours/as-app/commit/16332cbb764be5f067aa7cb3c2713399d1959f7c))
* re-up ([8331029](https://github.com/alerte-secours/as-app/commit/8331029e91220cc9c679dce1058ffc5f2cbb0577))
* reduce tracesSampleRate ([b5ae235](https://github.com/alerte-secours/as-app/commit/b5ae235ba4f093451bb7bd41b19aa77a8a0bda96))
* remove debuggin ([b70d6ed](https://github.com/alerte-secours/as-app/commit/b70d6ed9a070e9c01fa0d5206db5cc1a115e074e))
* sentry + re-enable expo-updates + disable debug ([8e8e120](https://github.com/alerte-secours/as-app/commit/8e8e120391f197e7826edaef47817ff8fb14a6b8))
* sentry tracing ([e6924ac](https://github.com/alerte-secours/as-app/commit/e6924ac9ff22b114eff4293f007becebe44e286d))
* sentry tracing ([4a0f3ab](https://github.com/alerte-secours/as-app/commit/4a0f3ab7effd79f75efd976f5fd9e1d1531e5b19))
* sentry tracing ([8ba4056](https://github.com/alerte-secours/as-app/commit/8ba4056187e6bd9ebf8ceec881197d6dc0aaaebc))
* theming ([4e2ea42](https://github.com/alerte-secours/as-app/commit/4e2ea4219501c16f0e74fcd2a5d48dca9845b3ee))
* undefined error ([47f11d1](https://github.com/alerte-secours/as-app/commit/47f11d1b888f4372fcb03db9bd1e839495455df3))
* undefined error ([d4de0b4](https://github.com/alerte-secours/as-app/commit/d4de0b4541fedb29b3e5161f91fc65b3253308e0))
* up android target sdk version ([7918e74](https://github.com/alerte-secours/as-app/commit/7918e74184165509b7a76c2420c24fcf6629a5fa))
* **upgrade:** expo 52 + rn 0.76.9 ([a1ed6cf](https://github.com/alerte-secours/as-app/commit/a1ed6cfca6217ba2068908f12e78af990f91b9c5))
* **wizard hero:** battery opti red inside parameters bubble ([dccf361](https://github.com/alerte-secours/as-app/commit/dccf361dbcb23239d28ea7f860ddeae3377349c0))
* bg location force sync all 12 hours ([71b73e7](https://github.com/alerte-secours/as-app/commit/71b73e7ba4733bd46345d8f73a2a33bb8786726a))
* **ios:** up ios version for rn compat ([8b1c529](https://github.com/alerte-secours/as-app/commit/8b1c5291d4ce0b042d745f76fc6dc62cf8bd6ca4))
## [1.10.6](https://github.com/alerte-secours/as-app/compare/v1.10.5...v1.10.6) (2025-07-04)
## [1.10.5](https://github.com/alerte-secours/as-app/compare/v1.10.4...v1.10.5) (2025-07-04)
### Bug Fixes
* bg location lost notif ([2fa7b48](https://github.com/alerte-secours/as-app/commit/2fa7b4839aedec61a5fdf2e5292440b3b0bb9c6b))
* theming ([4e2ea42](https://github.com/alerte-secours/as-app/commit/4e2ea4219501c16f0e74fcd2a5d48dca9845b3ee))
## [1.10.4](https://github.com/alerte-secours/as-app/compare/v1.10.3...v1.10.4) (2025-07-03)

View file

@ -83,8 +83,8 @@ android {
applicationId 'com.alertesecours'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 184
versionName "1.10.4"
versionCode 191
versionName "1.11.1"
multiDexEnabled true
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View file

@ -3,9 +3,6 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
@ -47,8 +44,6 @@
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://expo-updates.alertesecours.fr/api/manifest?project=alerte-secours&amp;channel=release"/>
<service android:name="com.transistorsoft.locationmanager.service.LocationRequestService" android:foregroundServiceType="location|dataSync" android:enabled="true" android:exported="false" tools:replace="android:foregroundServiceType"/>
<service android:name="com.transistorsoft.backgroundfetch.BackgroundFetchService" android:foregroundServiceType="dataSync" android:enabled="true" android:exported="false"/>
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@ -69,6 +64,10 @@
<action android:name="com.alertesecours.OPEN_RELATIVES"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="com.alertesecours.OPEN_BACKGROUND_GEOLOCATION_SETTINGS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter android:autoVerify="true" data-generated="true">
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https" android:host="app.alertesecours.fr" android:pathPrefix="/"/>

View file

@ -0,0 +1,7 @@
<resources>
<string name="app_name">Alerte Secours</string>
<!-- French permission message for background geolocation -->
<string name="message_permission_required">Alerte Secours nécessite la localisation en arrière-plan pour les alertes de proximité.</string>
<string name="title_permission_required">Autorisation requise</string>
</resources>

View file

@ -4,4 +4,6 @@
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
<string name="expo_system_ui_user_interface_style" translatable="false">automatic</string>
<string name="expo_runtime_version">1.0.0</string>
<string name="message_permission_required">Alerte Secours nécessite la localisation en arrière-plan pour les alertes de proximité.</string>
<string name="title_permission_required">Autorisation requise</string>
</resources>

View file

@ -131,12 +131,12 @@ let config = {
"tel",
"telprompt",
],
BGTaskSchedulerPermittedIdentifiers: [
"com.transistorsoft.fetch",
"com.transistorsoft.customtask",
],
},
UIBackgroundModes: ["location", "fetch", "processing"],
BGTaskSchedulerPermittedIdentifiers: [
"com.transistorsoft.fetch",
"com.transistorsoft.customtask",
],
},
plugins: [
[

View file

@ -97,3 +97,72 @@ You can run this script directly:
export DEVICE=emulator-5554
./install-android.sh
```
# enable USB debug mode
Add udev rules for your device
1. First, find your device's vendor ID:
```sh
lsusb
```
Look for your phone manufacturer (e.g., Google, Samsung, OnePlus). Note the ID like 18d1:4ee7
2. Create/edit the udev rules file:
```sh
sudo micro /etc/udev/rules.d/51-android.rules
```
3. Add a line for your device. Here are common manufacturers:
```
# Google
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"
# Samsung
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666", GROUP="plugdev"
# OnePlus
SUBSYSTEM=="usb", ATTR{idVendor}=="2a70", MODE="0666", GROUP="plugdev"
# Xiaomi
SUBSYSTEM=="usb", ATTR{idVendor}=="2717", MODE="0666", GROUP="plugdev"
```
4. Set proper permissions and reload rules:
```sh
sudo chmod a+r /etc/udev/rules.d/51-android.rules
sudo udevadm control --reload-rules
sudo service udev restart
```
5. Add yourself to the plugdev group:
```sh
sudo usermod -aG plugdev $USER
```
Force the authorization prompt
1. Kill adb server:
```sh
adb kill-server
```
2. Unplug your device
3. On your phone:
- Go to Developer options
- Revoke USB debugging authorizations
- Toggle USB debugging OFF then ON
4. Plug the device back in
5. Start adb with proper permissions:
```sh
adb start-server
adb devices
```
6. The authorization prompt should now appear on your phone

View file

@ -19,7 +19,7 @@ import onMessageReceived from "~/notifications/onMessageReceived";
import { createLogger } from "~/lib/logger";
import * as Sentry from "@sentry/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { memoryAsyncStorage } from "~/storage/memoryAsyncStorage";
import { STORAGE_KEYS } from "~/storage/storageKeys";
// setup notification, this have to stay in index.js
@ -32,13 +32,13 @@ messaging().setBackgroundMessageHandler(onMessageReceived);
registerRootComponent(App);
// Constants for persistence
const FORCE_SYNC_INTERVAL = 24 * 60 * 60 * 1000;
const FORCE_SYNC_INTERVAL = 12 * 60 * 60 * 1000;
// const FORCE_SYNC_INTERVAL = 5 * 60 * 1000; // DEBUGGING
// Helper functions for persisting sync time
const getLastSyncTime = async () => {
try {
const value = await AsyncStorage.getItem(
const value = await memoryAsyncStorage.getItem(
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
);
return value ? parseInt(value, 10) : Date.now();
@ -52,7 +52,7 @@ const getLastSyncTime = async () => {
const setLastSyncTime = async (time) => {
try {
await AsyncStorage.setItem(
await memoryAsyncStorage.setItem(
STORAGE_KEYS.GEOLOCATION_LAST_SYNC_TIME,
time.toString(),
);
@ -139,7 +139,7 @@ const HeadlessTask = async (event) => {
geolocBgLogger.debug("getCurrentPosition result", { location });
if (timeSinceLastSync >= FORCE_SYNC_INTERVAL) {
geolocBgLogger.info("Forcing location sync after 24h");
geolocBgLogger.info("Forcing location sync");
try {
// Change pace to ensure location updates with timeout

249
install-ios.sh Executable file
View file

@ -0,0 +1,249 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_error() {
echo -e "${RED}Error: $1${NC}" >&2
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}Warning: $1${NC}"
}
print_info() {
echo -e "${BLUE}$1${NC}"
}
print_plain() {
echo -e "$1"
}
# Check if Xcode command line tools are available
if ! command -v xcrun &> /dev/null; then
print_error "Xcode command line tools are not installed."
print_info "Please install them by running: xcode-select --install"
exit 1
fi
# Define IPA path
IPA_PATH="ios/build/AlerteSecours.ipa"
# Check if IPA file exists
if [ ! -f "$IPA_PATH" ]; then
print_error "IPA file not found at: $IPA_PATH"
print_info "Please build the iOS bundle first by running:"
print_info " yarn bundle:ios"
exit 1
fi
print_info "Found IPA file: $IPA_PATH"
# Function to get connected physical iOS devices
get_physical_devices() {
xcrun devicectl list devices 2>/dev/null | awk 'NR>2 && $4=="available" {print $3}' || true
}
# Function to get available simulators (booted ones)
get_booted_simulators() {
xcrun simctl list devices | grep -E "\(Booted\)" | sed -E 's/.*\(([A-F0-9-]{36})\) \(Booted\)/\1/' || true
}
# Function to get simulator name by UDID
get_simulator_name() {
local udid="$1"
xcrun simctl list devices | grep "$udid" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | xargs
}
# Function to get device name by UDID (for physical devices)
get_device_name() {
local udid="$1"
xcrun devicectl list devices 2>/dev/null | awk -v target_udid="$udid" 'NR>2 && $3==target_udid {print $1}' || echo "Unknown Device"
}
# Function to validate physical device UDID
validate_physical_device() {
local device_id="$1"
local devices=$(get_physical_devices)
if [ -z "$devices" ]; then
return 1
fi
echo "$devices" | grep -q "^$device_id$"
}
# Function to validate simulator UDID
validate_simulator() {
local simulator_id="$1"
local simulators=$(get_booted_simulators)
if [ -z "$simulators" ]; then
return 1
fi
echo "$simulators" | grep -q "^$simulator_id$"
}
# Function to install on physical device
install_on_device() {
local device_id="$1"
local device_name=$(get_device_name "$device_id")
print_info "Installing on physical device: $device_name ($device_id)"
if xcrun devicectl device install app --device "$device_id" "$IPA_PATH"; then
print_success "Installation completed successfully on $device_name!"
return 0
else
print_error "Installation failed on $device_name"
print_info "Common solutions:"
print_info " 1. Make sure the device is unlocked and trusted"
print_info " 2. Check that the provisioning profile matches the device"
print_info " 3. Verify the device has enough storage space"
print_info " 4. Try disconnecting and reconnecting the device"
return 1
fi
}
# Function to install on simulator
install_on_simulator() {
local simulator_id="$1"
local simulator_name=$(get_simulator_name "$simulator_id")
print_info "Installing on simulator: $simulator_name ($simulator_id)"
if xcrun simctl install "$simulator_id" "$IPA_PATH"; then
print_success "Installation completed successfully on $simulator_name!"
return 0
else
print_error "Installation failed on $simulator_name"
print_info "Make sure the simulator is booted and try again"
return 1
fi
}
# Main installation logic
if [ -n "$IOS_DEVICE" ]; then
# Specific physical device requested
print_info "Using specified physical device: $IOS_DEVICE"
if ! validate_physical_device "$IOS_DEVICE"; then
print_error "Physical device $IOS_DEVICE is not connected or not found."
print_info "Connected physical devices:"
physical_devices=$(get_physical_devices)
if [ -n "$physical_devices" ]; then
echo "$physical_devices" | while read -r device; do
device_name=$(get_device_name "$device")
print_info " - $device ($device_name)"
done
else
print_info " No physical devices found"
fi
exit 1
fi
install_on_device "$IOS_DEVICE"
elif [ -n "$IOS_SIMULATOR" ]; then
# Specific simulator requested
print_info "Using specified simulator: $IOS_SIMULATOR"
if ! validate_simulator "$IOS_SIMULATOR"; then
print_error "Simulator $IOS_SIMULATOR is not booted or not found."
print_info "Booted simulators:"
booted_simulators=$(get_booted_simulators)
if [ -n "$booted_simulators" ]; then
echo "$booted_simulators" | while read -r sim; do
sim_name=$(get_simulator_name "$sim")
print_info " - $sim ($sim_name)"
done
else
print_info " No booted simulators found"
print_info " Start a simulator from Xcode or run: xcrun simctl boot <simulator-udid>"
fi
exit 1
fi
install_on_simulator "$IOS_SIMULATOR"
else
# Auto-detect: prefer physical devices, fallback to simulators
print_info "Auto-detecting iOS targets..."
# Try physical devices first
physical_devices=$(get_physical_devices)
if [ -n "$physical_devices" ]; then
target_device=$(echo "$physical_devices" | head -n 1)
device_count=$(echo "$physical_devices" | wc -l | tr -d ' ')
if [ "$device_count" -gt 1 ]; then
print_warning "Multiple physical devices found. Using first device: $target_device"
print_info "Available physical devices:"
echo "$physical_devices" | while read -r device; do
device_name=$(get_device_name "$device")
if [ "$device" = "$target_device" ]; then
print_info " - $device ($device_name) [selected]"
else
print_info " - $device ($device_name)"
fi
done
print_info "To use a specific device, run: IOS_DEVICE=<device-udid> yarn install:ios"
else
device_name=$(get_device_name "$target_device")
print_info "Using physical device: $device_name ($target_device)"
fi
install_on_device "$target_device"
else
# No physical devices, try simulators
print_info "No physical devices found. Looking for booted simulators..."
booted_simulators=$(get_booted_simulators)
if [ -n "$booted_simulators" ]; then
target_simulator=$(echo "$booted_simulators" | head -n 1)
simulator_count=$(echo "$booted_simulators" | wc -l | tr -d ' ')
if [ "$simulator_count" -gt 1 ]; then
print_warning "Multiple booted simulators found. Using first simulator: $target_simulator"
print_info "Available booted simulators:"
echo "$booted_simulators" | while read -r sim; do
sim_name=$(get_simulator_name "$sim")
if [ "$sim" = "$target_simulator" ]; then
print_info " - $sim ($sim_name) [selected]"
else
print_info " - $sim ($sim_name)"
fi
done
print_info "To use a specific simulator, run: IOS_SIMULATOR=<simulator-udid> yarn install:ios"
else
simulator_name=$(get_simulator_name "$target_simulator")
print_info "Using simulator: $simulator_name ($target_simulator)"
fi
install_on_simulator "$target_simulator"
else
print_error "No iOS devices or booted simulators found."
print_info "Please either:"
print_info " 1. Connect and trust an iOS device, or"
print_info " 2. Boot a simulator from Xcode"
print_info ""
print_info "Usage examples:"
print_info " Auto-detect: yarn install:ios"
print_info " Specific device: IOS_DEVICE=<device-udid> yarn install:ios"
print_info " Specific simulator: IOS_SIMULATOR=<simulator-udid> yarn install:ios"
exit 1
fi
fi
fi

View file

@ -1,16 +1,11 @@
# This is used by the React Native CLI to control various options
# Configuration name to load
CONFIGURATION_NAME=Debug
# Path to the Xcode project
PROJECT_PATH="AlerteSecours.xcodeproj"
# Whether to enable the New Architecture
RCT_NEW_ARCH_ENABLED=0
# Whether to enable Hermes
USE_HERMES=1
# 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.
# Sentry Configuration
export SENTRY_PROPERTIES="ios/sentry.properties"
export AUTO_RELEASE=true
export SENTRY_CLI_EXTRA_ARGS="--log-level debug"
export SENTRY_CLI_RN_XCODE_EXTRA_ARGS="--allow-fetch"
export SENTRY_INCLUDE_NATIVE_SOURCES=true
# 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)

File diff suppressed because one or more lines are too long

View file

@ -1,24 +1,21 @@
#import "AppDelegate.h"
#import <Firebase/Firebase.h>
// @generated begin react-native-background-fetch-import - expo prebuild (DO NOT MODIFY) sync-fb890e6efd6cc6e67ebbda1087e0a6d7e0bcc527
#import <TSBackgroundFetch/TSBackgroundFetch.h>
// @generated end react-native-background-fetch-import
#import <Firebase/Firebase.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
#import <Firebase.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
self.moduleName = @"main";
// see https://github.com/invertase/react-native-firebase/issues/7788#issuecomment-2211820768
// and https://rnfirebase.io/#configure-react-native-firebase-modules
[FIRApp configure];
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>YOUR_API_KEY_HERE</string>
<key>GCM_SENDER_ID</key>
<string>YOUR_GCM_SENDER_ID_HERE</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.alertesecours.alertesecours</string>
<key>PROJECT_ID</key>
<string>YOUR_PROJECT_ID_HERE</string>
<key>STORAGE_BUCKET</key>
<string>YOUR_STORAGE_BUCKET_HERE</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>YOUR_GOOGLE_APP_ID_HERE</string>
</dict>
</plist>

View file

@ -1,21 +0,0 @@
{
"images": [
{
"idiom": "universal",
"filename": "image.png",
"scale": "1x"
},
{
"idiom": "universal",
"scale": "2x"
},
{
"idiom": "universal",
"scale": "3x"
}
],
"info": {
"version": 1,
"author": "expo"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

View file

@ -1,21 +0,0 @@
{
"images": [
{
"idiom": "universal",
"filename": "image.png",
"scale": "1x"
},
{
"idiom": "universal",
"scale": "2x"
},
{
"idiom": "universal",
"scale": "3x"
}
],
"info": {
"version": 1,
"author": "expo"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 B

View file

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.transistorsoft.fetch</string>
<string>com.transistorsoft.customtask</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -19,7 +24,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.10.4</string>
<string>1.11.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -42,7 +47,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>184</string>
<string>191</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
<device id="retina5_5" orientation="portrait" appearance="light"/>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -12,34 +13,29 @@
<objects>
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" insetsLayoutMarginsFromSafeArea="NO" image="SplashScreenBackground" translatesAutoresizingMaskIntoConstraints="NO" id="EXPO-SplashScreenBackground" userLabel="SplashScreenBackground">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
</imageView>
<imageView id="EXPO-SplashScreen" userLabel="SplashScreenLogo" image="SplashScreenLogo" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
<rect key="frame" x="0" y="0" width="414" height="736"/>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
<constraints>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="83fcb9b545b870ba44c24f0feeb116490c499c52"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="61d16215e44b98e39d0a2c74fdbfaaa22601b12c"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="f934da460e9ab5acae3ad9987d5b676a108796c1"/>
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
<color key="backgroundColor" name="SplashScreenBackground"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="140.625" y="129.4921875"/>
<point key="canvasLocation" x="0.0" y="0.0"/>
</scene>
</scenes>
<resources>
<image name="SplashScreenBackground" width="1" height="1"/>
<image name="SplashScreen" width="414" height="736"/>
<image name="SplashScreenLogo" width="414" height="736"/>
<namedColor name="SplashScreenBackground">
<color alpha="1.000" blue="0.780392156862745" green="0.309803921568627" red="0.211764705882353" customColorSpace="sRGB" colorSpace="custom"/>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>ERROR_RECOVERY_ONLY</string>
<key>EXUpdatesCodeSigningCertificate</key>
<string>YOUR_CODE_SIGNING_CERTIFICATE_HERE</string>
<key>EXUpdatesCodeSigningMetadata</key>
<dict>
<key>keyid</key>
<string>main</string>
<key>alg</key>
<string>rsa-v1_5-sha256</string>
</dict>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesRuntimeVersion</key>
<string>1.0.0</string>
<key>EXUpdatesURL</key>
<string>https://expo-updates.alertesecours.fr/api/manifest?project=alerte-secours&amp;channel=release</string>
</dict>
</plist>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>YOUR_API_KEY_HERE</string>
<key>GCM_SENDER_ID</key>
<string>YOUR_GCM_SENDER_ID_HERE</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.alertesecours.alertesecours</string>
<key>PROJECT_ID</key>
<string>YOUR_PROJECT_ID_HERE</string>
<key>STORAGE_BUCKET</key>
<string>YOUR_STORAGE_BUCKET_HERE</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>YOUR_GOOGLE_APP_ID_HERE</string>
</dict>
</plist>

View file

@ -1,19 +1,5 @@
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
# see https://github.com/zoontek/react-native-permissions?tab=readme-ov-file#ios
# require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
def node_require(script)
# Resolve script with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
"require.resolve(
'#{script}',
{paths: [process.argv[1]]},
)", __dir__]).strip
end
# Use it to require both react-native's and this package's scripts:
node_require('react-native/scripts/react_native_pods.rb')
node_require('react-native-permissions/scripts/setup.rb')
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
@ -21,11 +7,18 @@ podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties
ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
use_autolinking_method_symbol = ('use' + '_native' + '_modules!').to_sym
origin_autolinking_method = self.method(use_autolinking_method_symbol)
self.define_singleton_method(use_autolinking_method_symbol) do |*args|
if ENV['EXPO_UNSTABLE_CORE_AUTOLINKING'] == '1'
Pod::UI.puts('Using expo-modules-autolinking as core autolinking source'.green)
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
install! 'cocoapods',
:deterministic_uuids => false
prepare_react_native_project!
target 'AlerteSecours' do
use_expo_modules!
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
else
config_command = [
'node',
'--no-warnings',
@ -36,53 +29,13 @@ self.define_singleton_method(use_autolinking_method_symbol) do |*args|
'--platform',
'ios'
]
origin_autolinking_method.call(config_command)
else
origin_autolinking_method.call()
end
end
platform :ios, '13.4'
install! 'cocoapods',
:deterministic_uuids => false
prepare_react_native_project!
# ⬇️ uncomment the permissions you need
setup_permissions([
# 'AppTrackingTransparency',
# 'Bluetooth',
# 'Calendars',
# 'CalendarsWriteOnly',
'Camera',
'Contacts',
# 'FaceID',
'LocationAccuracy',
'LocationAlways',
'LocationWhenInUse',
# 'MediaLibrary',
'Microphone',
# 'Motion',
'Notifications',
'PhotoLibrary',
# 'PhotoLibraryAddOnly',
# 'Reminders',
# 'Siri',
# 'SpeechRecognition',
# 'StoreKit',
])
target 'AlerteSecours' do
use_expo_modules!
config = use_native_modules!
config = use_native_modules!(config_command)
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
# see https://rnfirebase.io/#configure-react-native-firebase-modules
use_frameworks! :linkage => :static
$RNFirebaseAsStaticFramework = true
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
@ -112,30 +65,5 @@ target 'AlerteSecours' do
end
end
end
# Set deployment target for all pods
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.4'
end
end
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
post_integrate do |installer|
begin
expo_patch_react_imports!(installer)
rescue => e
Pod::UI.warn e
end
end
end
# pod 'Firebase', :modular_headers => true
# pod 'FirebaseCoreInternal', :modular_headers => true
# pod 'GoogleUtilities', :modular_headers => true
# pod 'FirebaseCore', :modular_headers => true
# pod 'FirebaseMessaging', :modular_headers => true

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
{
"expo.jsEngine": "hermes",
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
"newArchEnabled": "false",
"ios.useFrameworks": "static",
"apple.extraPods": "[]",
"apple.ccacheEnabled": "false",
"apple.privacyManifestAggregationEnabled": "true",
"newArchEnabled": "false"
"apple.privacyManifestAggregationEnabled": "true"
}

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -1,6 +1,6 @@
{
"name": "alerte-secours",
"version": "1.10.4",
"version": "1.11.1",
"main": "index.js",
"scripts": {
"start": "expo start --dev-client --private-key-path ./keys/private-key.pem",
@ -37,6 +37,7 @@
"e2e:test": "detox test --configuration android.emu.debug",
"e2e:run": "yarn start & yarn e2e:build && yarn e2e:deploy && yarn e2e:test",
"install:android": "./install-android.sh",
"install:ios": "./install-ios.sh",
"log:android": "adb -s $DEVICE logcat | grep -E 'ReactNativeJS: '",
"log:ios:simulator": "xcrun simctl spawn booted log stream --level debug --predicate 'subsystem contains \"com.facebook.react.log\" and processImagePath contains \"AlerteSecours\"'",
"log:ios": "idevicesyslog | grep -i 'AlerteSecours\\|ReactNative'",
@ -49,8 +50,8 @@
"screenshot:android": "scripts/screenshot-android.sh"
},
"customExpoVersioning": {
"versionCode": 184,
"buildNumber": 184
"versionCode": 191,
"buildNumber": 191
},
"commit-and-tag-version": {
"scripts": {

View file

@ -6,7 +6,7 @@ import { ErrorUtils } from "react-native";
import { createLogger } from "~/lib/logger";
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
import { authActions, permissionWizardActions } from "~/stores";
import { authActions, permissionWizardActions, paramsActions } from "~/stores";
import { secureStore } from "~/storage/memorySecureStore";
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
@ -62,6 +62,7 @@ const initializeStores = () => {
// Then initialize other stores sequentially
initializeStore("authActions", authActions.init);
initializeStore("permissionWizard", permissionWizardActions.init);
initializeStore("paramsActions", paramsActions.init);
initializeStore("storeSubscriptions", storeSubscriptions.init);
appLogger.info("Core initialization complete");

View file

@ -690,7 +690,7 @@ const useStyles = createStyles(({ wp, hp, scaleText, theme: { colors } }) => ({
color: colors.primary,
},
batteryOptimizationAlert: {
backgroundColor: colors.errorContainer || colors.surfaceVariant,
backgroundColor: colors.surfaceVariant,
padding: 15,
borderRadius: 6,
marginBottom: 15,

View file

@ -18,8 +18,8 @@ export default function DrawerItemList(props) {
const { routes } = state;
const section1 = routes.slice(0, 5);
const section2 = routes.slice(5, 8);
const section3 = routes.slice(8, routes.length);
const section2 = routes.slice(5, 9);
const section3 = routes.slice(9, routes.length);
return (
<>

View file

@ -0,0 +1,23 @@
import { navActions } from "~/stores";
import { createLogger } from "~/lib/logger";
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
const backgroundGeolocationLogger = createLogger({
module: BACKGROUND_SCOPES.NOTIFICATIONS,
feature: "action-open-background-geolocation-settings",
});
export default function actionOpenBackgroundGeolocationSettings({ data }) {
backgroundGeolocationLogger.debug(
"actionOpenBackgroundGeolocationSettings called",
);
navActions.setNextNavigation([
{
name: "Params",
params: {
anchor: "permissions",
},
},
]);
}

View file

@ -0,0 +1,26 @@
import { navActions } from "~/stores";
import { createLogger } from "~/lib/logger";
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
const settingsLogger = createLogger({
module: BACKGROUND_SCOPES.NOTIFICATIONS,
feature: "action-open-settings",
});
export default function actionOpenSettings({ data }) {
settingsLogger.debug("actionOpenSettings called", {
data,
hasData: !!data,
dataKeys: data ? Object.keys(data) : [],
});
navActions.setNextNavigation([
{
name: "Params",
},
]);
settingsLogger.debug("Navigation set to Params screen", {
navigationTarget: "Params",
});
}

View file

@ -0,0 +1,67 @@
import { createLogger } from "~/lib/logger";
import { BACKGROUND_SCOPES } from "~/lib/logger/scopes";
import { Light } from "~/theme/app";
import { displayNotification } from "../helpers";
import { generateBackgroundGeolocationLostContent } from "../content";
const { colors } = Light;
const backgroundGeolocationLogger = createLogger({
module: BACKGROUND_SCOPES.NOTIFICATIONS,
feature: "background-geolocation-channel",
});
const channelId = "system";
export default async function notifBackgroundGeolocationLost(data) {
backgroundGeolocationLogger.debug(
"Displaying background geolocation lost notification",
{
data,
},
);
// DEBUG: Log notification configuration for diagnosis
backgroundGeolocationLogger.info(
"DEBUG: Background geolocation notification config",
{
channelId,
pressActionId: "open-settings",
launchActivity: "default",
hasData: !!data,
dataKeys: data ? Object.keys(data) : [],
},
);
// Generate notification content
const { title, body, bigText } =
generateBackgroundGeolocationLostContent(data);
await displayNotification({
channelId,
title,
body,
data,
color: colors.warning || colors.primary,
bigText,
android: {
pressAction: {
id: "open-background-geolocation-settings",
launchActivity: "default",
},
actions: [
{
title: "Paramètres",
pressAction: {
id: "open-background-geolocation-settings",
launchActivity: "default",
},
},
],
},
});
backgroundGeolocationLogger.info(
"Background geolocation lost notification displayed successfully",
);
}

View file

@ -0,0 +1,10 @@
import { createChannel } from "../helpers";
const channelId = "system";
export async function createNotificationChannel() {
await createChannel({
id: channelId,
name: "Paramètres",
});
}

View file

@ -88,9 +88,9 @@ export const generateSuggestKeepOpenContent = (data) => {
export const generateBackgroundGeolocationLostContent = (data) => {
return {
title: `Localisation en arrière-plan désactivée`,
body: `Votre localisation en arrière-plan a été désactivée. Veuillez vérifier les paramètres de l'application.`,
bigText: `Votre localisation en arrière-plan a été désactivée. Pour continuer à utiliser pleinement l'application, veuillez vérifier les paramètres de votre appareil.`,
title: `Alerte-Secours ne peut plus accéder à votre position`,
body: `Vous ne pouvez plus recevoir d'alertes de proximité. Vérifiez les paramètres.`,
bigText: `Alerte-Secours ne peut plus accéder à votre position en arrière-plan. Vous ne pouvez plus recevoir d'alertes de proximité. Causes possibles : permissions révoquées, optimisation de batterie active, ou actualisation désactivée. Accédez aux paramètres de l'application pour réactiver.`,
};
};

View file

@ -7,6 +7,7 @@ import notifSuggestClose from "./channels/notifSuggestClose";
import notifSuggestKeepOpen from "./channels/notifSuggestKeepOpen";
import notifRelativeAllowAsk from "./channels/notifRelativeAllowAsk";
import notifRelativeInvitation from "./channels/notifRelativeInvitation";
import notifBackgroundGeolocationLost from "./channels/notifBackgroundGeolocationLost";
const displayLogger = createLogger({
module: BACKGROUND_SCOPES.NOTIFICATIONS,
@ -20,6 +21,7 @@ const SUPPORTED_ACTIONS = {
"suggest-keep-open": notifSuggestKeepOpen,
"relative-allow-ask": notifRelativeAllowAsk,
"relative-invitation": notifRelativeInvitation,
"background-geolocation-lost": notifBackgroundGeolocationLost,
};
export default async function displayNotificationHandler(data) {

View file

@ -1,6 +1,7 @@
import { VirtualNotificationTypes } from "./virtualNotifications";
import { getNotificationContent } from "./content";
import openSettings from "~/lib/native/openSettings";
import { navActions } from "~/stores";
export const getNotificationColor = (notification, theme) => {
const { colors } = theme;
@ -83,7 +84,16 @@ export const createNotificationHandlers = (handlers) => {
suggest_keep_open: async (data) => await openAlert({ data }),
relative_invitation: async (data) => await openRelatives({ data }),
relative_allow_ask: async (data) => await openRelatives({ data }),
background_geolocation_lost: async (data) => openSettings(),
background_geolocation_lost: async (_data) => {
navActions.setNextNavigation([
{
name: "Params",
params: {
anchor: "permissions",
},
},
]);
},
};
return {

View file

@ -14,6 +14,8 @@ import actionRelativeAllowAccept from "./actions/actionRelativeAllowAccept";
import actionRelativeAllowReject from "./actions/actionRelativeAllowReject";
import actionRelativeInvitationAccept from "./actions/actionRelativeInvitationAccept";
import actionRelativeInvitationReject from "./actions/actionRelativeInvitationReject";
import actionOpenSettings from "./actions/actionOpenSettings";
import actionOpenBackgroundGeolocationSettings from "./actions/actionOpenBackgroundGeolocationSettings";
import { navActions } from "~/stores";
@ -96,11 +98,12 @@ export const onNotificationOpenedAppEvent = async (remoteMessage) => {
// return;
// }
try {
eventLogger.info("Processing background notification tap", {
eventLogger.debug("Processing background notification tap", {
messageId: remoteMessage?.messageId,
data: remoteMessage?.data,
notification: remoteMessage?.notification,
clickAction: remoteMessage?.notification?.android?.clickAction,
notificationType: remoteMessage?.data?.type || "unknown",
});
if (!remoteMessage?.notification) {
@ -273,5 +276,28 @@ export const onEvent = async ({ type, notification, pressAction }) => {
await actionRelativeInvitationReject({ data });
break;
}
case "open-settings": {
eventLogger.debug("Processing open-settings action", {
data,
actionId,
notificationId: notification?.id,
launchActivity: pressAction?.launchActivity,
});
await actionOpenSettings({ data });
break;
}
case "open-background-geolocation-settings": {
eventLogger.debug(
"Processing open-background-geolocation-settings action",
{
data,
actionId,
notificationId: notification?.id,
launchActivity: pressAction?.launchActivity,
},
);
await actionOpenBackgroundGeolocationSettings({ data });
break;
}
}
};

View file

@ -6,6 +6,7 @@ import { createNotificationChannel as createSuggestCloseChannel } from "./channe
import { createNotificationChannel as createSuggestKeepOpenChannel } from "./channels/notifSuggestKeepOpen";
import { createNotificationChannel as createRelativeAllowAskChannel } from "./channels/notifRelativeAllowAsk";
import { createNotificationChannel as createRelativeInvitationChannel } from "./channels/notifRelativeInvitation";
import { createNotificationChannel as createSystemChannel } from "./channels/notifSystem";
export default async function setActionCategories() {
// Create all notification channels
@ -17,6 +18,7 @@ export default async function setActionCategories() {
createSuggestKeepOpenChannel(),
createRelativeAllowAskChannel(),
createRelativeInvitationChannel(),
createSystemChannel(),
]);
} catch (error) {
const errorData = {
@ -113,5 +115,20 @@ export default async function setActionCategories() {
},
],
},
{
id: "system",
actions: [
{
id: "open-settings",
title: "Paramètres",
foreground: true,
},
{
id: "open-background-geolocation-settings",
title: "Paramètres",
foreground: true,
},
],
},
]);
}

View file

@ -0,0 +1,78 @@
import React, { useCallback } from "react";
import { View } from "react-native";
import { Title, Switch } from "react-native-paper";
import { createStyles } from "~/theme";
import { useParamsState, paramsActions } from "~/stores";
import Text from "~/components/Text";
import { setSentryEnabled } from "~/sentry";
function SentryOptOut() {
const styles = useStyles();
const { sentryEnabled } = useParamsState(["sentryEnabled"]);
const handleToggle = useCallback(async () => {
const newValue = !sentryEnabled;
await paramsActions.setSentryEnabled(newValue);
// Dynamically enable/disable Sentry
setSentryEnabled(newValue);
}, [sentryEnabled]);
return (
<View style={styles.container}>
<Title style={styles.title}>Rapport d'erreurs</Title>
<View style={styles.content}>
<View style={styles.switchContainer}>
<Text style={styles.label}>Envoyer les rapports d'erreurs</Text>
<Switch
value={sentryEnabled}
onValueChange={handleToggle}
style={styles.switch}
/>
</View>
<Text style={styles.description}>
Les rapports d'erreurs nous aident à améliorer l'application en nous
permettant de mieux identifier et corriger les problèmes techniques.
</Text>
</View>
</View>
);
}
const useStyles = createStyles(({ theme: { colors } }) => ({
container: {
width: "100%",
alignItems: "center",
},
title: {
fontSize: 20,
fontWeight: "bold",
marginVertical: 15,
},
content: {
width: "100%",
},
switchContainer: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 5,
paddingVertical: 5,
},
label: {
fontSize: 16,
flex: 1,
marginRight: 15,
},
switch: {
flexShrink: 0,
},
description: {
fontSize: 14,
color: colors.onSurfaceVariant,
textAlign: "left",
lineHeight: 20,
},
}));
export default SentryOptOut;

View file

@ -1,17 +1,40 @@
import React from "react";
import { View, ScrollView } from "react-native";
import React, { useCallback, useRef } from "react";
import { View, ScrollView, InteractionManager } from "react-native";
import { createStyles } from "~/theme";
import ParamsNotifications from "./Notifications";
import ParamsRadius from "./Radius";
import ParamsEmergencyCall from "./EmergencyCall";
import ThemeSwitcher from "./ThemeSwitcher";
import Permissions from "./Permissions";
import SentryOptOut from "./SentryOptOut";
import { useRoute, useFocusEffect } from "@react-navigation/native";
export default function ParamsView({ data }) {
const styles = useStyles();
const scrollRef = useRef(null);
const { params } = useRoute();
const didScroll = useRef(false);
const recordLayout = useCallback(
(key) =>
({
nativeEvent: {
layout: { y },
},
}) => {
if (didScroll.current || params?.anchor !== key) return;
InteractionManager.runAfterInteractions(() => {
scrollRef.current?.scrollTo({ y, animated: true });
didScroll.current = true;
});
},
[params?.anchor],
);
return (
<ScrollView style={styles.scrollView}>
<ScrollView ref={scrollRef} style={styles.scrollView}>
<View style={styles.container}>
<View style={styles.section}>
<ThemeSwitcher />
@ -26,6 +49,9 @@ export default function ParamsView({ data }) {
<ParamsRadius data={data} />
</View>
<View style={styles.section}>
<SentryOptOut />
</View>
<View onLayout={recordLayout("permissions")} style={styles.section}>
<Permissions />
</View>
</View>

View file

@ -3,6 +3,15 @@ import { Platform } from "react-native";
import env from "~/env";
import packageJson from "../../package.json";
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
import { STORAGE_KEYS } from "~/storage/storageKeys";
import { createLogger } from "~/lib/logger";
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
const sentryLogger = createLogger({
module: SYSTEM_SCOPES.APP,
feature: "sentry",
});
// Get the build number from native code
const getBuildNumber = () => {
@ -23,60 +32,139 @@ const getReleaseVersion = () => {
return `com.alertesecours@${version}+${buildNumber}`;
};
Sentry.init({
dsn: env.SENTRY_DSN,
tracesSampleRate: 0.1,
debug: __DEV__,
// Configure release to match ios-archive.sh format
release: getReleaseVersion(),
// Use BUILD_TIME from env to match the value used in sourcemap upload
dist: env.BUILD_TIME,
enableNative: true,
attachStacktrace: true,
environment: __DEV__ ? "development" : "production",
normalizeDepth: 10,
maxBreadcrumbs: 100,
// Enable debug ID tracking
_experiments: {
debugIds: true,
},
beforeSend(event) {
event.extra = {
...event.extra,
jsEngine: global.HermesInternal ? "hermes" : "jsc",
hermesEnabled: !!global.HermesInternal,
version: packageJson.version,
buildNumber: getBuildNumber(),
buildTime: env.BUILD_TIME,
};
// Check if Sentry is enabled by user preference
const checkSentryEnabled = async () => {
try {
// Wait for memory storage to be initialized
let retries = 0;
const maxRetries = 10;
if (event.exception) {
event.exception.values = event.exception.values?.map((value) => ({
...value,
mechanism: {
...value.mechanism,
handled: true,
synthetic: false,
type: "hermes",
},
}));
while (retries < maxRetries) {
try {
const stored = await memoryAsyncStorage.getItem(
STORAGE_KEYS.SENTRY_ENABLED,
);
if (stored !== null) {
return JSON.parse(stored);
}
break; // Storage is ready, no preference stored
} catch (error) {
if (
error.message?.includes("not initialized") &&
retries < maxRetries - 1
) {
// Wait a bit and retry if storage not initialized
await new Promise((resolve) => setTimeout(resolve, 100));
retries++;
continue;
}
sentryLogger.warn("Failed to check Sentry preference", {
error: error.message,
});
break;
}
}
} catch (error) {
sentryLogger.warn("Failed to check Sentry preference", {
error: error.message,
});
}
// Default to enabled if no preference stored or error occurred
return true;
};
return event;
},
beforeBreadcrumb(breadcrumb) {
if (breadcrumb.category === "console") {
// Initialize Sentry with user preference check
const initializeSentry = async () => {
const isEnabled = await checkSentryEnabled();
Sentry.init({
dsn: env.SENTRY_DSN,
enabled: isEnabled,
tracesSampleRate: 0.1,
debug: __DEV__,
// Configure release to match ios-archive.sh format
release: getReleaseVersion(),
// Use BUILD_TIME from env to match the value used in sourcemap upload
dist: env.BUILD_TIME,
enableNative: true,
attachStacktrace: true,
environment: __DEV__ ? "development" : "production",
normalizeDepth: 10,
maxBreadcrumbs: 100,
// Enable debug ID tracking
_experiments: {
debugIds: true,
},
beforeSend(event) {
event.extra = {
...event.extra,
jsEngine: global.HermesInternal ? "hermes" : "jsc",
hermesEnabled: !!global.HermesInternal,
version: packageJson.version,
buildNumber: getBuildNumber(),
buildTime: env.BUILD_TIME,
};
if (event.exception) {
event.exception.values = event.exception.values?.map((value) => ({
...value,
mechanism: {
...value.mechanism,
handled: true,
synthetic: false,
type: "hermes",
},
}));
}
return event;
},
beforeBreadcrumb(breadcrumb) {
if (breadcrumb.category === "console") {
return breadcrumb;
}
return breadcrumb;
}
return breadcrumb;
},
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
// Sentry.mobileReplayIntegration({
// maskAllText: false,
// maskAllImages: false,
// maskAllVectors: false,
// }),
],
},
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
// Sentry.mobileReplayIntegration({
// maskAllText: false,
// maskAllImages: false,
// maskAllVectors: false,
// }),
],
});
};
// Initialize Sentry asynchronously
initializeSentry().catch((error) => {
sentryLogger.warn("Failed to initialize Sentry", {
error: error.message,
});
});
// Export function to dynamically control Sentry
export const setSentryEnabled = (enabled) => {
try {
// Use the newer Sentry API
const client = Sentry.getClient();
if (client) {
const options = client.getOptions();
options.enabled = enabled;
if (!enabled) {
// Clear any pending events when disabling
Sentry.withScope((scope) => {
scope.clear();
});
}
sentryLogger.info("Sentry state toggled", { enabled });
} else {
sentryLogger.warn("Sentry client not available for toggling");
}
} catch (error) {
sentryLogger.warn("Failed to toggle Sentry state", {
error: error.message,
});
}
};

View file

@ -80,4 +80,5 @@ export const STORAGE_KEYS = {
LAST_KNOWN_LOCATION: registerAsyncStorageKey("@last_known_location"),
EULA_ACCEPTED_SIMPLE: registerAsyncStorageKey("eula_accepted"),
EMULATOR_MODE_ENABLED: registerAsyncStorageKey("emulator_mode_enabled"),
SENTRY_ENABLED: registerAsyncStorageKey("@sentry_enabled"),
};

View file

@ -1,4 +1,13 @@
import { createAtom } from "~/lib/atomic-zustand";
import memoryAsyncStorage from "~/storage/memoryAsyncStorage";
import { STORAGE_KEYS } from "~/storage/storageKeys";
import { createLogger } from "~/lib/logger";
import { SYSTEM_SCOPES } from "~/lib/logger/scopes";
const paramsLogger = createLogger({
module: SYSTEM_SCOPES.APP,
feature: "params",
});
export default createAtom(({ merge, reset }) => {
const setDevModeEnabled = (b) => {
@ -37,6 +46,45 @@ export default createAtom(({ merge, reset }) => {
});
};
const setSentryEnabled = async (sentryEnabled) => {
merge({
sentryEnabled,
});
// Persist to storage
try {
await memoryAsyncStorage.setItem(
STORAGE_KEYS.SENTRY_ENABLED,
JSON.stringify(sentryEnabled),
);
} catch (error) {
paramsLogger.warn("Failed to persist Sentry preference", {
error: error.message,
});
}
};
const initSentryEnabled = async () => {
try {
const stored = await memoryAsyncStorage.getItem(
STORAGE_KEYS.SENTRY_ENABLED,
);
if (stored !== null) {
const sentryEnabled = JSON.parse(stored);
merge({ sentryEnabled });
return sentryEnabled;
}
} catch (error) {
paramsLogger.warn("Failed to load Sentry preference", {
error: error.message,
});
}
};
const init = async () => {
await initSentryEnabled();
};
return {
default: {
// devModeEnabled: false,
@ -46,6 +94,7 @@ export default createAtom(({ merge, reset }) => {
mapColorScheme: "auto",
hasRegisteredRelatives: null,
alertListSortBy: "location",
sentryEnabled: true,
},
actions: {
reset,
@ -55,6 +104,8 @@ export default createAtom(({ merge, reset }) => {
setMapColorScheme,
setHasRegisteredRelatives,
setAlertListSortBy,
setSentryEnabled,
init,
},
};
});

View file

@ -24,7 +24,7 @@ const ThemeLight = {
error: "#fa5252",
onError: "#ffffff",
errorContainer: "##fa5252",
errorContainer: "#fa5252",
onErrorContainer: "#ffffff",
warn: "#f59f00",