diff --git a/README.md b/README.md
index 100e8a0..6dd1af9 100644
--- a/README.md
+++ b/README.md
@@ -4,16 +4,63 @@ A Flutter app for mapping and tagging ALPR-style cameras (and other surveillance
---
-## Code Organization
+## Code Organization (2025 Refactor)
-This project uses a modular file/folder structure for maintainability:
-- **Settings sections** each live in their own file under `lib/screens/settings_screen_sections/`.
-- **Offline map area models, tile logic, and network/camera helpers** are grouped under `lib/services/offline_areas/`.
-- The main Settings and OfflineAreaService files are now slim front-ends that delegate logic to these modules.
+- **Data providers:** All map tile and camera data fetching now routes through `lib/services/map_data_provider.dart`, which supports both OSM/Overpass and fully offline/local sources, with pluggable submodules:
+ - Remote tile fetch: `map_data_submodules/tiles_from_osm.dart`
+ - Remote cameras: `map_data_submodules/cameras_from_overpass.dart`
+ - *Coming soon:* Local tile/camera modules for offline/area-aware access
+- **Settings UI:** Each settings section lives in its own widget under `lib/screens/settings_screen_sections/`, using clean, modular ListTile-based layouts.
+- **Offline areas:** Management, persistence, and download logic remain in `OfflineAreaService`, but all fetch/caching is routed through the new provider.
+- **Legacy OSM/Overpass tile and camera fetch code has been removed from old modules.**
---
-## User Experience & Features
+## Key Features
+
+### Map Data & Provider Architecture
+- **All map tile and camera fetches** go through MapDataProvider, which selects local or remote sources as needed, automatically obeying the user's offline/online preference and settings.
+- **Offline Mode:** A global toggle in Settings disables all remote network fetches, forcing the app to use only locally downloaded map areas and cached camera data. (Instant feedback; no network calls when enabled.)
+- **MapSource Selection:** MapDataProvider lets calling code specify local-only, remote-only, or auto preference for tiles and camera points.
+
+### Map View
+- **Seamless offline/online tile loading:** Tiles are fetched (in parallel, with global concurrency/throttle control and exponential backoff) from OSM *only as needed*, with robust error handling and UI updates as tiles arrive.
+- **Camera overlays** are fetched from Overpass or local cache, respecting both offline mode and user preference for which camera types to display.
+
+### Camera Profiles & Upload Queue
+- Unchanged: creation/editing/enabling; see prior documentation.
+
+### Offline Map Areas
+- **Download tiles/cameras for any bounding box**; areas cover any region/zoom, and are automatically de-duped and managed.
+- **Robust area downloads** use the same MapDataProvider for source-of-truth logic, so downloads are always consistent with runtime lookup.
+- **Permanent world base map** at low zoom always available for core map functionality, even on first-use/offline.
+
+### Modular, Future-friendly Codebase
+- **No network fetch code outside the provider and submodules.**
+- **All legacy/duplicate OSM/Overpass downloaders have been removed or marked for deprecation.**
+
+---
+
+## For Developers
+
+**Highlights:**
+- To add a new data source, just drop in a new submodule and route fetch via MapDataProvider.
+- Any section of the app that needs tiles or camera data calls MapDataProvider with the relevant bounds/zoom/profiles and source preference.
+- Offline Mode and all core settings are strictly respected at a single data/control point.
+
+---
+
+## Roadmap (2025+)
+
+- **COMPLETE:** Core provider logic, settings, robust downloading and modular prefetch/caching.
+- **IN PROGRESS:** Local/offline tile/camera fetch modules for runtime map viewing and offline area management.
+- **NEXT:** More map overlays, offline routing, and data visualization.
+- **SOON:** UX polish for download/error states, multi-layer base maps.
+
+---
+
+*See prior README version for detailed setup/build/dependency notes—they remain unchanged!*
+
### Map View
- **Explore the Map:** View OSM raster tiles, live camera overlays, and a visual scale bar and zoom indicator in the lower left.
diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png
new file mode 100644
index 0000000..342225a
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png
new file mode 100644
index 0000000..5058638
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png
new file mode 100644
index 0000000..72b7566
Binary files /dev/null and b/android/app/src/main/res/drawable-v21/background.png differ
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
index f74085f..3cc4948 100644
--- a/android/app/src/main/res/drawable-v21/launch_background.xml
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -1,12 +1,9 @@
-
-
-
-
-
+ -
+
+
+ -
+
+
diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png
new file mode 100644
index 0000000..88007cc
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png
new file mode 100644
index 0000000..23a7632
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png
new file mode 100644
index 0000000..7231139
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png
new file mode 100644
index 0000000..72b7566
Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
index 304732f..3cc4948 100644
--- a/android/app/src/main/res/drawable/launch_background.xml
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -1,12 +1,9 @@
-
-
-
-
-
+ -
+
+
+ -
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..98e79f7 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..96baa1c 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..2e62a59 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..c17a2f5 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..bea0ec8 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml
new file mode 100644
index 0000000..5fef228
--- /dev/null
+++ b/android/app/src/main/res/values-night-v31/styles.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
index 06952be..dbc9ea9 100644
--- a/android/app/src/main/res/values-night/styles.xml
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -5,6 +5,10 @@
- @drawable/launch_background
+ - false
+ - false
+ - false
+ - shortEdges
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index cb1ef88..0d1fa8f 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -5,6 +5,10 @@
- @drawable/launch_background
+ - false
+ - false
+ - false
+ - shortEdges
- CFBundleURLTypes
-
-
- CFBundleTypeRole
- None
- CFBundleURLSchemes
-
- flockmap
-
-
-
-
-
- LSApplicationQueriesSchemes
-
- https
-
-
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Flock Map App
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ flock_map_app
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ NSLocationWhenInUseUsageDescription
+ This app needs your location to show nearby cameras.
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ None
+ CFBundleURLSchemes
+
+ flockmap
+
+
+
+
+ LSApplicationQueriesSchemes
+
+ https
+
+ UIStatusBarHidden
+
+
diff --git a/lib/app_state.dart b/lib/app_state.dart
index 04cfdb2..510ddf2 100644
--- a/lib/app_state.dart
+++ b/lib/app_state.dart
@@ -42,6 +42,9 @@ class AppState extends ChangeNotifier {
final _auth = AuthService();
String? _username;
+ bool _isInitialized = false;
+ bool get isInitialized => _isInitialized;
+
final List _profiles = [];
final Set _enabled = {};
static const String _enabledPrefsKey = 'enabled_profiles';
@@ -153,6 +156,7 @@ class AppState extends ChangeNotifier {
}
_startUploader();
+ _isInitialized = true;
notifyListeners();
}
diff --git a/lib/main.dart b/lib/main.dart
index 9d85692..d72e78f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -9,7 +9,26 @@ void main() {
runApp(
ChangeNotifierProvider(
create: (_) => AppState(),
- child: const FlockMapApp(),
+ child: Consumer(
+ builder: (context, appState, _) {
+ if (!appState.isInitialized) {
+ // You can customize this splash/loading screen as needed
+ return MaterialApp(
+ home: Scaffold(
+ backgroundColor: Color(0xFF202020),
+ body: Center(
+ child: Image.asset(
+ 'assets/app_icon.png',
+ width: 240,
+ height: 240,
+ ),
+ ),
+ ),
+ );
+ }
+ return const FlockMapApp();
+ },
+ ),
),
);
}
diff --git a/lib/services/map_data_submodules/tiles_from_osm.dart b/lib/services/map_data_submodules/tiles_from_osm.dart
index 8c23579..67dfa7d 100644
--- a/lib/services/map_data_submodules/tiles_from_osm.dart
+++ b/lib/services/map_data_submodules/tiles_from_osm.dart
@@ -9,17 +9,11 @@ final _tileFetchSemaphore = _SimpleSemaphore(4); // Max 4 concurrent
/// Fetches a tile from OSM, with in-memory retries/backoff, and global concurrency limit.
/// Returns tile image bytes, or throws on persistent failure.
-import '../../app_state.dart';
-
Future> fetchOSMTile({
required int z,
required int x,
required int y,
}) async {
- if (AppState().offlineMode) {
- print('[fetchOSMTile] BLOCKED by offline mode ($z/$x/$y)');
- throw Exception('Offline mode enabled—cannot fetch OSM tile.');
- }
final url = 'https://tile.openstreetmap.org/$z/$x/$y.png';
const int maxAttempts = 3;
int attempt = 0;
diff --git a/pubspec.lock b/pubspec.lock
index 7fae507..29982b5 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,30 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ ansicolor:
+ dependency: transitive
+ description:
+ name: ansicolor
+ sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.7"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
async:
dependency: transitive
description:
@@ -17,6 +41,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.4"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.2"
clock:
dependency: transitive
description:
@@ -41,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.6"
+ csslib:
+ dependency: transitive
+ description:
+ name: csslib
+ sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.2"
dart_earcut:
dependency: transitive
description:
@@ -94,6 +142,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.4"
flutter_map:
dependency: "direct main"
description:
@@ -102,6 +158,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.1"
+ flutter_native_splash:
+ dependency: "direct dev"
+ description:
+ name: flutter_native_splash
+ sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.6"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -219,6 +283,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.5"
+ html:
+ dependency: transitive
+ description:
+ name: html
+ sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.15.6"
http:
dependency: "direct main"
description:
@@ -235,6 +307,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.4"
intl:
dependency: transitive
description:
@@ -243,6 +323,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.20.2"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.0"
latlong2:
dependency: "direct main"
description:
@@ -363,6 +451,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.0"
platform:
dependency: transitive
description:
@@ -379,6 +475,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.3"
proj4dart:
dependency: transitive
description:
@@ -512,6 +616,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.1"
+ universal_io:
+ dependency: transitive
+ description:
+ name: universal_io
+ sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.2"
url_launcher:
dependency: transitive
description:
@@ -632,6 +744,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.5.0"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.27.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 06e6a4d..15ae275 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -27,6 +27,10 @@ dependencies:
shared_preferences: ^2.2.2
uuid: ^4.0.0
+dev_dependencies:
+ flutter_launcher_icons: ^0.14.4
+ flutter_native_splash: ^2.4.6
+
flutter:
uses-material-design: true
@@ -34,3 +38,16 @@ flutter:
- assets/info.txt
- assets/transparent_1x1.png
- assets/black_1x1.png
+ - assets/app_icon.png
+
+flutter_native_splash:
+ color: "#202020"
+ image: assets/app_icon.png
+ android: true
+ ios: true
+
+flutter_icons:
+ android: true
+ ios: true
+ image_path: "assets/app_icon.png"
+ min_sdk_android: 21
\ No newline at end of file