diff --git a/.gitignore b/.gitignore
index c7d32c2..81aef0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,8 @@ AGENTS.md
# Zephyr
# ----------------------------
firmware/app/build/
+firmware/app/build_*/
+firmware/diagnostic
external/
west-manifest/
.venv/
@@ -66,4 +68,5 @@ _autosave-*
# ----------------------------
# Divers
# ----------------------------
-tools/
\ No newline at end of file
+tools/
+*.xcf
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8229a44..e342c9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,28 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
### Added
+- One-second white RGB indication when firmware initialization completes and
+ the keypad loop is ready.
+- Branded Android launcher icon and native splash screen using the project
+ assets.
+- French and English mobile UI with automatic phone-language selection and a
+ manual override.
+- Dark mobile dashboard, shortcut bar, styled modals, code limits, opening
+ history, phone-side NFC UID scanning, and administrator/guest role controls.
+- Secure saved-box records, automatic BLE reconnection, invitation QR
+ generation/scanning, and guest revocation.
+- AES-256-GCM password-protected mobile backup and restore using a
+ PBKDF2-SHA256 derived key.
+- Persistent box names and administrator factory reset with BLE bond removal.
+- Persistent seven-day opening history for permanent codes, temporary codes,
+ named NFC tags, and named mobile identities.
+- Fallback time `2026-06-01T00:00:00Z` and reboot continuity from the newest
+ persisted opening event.
+- Eight permanent and twenty temporary access-code slots.
+- BLE LE Secure Connections, AES-CCM encrypted GATT access, persistent bonding,
+ and 128-bit administrator/guest application identities.
+- First-phone administrator provisioning without exposing the stored key.
+- App-triggered NFC enrollment with persistent tag names.
- Initial project structure.
- Zephyr RTOS firmware baseline.
- Standardized project architecture.
@@ -27,6 +49,13 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
- Persistent six-digit access code storage.
- Persistent NFC tag UID storage with default development UID `60:4F:E2:B5`.
- NFC scan-mode application flow triggered by keypad activity.
+- Flutter mobile application shell in `mobile-app/app`.
+- Mobile application use of the root `images/` logo and `background.jpg` assets.
+- Mobile BLE scan/connect flow targeting the OpenParcelBox XIAO service.
+- Mobile commands for XIAO clock synchronization, direct lock opening, access-code management, and NFC tag management.
+- Firmware BLE administration service with JSON command writes and stored-state readback.
+- Volatile phone-synchronized firmware clock for lock-opening log timestamps.
+- One-time access codes that are removed after first successful keypad use.
- Lock control pulse on XIAO pin `D9`.
- Lock state feedback detection using KR-S79 `COM/NC` on XIAO `D7` / `D8`.
- Long buzzer success beep for accepted unlock codes.
@@ -48,10 +77,62 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
- Updated keypad unlock user feedback with off-at-rest LEDs, green-open indication, red invalid-code feedback, and `B` entry cancellation.
- Updated runtime LED feedback with blue NFC scan-mode indication.
- Updated open-lock reminder beep interval to 2 seconds.
+- Updated the mobile application to reload access codes and NFC tags from the firmware BLE state characteristic.
- Corrected NFC hardware documentation: the nRF52840 integrated NFCT peripheral is tag-side NFC-A hardware, while passive badge UID reading requires a dedicated NFC reader circuit.
### Fixed
+- Removed oversized Settings callback allocations that exceeded the 1024-byte
+ main stack once opening-history or application-identity records existed.
+- Moved Settings/NVS loading to a dedicated 4096-byte services thread and
+ increased the main, system-workqueue, and Bluetooth RX stack margins.
+- Kept the keypad fallback code and default clock available from RAM before any
+ flash or Bluetooth operation can run.
+- Added bounded retries when Bluetooth advertising temporarily returns
+ `-EAGAIN`, and no longer abort advertising solely because stored Bluetooth
+ settings report a load error.
+- Prevented invalid persisted box settings from overwriting the active default
+ configuration before validation.
+- Kept opening-history timestamps monotonic after an older phone clock sync so
+ one descending event cannot invalidate the complete table on reboot.
+- Provided Flutter's Material localization delegates so input fields and
+ registration dialogs work when the phone language is French.
+- Prevented incompatible persistent development-table layouts from blocking
+ firmware startup before keypad initialization; affected tables now recover
+ through their safe defaults.
+- Made Bluetooth initialization asynchronous so keypad operation and USB
+ availability no longer wait for BLE controller startup.
+- Removed the silent early exit when RGB LED initialization fails.
+- Kept keypad access available with in-memory default credentials when
+ persistent settings fail, and added background keypad initialization retries.
+- Fixed secure BLE startup ordering: the persisted `bt/*` settings and identity
+ are now loaded after `bt_enable()` and before advertising, preventing
+ `BLE advertising failed: -11`.
+- Moved secure BLE initialization to a dedicated thread started after the local
+ keypad-ready indication, so Bluetooth or bond restoration can never block
+ keypad access, lock control, or the main application loop.
+- Removed the interactive Zephyr/I2C shell and its competing serial backend;
+ USB CDC is now owned exclusively by the firmware console, matching the
+ validated standalone diagnostic configuration.
+- Restricted guest BLE sessions to authentication, clock synchronization, and
+ lock opening, and replaced oversized full-state notifications with a compact
+ change marker.
+- Added strict validation of persisted identity and opening-history records
+ before they can be used or serialized.
+- Removed automatic NVS writes and migrations from the boot path. Default
+ tables are now available immediately in RAM and are persisted only after a
+ real credential, identity, tag, box-name, or history change.
+- Replaced the coupled application loop with a resilient local-first entry
+ point: keypad input, code validation, lock control, buzzer, LEDs, and door
+ feedback run independently from BLE and NFC processing.
+- Temporarily removed NFC polling from the production main loop while retaining
+ NFC storage and BLE management initialization; it will be restored after the
+ validated keypad/BLE baseline is confirmed on hardware.
+
+- Fixed Flutter inherited-widget assertions when closing input modals by keeping
+ text controllers alive until their dialog widgets are actually unmounted.
+- Changed encrypted-backup restoration to select the backup file before asking
+ for its password.
- Matrix keypad scanning.
- GPIO expander abstraction.
- Firmware build configuration.
diff --git a/README.md b/README.md
index 1c7c97d..16c19a2 100644
--- a/README.md
+++ b/README.md
@@ -32,12 +32,25 @@ Completed or validated:
- Persistent six-digit access codes
- Keypad unlock flow with default development code `784512`
- Persistent NFC tag UID table and scan-mode firmware API
+- Flutter mobile application with branded Android icon/splash, bilingual dark
+ interface, secure registration, encrypted backups, and role-aware modals
+- Mobile BLE automatic reconnection and command UI for clock sync, lock
+ opening, history, access codes, NFC tags, and guest invitations
+- Firmware BLE service for those mobile commands plus stored-state readback
+- One-time access codes with automatic removal after first successful keypad use
+- Phone-synchronized runtime timestamps for lock-opening logs
+- Persistent seven-day opening history with power-loss time continuity
+- Eight permanent and twenty temporary access-code slots
+- BLE LE Secure Connections, encrypted GATT access, and 128-bit phone identities
+- First-phone administrator provisioning and named guest identities
+- App-triggered NFC enrollment with stored tag names
- Lock command pulse on XIAO `D9`
- KR-S79 `COM/NC` lock state feedback on XIAO `D7` / `D8`
- Green-open, off-closed, red-invalid LED feedback
- Short key beeps, long valid-code beep, invalid-code beep sequence, and 2s open reminder beep
- Door opened / closed serial debug messages
- Firmware debug output over serial console
+- One-second white RGB confirmation when the local keypad path is ready
In progress:
@@ -45,16 +58,16 @@ In progress:
- Production keypad mapping
- NFC credential reader hardware selection
- Hardware validation
+- BLE protocol documentation and hardening
Planned:
-- Administrator access management
+- Firmware update from the mobile application
- NFC badge unlock with a dedicated reader circuit
- Battery management
- Zigbee
- OTA updates
- Home Assistant integration
-- Mobile configuration application
- Custom PCB
## Repository Structure
@@ -69,7 +82,8 @@ OpenParcelBox/
| +-- app/ Main firmware application
+-- hardware/ Hardware documentation and reverse engineering
+-- homeassistant/ Home Assistant integration notes
-+-- mobile-app/ Mobile application notes
++-- mobile-app/ Flutter mobile application and notes
++-- images/ Project logo and application background assets
+-- CHANGELOG.md
+-- LICENSE
+-- README.md
@@ -93,6 +107,21 @@ Current firmware modules:
- Lock state feedback
- Persistent access codes
- Persistent NFC tag UID storage
+- BLE administration service
+
+## Mobile App
+
+The mobile application is developed with Flutter in:
+
+```text
+mobile-app/app/
+```
+
+The application provides local-first administrator and guest surfaces for
+secure Bluetooth registration, automatic reconnection, XIAO date/time
+synchronization, access-code management, NFC enrollment, guest invitation QR
+codes, opening history, encrypted backup/restore, direct opening, and
+authoritative firmware state readback.
Firmware documentation is available in:
diff --git a/ROADMAP.md b/ROADMAP.md
index 4aac730..a4b63a8 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -46,10 +46,11 @@ The firmware is based on Zephyr RTOS and follows a modular architecture to simpl
| NFC credential storage and scan API | Done |
| Dedicated NFC reader driver | Pending |
| Power management | Pending |
-| Bluetooth configuration | Pending |
+| Bluetooth configuration | In progress |
| Zigbee integration | Pending |
| Home Assistant integration | Pending |
| PCB V1 | Pending |
+| Flutter mobile application | In progress |
---
@@ -192,7 +193,20 @@ Features:
- Device information.
- Firmware information.
-Status: planned.
+Implemented so far:
+
+- Flutter-side BLE scan and connection flow.
+- Draft JSON command transport for clock sync, lock opening, access-code management, and NFC tag management.
+- Firmware-side BLE command characteristic and state readback characteristic.
+- Phone-synchronized volatile firmware clock for timestamped lock-opening logs.
+- BLE LE Secure Connections, encrypted GATT permissions, and bonded peers.
+- First-phone administrator plus named guest application identities.
+- App-triggered named NFC enrollment.
+- Persistent seven-day opening history and reboot time continuity.
+- Persistent box identifier, guest invitation/revocation commands, and
+ administrator factory reset with BLE bond removal.
+
+Status: in progress.
---
@@ -240,7 +254,27 @@ Features:
- Device status.
- Firmware update.
-Status: planned.
+Implemented so far:
+
+- Application shell using the project logo and background image.
+- Bluetooth scan/connect UI targeting the XIAO BLE service.
+- Phone-to-XIAO date/time sync command.
+- Direct lock-open command.
+- Random six-digit numeric code generation.
+- Permanent code management screen.
+- One-time temporary code management screen.
+- NFC tag add/remove screen.
+- Stored code and NFC tag readback from the firmware BLE state characteristic.
+- Branded Android launcher icon and native splash screen.
+- Automatic French/English selection with a manual override.
+- Dark modal dashboard with separate administrator and guest capabilities.
+- Secure box registration, invitation QR scanning/generation, and automatic
+ BLE reconnection.
+- AES-256-GCM password-protected application backup and restore.
+- Opening history, code limits, phone-side NFC scans, and destructive-action
+ warnings.
+
+Status: in progress.
---
diff --git a/TODO.md b/TODO.md
index 7701118..a28f04f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -23,7 +23,7 @@ Tasks are grouped by development phase and updated throughout the project.
- [x] Create firmware documentation.
- [x] Create hardware documentation baseline.
- [ ] Create development documentation.
-- [ ] Create protocol documentation.
+- [x] Create BLE protocol documentation.
### Firmware Baseline
@@ -146,7 +146,7 @@ Tasks are grouped by development phase and updated throughout the project.
### Configuration
- [x] Persistent storage.
-- [ ] Factory reset.
+- [x] Factory reset.
- [ ] Version information.
---
@@ -181,10 +181,26 @@ Tasks are grouped by development phase and updated throughout the project.
## Later Phases
-- [ ] Bluetooth communication.
-- [ ] Flutter mobile application.
-- [ ] User management.
-- [ ] Local event history.
+- [x] Firmware BLE command service.
+- [x] Initial Flutter mobile application shell.
+- [x] Mobile Bluetooth scan and connect UI.
+- [x] Mobile clock-sync command.
+- [x] Mobile random permanent and one-time code management UI.
+- [x] Mobile NFC tag add/remove UI.
+- [x] Mobile direct lock-open command UI.
+- [x] Mobile stored-state readback from firmware.
+- [x] Administrator and named guest identity storage.
+- [x] Local seven-day event history.
+- [x] Secure BLE pairing and encrypted application authorization.
+- [x] App-triggered named NFC enrollment flow.
+- [x] Branded Android launcher icon and native splash screen.
+- [x] French/English application localization and language override.
+- [x] Secure saved-box registration and automatic BLE reconnection.
+- [x] Administrator/guest modal dashboard and role restrictions.
+- [x] Guest invitation QR generation, scanning, and revocation.
+- [x] Password-protected AES-256-GCM application backup and restore.
+- [x] Mobile opening-history, NFC, and factory-reset administration.
+- [ ] Validate pairing recovery and guest invitation on Android and iOS hardware.
- [ ] Zigbee connectivity.
- [ ] Home Assistant integration.
- [ ] Temporary access.
diff --git a/images/background.jpg b/images/background.jpg
new file mode 100644
index 0000000..8812576
Binary files /dev/null and b/images/background.jpg differ
diff --git a/images/openparcelbox-logo.ico b/images/openparcelbox-logo.ico
new file mode 100644
index 0000000..6ba5a75
Binary files /dev/null and b/images/openparcelbox-logo.ico differ
diff --git a/mobile-app/app/.gitignore b/mobile-app/app/.gitignore
new file mode 100644
index 0000000..3820a95
--- /dev/null
+++ b/mobile-app/app/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+/coverage/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/mobile-app/app/.metadata b/mobile-app/app/.metadata
new file mode 100644
index 0000000..235bfb1
--- /dev/null
+++ b/mobile-app/app/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "ee80f08bbf97172ec030b8751ceab557177a34a6"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
+ base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
+ - platform: android
+ create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
+ base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/mobile-app/app/README.md b/mobile-app/app/README.md
new file mode 100644
index 0000000..716fcb3
--- /dev/null
+++ b/mobile-app/app/README.md
@@ -0,0 +1,104 @@
+# OpenParcelBox Mobile App
+
+Flutter application for local OpenParcelBox setup and administration.
+
+## User Interface
+
+- Dark solid background `#333333`, button surfaces `#292929`, modal surfaces
+ `#303030`, accent `#c19d60`, and text `#c6c6c6`.
+- Project logo used for the Android launcher icon and centered native splash.
+- Native splash uses the project `background.jpg`.
+- Automatic French or English selection from the phone, with a manual override.
+- Empty state restricted to box registration and encrypted-backup restoration.
+- Shortcut bar for opening and history, plus modal cards for codes, NFC tags,
+ guests, and settings.
+- Guest mode hides NFC, guest administration, factory reset, and future firmware
+ update controls.
+
+## Registration and Secure Storage
+
+The first phone connecting to an unprovisioned box:
+
+1. chooses the box identifier;
+2. generates a random 128-bit administrator identity;
+3. sends both values over the encrypted BLE link;
+4. stores the remote BLE identifier, box name, role, and identity in the phone
+ secure keystore.
+
+An administrator creates named guest identities and displays an invitation QR
+code. A guest scans that QR code to register the same box and its unique guest
+identity. Saved boxes reconnect automatically when the application starts.
+
+Application backups contain the saved connection records. They are protected
+with AES-256-GCM and a PBKDF2-SHA256 key derived from the user password before a
+file is written. During restoration, the application first asks for the backup
+file and only then requests the password needed to decrypt it.
+
+## Features
+
+- Direct lock opening and seven-day opening history.
+- Eight permanent and twenty one-time access codes.
+- NFC tag listing, renaming, deletion, manual UID entry, phone NFC scanning,
+ and enrollment through the box reader.
+- Named guest creation, invitation QR display, and revocation.
+- Firmware clock synchronization after connection.
+- Language override, encrypted backup/restore, factory reset, and local removal
+ of a saved box.
+- Reserved disabled entry for a later firmware-update flow.
+
+## BLE Security and Protocol
+
+The firmware requires BLE LE Secure Connections and encrypted GATT access.
+Bluetooth uses AES-CCM link encryption. Every application command is also
+authorized with the random administrator or guest identity stored in the phone
+keystore.
+
+Service UUID:
+
+```text
+f2a00000-8e7a-4f8d-9b1d-7d8e4b7a0001
+```
+
+Command characteristic:
+
+```text
+f2a00001-8e7a-4f8d-9b1d-7d8e4b7a0001
+```
+
+State characteristic:
+
+```text
+f2a00002-8e7a-4f8d-9b1d-7d8e4b7a0001
+```
+
+See `docs/firmware/bluetooth.md` from the repository root for the command and
+state formats.
+
+## Dependencies
+
+The app uses:
+
+- `flutter_blue_plus` for BLE;
+- `flutter_secure_storage` for connection identities;
+- `mobile_scanner` and `qr_flutter` for guest invitations;
+- `nfc_manager` for optional phone-side NFC UID scans;
+- `file_picker` and `cryptography` for encrypted backups.
+
+`flutter_blue_plus` 2.3.10 requires a license mode when connecting. The app uses
+`License.nonprofit` for this open-source project.
+
+## Development
+
+```powershell
+flutter pub get
+flutter analyze
+flutter test
+flutter build apk --debug
+```
+
+Native assets can be regenerated with:
+
+```powershell
+dart run flutter_launcher_icons
+dart run flutter_native_splash:create
+```
diff --git a/mobile-app/app/analysis_options.yaml b/mobile-app/app/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/mobile-app/app/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/mobile-app/app/android/.gitignore b/mobile-app/app/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/mobile-app/app/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/mobile-app/app/android/app/build.gradle.kts b/mobile-app/app/android/app/build.gradle.kts
new file mode 100644
index 0000000..cd13db6
--- /dev/null
+++ b/mobile-app/app/android/app/build.gradle.kts
@@ -0,0 +1,46 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "fr.zaynet.openparcelbox.app"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "fr.zaynet.openparcelbox.app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/mobile-app/app/android/app/src/debug/AndroidManifest.xml b/mobile-app/app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/mobile-app/app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/AndroidManifest.xml b/mobile-app/app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..580b1c8
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/kotlin/fr/zaynet/openparcelbox/app/MainActivity.kt b/mobile-app/app/android/app/src/main/kotlin/fr/zaynet/openparcelbox/app/MainActivity.kt
new file mode 100644
index 0000000..5c70a8e
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/kotlin/fr/zaynet/openparcelbox/app/MainActivity.kt
@@ -0,0 +1,5 @@
+package fr.zaynet.openparcelbox.app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/mobile-app/app/android/app/src/main/res/drawable-hdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-hdpi/android12splash.png
new file mode 100644
index 0000000..1623c79
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-hdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/mobile-app/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..5be96cf
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-hdpi/splash.png b/mobile-app/app/android/app/src/main/res/drawable-hdpi/splash.png
new file mode 100644
index 0000000..1623c79
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-hdpi/splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-mdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-mdpi/android12splash.png
new file mode 100644
index 0000000..7967f6a
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-mdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/mobile-app/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..eff5201
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-mdpi/splash.png b/mobile-app/app/android/app/src/main/res/drawable-mdpi/splash.png
new file mode 100644
index 0000000..7967f6a
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-mdpi/splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-night-hdpi/android12splash.png
new file mode 100644
index 0000000..1623c79
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-night-mdpi/android12splash.png
new file mode 100644
index 0000000..7967f6a
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
new file mode 100644
index 0000000..90f06a5
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
new file mode 100644
index 0000000..f184c20
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
new file mode 100644
index 0000000..2b2e885
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-v21/background.png b/mobile-app/app/android/app/src/main/res/drawable-v21/background.png
new file mode 100644
index 0000000..49a1724
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-v21/background.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile-app/app/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..3cc4948
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/android12splash.png
new file mode 100644
index 0000000..90f06a5
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..46eb1ca
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xhdpi/splash.png b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/splash.png
new file mode 100644
index 0000000..90f06a5
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xhdpi/splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/android12splash.png
new file mode 100644
index 0000000..f184c20
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..a161790
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/splash.png b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/splash.png
new file mode 100644
index 0000000..f184c20
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxhdpi/splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/android12splash.png
new file mode 100644
index 0000000..2b2e885
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ed67fb9
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/splash.png b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/splash.png
new file mode 100644
index 0000000..2b2e885
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable/background.png b/mobile-app/app/android/app/src/main/res/drawable/background.png
new file mode 100644
index 0000000..49a1724
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/drawable/background.png differ
diff --git a/mobile-app/app/android/app/src/main/res/drawable/launch_background.xml b/mobile-app/app/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..3cc4948
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile-app/app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c79c58a
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile-app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f95c5fc
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile-app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..e37a115
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile-app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..18b3349
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile-app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..5b4a62f
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/mobile-app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile-app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a099d6d
Binary files /dev/null and b/mobile-app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/mobile-app/app/android/app/src/main/res/values-night-v31/styles.xml b/mobile-app/app/android/app/src/main/res/values-night-v31/styles.xml
new file mode 100644
index 0000000..cbfc537
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/values-night-v31/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/values-night/styles.xml b/mobile-app/app/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..dbc9ea9
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/values-v31/styles.xml b/mobile-app/app/android/app/src/main/res/values-v31/styles.xml
new file mode 100644
index 0000000..7977c19
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/values-v31/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/main/res/values/colors.xml b/mobile-app/app/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f2269c5
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #333333
+
\ No newline at end of file
diff --git a/mobile-app/app/android/app/src/main/res/values/styles.xml b/mobile-app/app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..0d1fa8f
--- /dev/null
+++ b/mobile-app/app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/app/src/profile/AndroidManifest.xml b/mobile-app/app/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/mobile-app/app/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/mobile-app/app/android/build.gradle.kts b/mobile-app/app/android/build.gradle.kts
new file mode 100644
index 0000000..10922c9
--- /dev/null
+++ b/mobile-app/app/android/build.gradle.kts
@@ -0,0 +1,36 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+
+ // mobile_scanner 7.2.1 detects AGP 9, while Flutter currently opts this
+ // project out of AGP's built-in Kotlin support. Apply the legacy Kotlin
+ // plugin explicitly so the package's Kotlin DSL remains available.
+ if (name in setOf("file_picker", "mobile_scanner", "nfc_manager")) {
+ pluginManager.apply("org.jetbrains.kotlin.android")
+ tasks.withType().configureEach {
+ compilerOptions.jvmTarget.set(
+ org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ )
+ }
+ }
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/mobile-app/app/android/build/reports/problems/problems-report.html b/mobile-app/app/android/build/reports/problems/problems-report.html
new file mode 100644
index 0000000..0c57573
--- /dev/null
+++ b/mobile-app/app/android/build/reports/problems/problems-report.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/mobile-app/app/android/gradle.properties b/mobile-app/app/android/gradle.properties
new file mode 100644
index 0000000..b7ecb1f
--- /dev/null
+++ b/mobile-app/app/android/gradle.properties
@@ -0,0 +1,7 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+# This builtInKotlin flag was added automatically by Flutter migrator
+android.builtInKotlin=false
+# This newDsl flag was added automatically by Flutter migrator
+android.newDsl=false
+kotlin.incremental=false
diff --git a/mobile-app/app/android/gradle/wrapper/gradle-wrapper.properties b/mobile-app/app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2d428bf
--- /dev/null
+++ b/mobile-app/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
diff --git a/mobile-app/app/android/settings.gradle.kts b/mobile-app/app/android/settings.gradle.kts
new file mode 100644
index 0000000..c21f0c5
--- /dev/null
+++ b/mobile-app/app/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "9.0.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.3.20" apply false
+}
+
+include(":app")
diff --git a/mobile-app/app/images/background.jpg b/mobile-app/app/images/background.jpg
new file mode 100644
index 0000000..8812576
Binary files /dev/null and b/mobile-app/app/images/background.jpg differ
diff --git a/mobile-app/app/images/openparcelbox-logo-256.png b/mobile-app/app/images/openparcelbox-logo-256.png
new file mode 100644
index 0000000..e06db11
Binary files /dev/null and b/mobile-app/app/images/openparcelbox-logo-256.png differ
diff --git a/mobile-app/app/lib/app_strings.dart b/mobile-app/app/lib/app_strings.dart
new file mode 100644
index 0000000..75e9204
--- /dev/null
+++ b/mobile-app/app/lib/app_strings.dart
@@ -0,0 +1,157 @@
+import 'package:flutter/widgets.dart';
+
+class AppStrings {
+ AppStrings(this.locale);
+
+ final Locale locale;
+
+ bool get isFrench => locale.languageCode == 'fr';
+
+ String text(String key) => (_values[key] ?? const {})[
+ isFrench ? 'fr' : 'en'] ??
+ key;
+
+ static const Map> _values =
+ >{
+ 'add_box': {
+ 'fr': 'Ajouter une nouvelle OpenParcelBox',
+ 'en': 'Add a new OpenParcelBox',
+ },
+ 'restore_backup': {
+ 'fr': 'Restaurer une sauvegarde',
+ 'en': 'Restore a backup',
+ },
+ 'open': {'fr': 'Ouvrir', 'en': 'Open'},
+ 'history': {'fr': 'Historique', 'en': 'History'},
+ 'permanent_codes': {
+ 'fr': 'Codes permanents',
+ 'en': 'Permanent codes',
+ },
+ 'temporary_codes': {
+ 'fr': 'Codes temporaires',
+ 'en': 'Temporary codes',
+ },
+ 'nfc_tags': {'fr': 'Tags NFC', 'en': 'NFC tags'},
+ 'guests': {'fr': 'Invités', 'en': 'Guests'},
+ 'settings': {'fr': 'Paramètres', 'en': 'Settings'},
+ 'close': {'fr': 'Fermer', 'en': 'Close'},
+ 'add': {'fr': 'Ajouter', 'en': 'Add'},
+ 'delete': {'fr': 'Supprimer', 'en': 'Delete'},
+ 'edit': {'fr': 'Modifier', 'en': 'Edit'},
+ 'cancel': {'fr': 'Annuler', 'en': 'Cancel'},
+ 'save': {'fr': 'Enregistrer', 'en': 'Save'},
+ 'scan': {'fr': 'Scanner', 'en': 'Scan'},
+ 'scan_qr': {
+ 'fr': 'Scanner un QR code invité',
+ 'en': 'Scan a guest QR code',
+ },
+ 'box_name': {
+ 'fr': 'Identifiant de la box',
+ 'en': 'Box identifier',
+ },
+ 'nearby_boxes': {
+ 'fr': 'OpenParcelBox détectées',
+ 'en': 'Detected OpenParcelBox devices',
+ },
+ 'search': {'fr': 'Rechercher', 'en': 'Search'},
+ 'no_results': {
+ 'fr': 'Aucune OpenParcelBox détectée.',
+ 'en': 'No OpenParcelBox detected.',
+ },
+ 'connected': {'fr': 'Connectée', 'en': 'Connected'},
+ 'disconnected': {'fr': 'Déconnectée', 'en': 'Disconnected'},
+ 'connecting': {'fr': 'Connexion…', 'en': 'Connecting…'},
+ 'no_code': {'fr': 'Aucun code.', 'en': 'No code.'},
+ 'new_code': {'fr': 'Générer un code', 'en': 'Generate a code'},
+ 'code_limit': {
+ 'fr': 'La limite de codes est atteinte.',
+ 'en': 'The code limit has been reached.',
+ },
+ 'no_history': {
+ 'fr': 'Aucune ouverture enregistrée.',
+ 'en': 'No opening has been recorded.',
+ },
+ 'opened_by_permanent': {
+ 'fr': 'Code permanent',
+ 'en': 'Permanent code',
+ },
+ 'opened_by_temporary': {
+ 'fr': 'Code temporaire',
+ 'en': 'Temporary code',
+ },
+ 'opened_by_nfc': {'fr': 'Tag NFC', 'en': 'NFC tag'},
+ 'opened_by_app': {'fr': 'Application', 'en': 'Application'},
+ 'tag_name': {'fr': 'Nom du tag', 'en': 'Tag name'},
+ 'tag_uid': {'fr': 'UID du tag', 'en': 'Tag UID'},
+ 'scan_phone_nfc': {
+ 'fr': 'Scanner avec le téléphone',
+ 'en': 'Scan with phone',
+ },
+ 'pair_on_box': {
+ 'fr': 'Appairer sur la box',
+ 'en': 'Pair on the box',
+ },
+ 'manual_uid': {'fr': 'Ajouter un UID', 'en': 'Add a UID'},
+ 'nfc_unavailable': {
+ 'fr': 'Le NFC est indisponible sur ce téléphone.',
+ 'en': 'NFC is unavailable on this phone.',
+ },
+ 'hold_tag': {
+ 'fr': 'Approchez un tag NFC du téléphone…',
+ 'en': 'Hold an NFC tag near the phone…',
+ },
+ 'guest_name': {'fr': "Nom de l'invité", 'en': 'Guest name'},
+ 'show_qr': {'fr': 'Voir le QR code', 'en': 'Show QR code'},
+ 'no_guest': {'fr': 'Aucun invité.', 'en': 'No guest.'},
+ 'language': {'fr': 'Langue', 'en': 'Language'},
+ 'automatic': {'fr': 'Automatique', 'en': 'Automatic'},
+ 'french': {'fr': 'Français', 'en': 'French'},
+ 'english': {'fr': 'Anglais', 'en': 'English'},
+ 'backup': {
+ 'fr': "Sauvegarder les paramètres de l'app",
+ 'en': 'Back up app settings',
+ },
+ 'backup_location': {
+ 'fr': "Choisir l'emplacement de sauvegarde",
+ 'en': 'Choose backup location',
+ },
+ 'restore': {
+ 'fr': 'Restaurer une sauvegarde',
+ 'en': 'Restore a backup',
+ },
+ 'password': {'fr': 'Mot de passe', 'en': 'Password'},
+ 'firmware_update': {
+ 'fr': 'Mise à jour du firmware (à venir)',
+ 'en': 'Firmware update (coming later)',
+ },
+ 'factory_reset': {
+ 'fr': "Réinitialiser la box",
+ 'en': 'Factory-reset the box',
+ },
+ 'forget_box': {'fr': 'Supprimer la box', 'en': 'Forget the box'},
+ 'danger': {'fr': 'Danger', 'en': 'Danger'},
+ 'factory_warning': {
+ 'fr':
+ "Cette action supprime tous les paramètres de la box et autorise l'appairage d'un nouvel administrateur.",
+ 'en':
+ 'This deletes every setting on the box and allows a new administrator to pair.',
+ },
+ 'forget_warning': {
+ 'fr':
+ "Seules les informations de l'application seront supprimées. Les paramètres de la box restent inchangés et un administrateur peut perdre définitivement l'accès.",
+ 'en':
+ 'Only app information will be deleted. Box settings remain unchanged and an administrator may permanently lose access.',
+ },
+ 'confirm': {'fr': 'Confirmer', 'en': 'Confirm'},
+ 'administrator': {'fr': 'Administrateur', 'en': 'Administrator'},
+ 'guest': {'fr': 'Invité', 'en': 'Guest'},
+ 'backup_done': {
+ 'fr': 'Sauvegarde chiffrée créée.',
+ 'en': 'Encrypted backup created.',
+ },
+ 'restore_done': {
+ 'fr': 'Sauvegarde restaurée.',
+ 'en': 'Backup restored.',
+ },
+ };
+}
diff --git a/mobile-app/app/lib/backup_service.dart b/mobile-app/app/lib/backup_service.dart
new file mode 100644
index 0000000..43657a4
--- /dev/null
+++ b/mobile-app/app/lib/backup_service.dart
@@ -0,0 +1,108 @@
+import 'dart:convert';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:cryptography/cryptography.dart';
+import 'package:file_picker/file_picker.dart';
+
+import 'models.dart';
+
+class BackupService {
+ static const _format = 'openparcelbox-backup-v1';
+ final Random _random = Random.secure();
+
+ Future exportBoxes(
+ List boxes,
+ String password, {
+ String? initialDirectory,
+ }) async {
+ if (password.isEmpty) {
+ throw ArgumentError('A backup password is required.');
+ }
+ final salt = _randomBytes(16);
+ final nonce = _randomBytes(12);
+ final key = await _deriveKey(password, salt);
+ final clearText = utf8.encode(
+ jsonEncode({
+ 'format': _format,
+ 'boxes': boxes.map((box) => box.toJson()).toList(),
+ }),
+ );
+ final secretBox = await AesGcm.with256bits().encrypt(
+ clearText,
+ secretKey: key,
+ nonce: nonce,
+ );
+ final envelope = utf8.encode(
+ jsonEncode({
+ 'format': _format,
+ 'salt': base64Encode(salt),
+ 'nonce': base64Encode(secretBox.nonce),
+ 'ciphertext': base64Encode(secretBox.cipherText),
+ 'mac': base64Encode(secretBox.mac.bytes),
+ }),
+ );
+ final path = await FilePicker.saveFile(
+ dialogTitle: 'OpenParcelBox backup',
+ fileName: 'openparcelbox-backup.opb',
+ initialDirectory: initialDirectory,
+ type: FileType.custom,
+ allowedExtensions: const ['opb'],
+ bytes: Uint8List.fromList(envelope),
+ );
+ return path != null;
+ }
+
+ Future pickBackupFile() async {
+ final result = await FilePicker.pickFiles(
+ type: FileType.custom,
+ allowedExtensions: const ['opb'],
+ withData: true,
+ );
+ return result?.files.single.bytes;
+ }
+
+ Future> importBoxes(
+ Uint8List bytes,
+ String password,
+ ) async {
+ if (password.isEmpty) {
+ throw ArgumentError('A backup password is required.');
+ }
+ final envelope = jsonDecode(utf8.decode(bytes));
+ if (envelope is! Map || envelope['format'] != _format) {
+ throw const FormatException('Unsupported OpenParcelBox backup.');
+ }
+ final salt = base64Decode(envelope['salt']! as String);
+ final nonce = base64Decode(envelope['nonce']! as String);
+ final cipherText = base64Decode(envelope['ciphertext']! as String);
+ final mac = Mac(base64Decode(envelope['mac']! as String));
+ final clearText = await AesGcm.with256bits().decrypt(
+ SecretBox(cipherText, nonce: nonce, mac: mac),
+ secretKey: await _deriveKey(password, salt),
+ );
+ final decoded = jsonDecode(utf8.decode(clearText));
+ if (decoded is! Map || decoded['format'] != _format) {
+ throw const FormatException('Invalid OpenParcelBox backup.');
+ }
+ final boxes = decoded['boxes'];
+ if (boxes is! List