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