Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ Key packages used in this project:
- `flutter_blue_plus`: Mobile Bluetooth (Android/iOS)
- `flutter_web_bluetooth`: Web Bluetooth (Chrome/Edge)
- `geolocator`: GPS/Location
- `flutter_map`: Map rendering
- `maplibre_gl`: Map rendering (MapLibre GL vector tiles via OpenFreeMap)
- `hive`: Local storage
- `provider`: State management
- `http`: API requests
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ android {
applicationId = "net.meshmapper.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
minSdk = flutter.minSdkVersion // MapLibre GL requires 23+
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin just_audio, com.ryanheise.just_audio.JustAudioPlugin", e);
}
try {
flutterEngine.getPlugins().add(new org.maplibre.maplibregl.MapLibreMapsPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin maplibre_gl, org.maplibre.maplibregl.MapLibreMapsPlugin", e);
}
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin());
} catch (Exception e) {
Expand Down
22 changes: 0 additions & 22 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,5 @@ end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)

target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_EVENTS=0',
'PERMISSION_EVENTS_FULL_ACCESS=0',
'PERMISSION_REMINDERS=0',
'PERMISSION_CONTACTS=0',
'PERMISSION_CAMERA=0',
'PERMISSION_MICROPHONE=0',
'PERMISSION_SPEECH_RECOGNIZER=0',
'PERMISSION_PHOTOS=0',
'PERMISSION_LOCATION=1',
'PERMISSION_NOTIFICATIONS=1',
'PERMISSION_MEDIA_LIBRARY=0',
'PERMISSION_SENSORS=0',
'PERMISSION_BLUETOOTH=0',
'PERMISSION_APP_TRACKING_TRANSPARENCY=0',
'PERMISSION_CRITICAL_ALERTS=0',
'PERMISSION_ASSISTANT=0',
]
end
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'services/bluetooth/mobile_bluetooth.dart';
import 'services/bluetooth/web_bluetooth.dart';
import 'services/background_service.dart';
import 'services/debug_file_logger.dart';
import 'services/offline_map_service.dart';
import 'utils/debug_logger_io.dart';

void main() async {
Expand Down Expand Up @@ -69,6 +70,11 @@ void main() async {
await BackgroundServiceManager.cleanupOrphanedService();
}

// Clean up any stale offline map download notification
if (!kIsWeb) {
await OfflineMapService().cleanupOrphanedNotification();
}

runApp(MeshMapperApp(initialThemeMode: initialThemeMode));
}

Expand Down Expand Up @@ -219,6 +225,9 @@ class MeshMapperApp extends StatelessWidget {
ChangeNotifierProvider(
create: (_) => AppStateProvider(bluetoothService: bluetoothService),
),
ChangeNotifierProvider(
create: (_) => OfflineMapService()..initialize(),
),
],
child: _ThemedApp(initialThemeMode: initialThemeMode),
);
Expand Down
18 changes: 16 additions & 2 deletions lib/models/user_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ class UserPreferences {
/// Download map tiles (base map + coverage overlay). When false, no tile network requests are made to save mobile data.
final bool mapTilesEnabled;

/// MeshMapper coverage raster overlay opacity (0.0 = fully transparent,
/// 1.0 = fully opaque). Applied to the `meshmapper-overlay-layer` raster
/// layer so users can see the base map underneath the coverage squares.
final double coverageOverlayOpacity;

/// Disconnect alert: play audible alert when pinging stops unexpectedly (BLE disconnect, idle timeout, maintenance)
final bool disconnectAlertEnabled;

Expand Down Expand Up @@ -129,7 +134,7 @@ class UserPreferences {
this.iataCode,
this.backgroundModeEnabled = false,
this.developerModeEnabled = false,
this.mapStyle = 'dark',
this.mapStyle = 'liberty',
this.closeAppAfterDisconnect = false,
this.themeMode = 'dark',
this.unitSystem = 'metric',
Expand All @@ -148,6 +153,7 @@ class UserPreferences {
this.gpsMarkerStyle = 'arrow',
this.colorVisionType = 'none',
this.mapTilesEnabled = true,
this.coverageOverlayOpacity = 0.7,
this.disconnectAlertEnabled = false,
this.customApiEnabled = false,
this.customApiUrl,
Expand All @@ -172,7 +178,7 @@ class UserPreferences {
iataCode: json['iataCode'] as String?,
backgroundModeEnabled: (json['backgroundModeEnabled'] as bool?) ?? false,
developerModeEnabled: (json['developerModeEnabled'] as bool?) ?? false,
mapStyle: (json['mapStyle'] as String?) ?? 'dark',
mapStyle: (json['mapStyle'] as String?) ?? 'liberty',
closeAppAfterDisconnect:
(json['closeAppAfterDisconnect'] as bool?) ?? false,
themeMode: (json['themeMode'] as String?) ?? 'dark',
Expand All @@ -193,6 +199,8 @@ class UserPreferences {
gpsMarkerStyle: _migrateGpsMarkerStyle(json['gpsMarkerStyle'] as String?),
colorVisionType: (json['colorVisionType'] as String?) ?? 'none',
mapTilesEnabled: (json['mapTilesEnabled'] as bool?) ?? true,
coverageOverlayOpacity:
(json['coverageOverlayOpacity'] as num?)?.toDouble() ?? 0.7,
disconnectAlertEnabled:
(json['disconnectAlertEnabled'] as bool?) ?? false,
customApiEnabled: (json['customApiEnabled'] as bool?) ?? false,
Expand Down Expand Up @@ -247,6 +255,7 @@ class UserPreferences {
'gpsMarkerStyle': gpsMarkerStyle,
'colorVisionType': colorVisionType,
'mapTilesEnabled': mapTilesEnabled,
'coverageOverlayOpacity': coverageOverlayOpacity,
'disconnectAlertEnabled': disconnectAlertEnabled,
'customApiEnabled': customApiEnabled,
'customApiUrl': customApiUrl,
Expand Down Expand Up @@ -290,6 +299,7 @@ class UserPreferences {
String? gpsMarkerStyle,
String? colorVisionType,
bool? mapTilesEnabled,
double? coverageOverlayOpacity,
bool? disconnectAlertEnabled,
bool? customApiEnabled,
String? customApiUrl,
Expand Down Expand Up @@ -334,6 +344,8 @@ class UserPreferences {
gpsMarkerStyle: gpsMarkerStyle ?? this.gpsMarkerStyle,
colorVisionType: colorVisionType ?? this.colorVisionType,
mapTilesEnabled: mapTilesEnabled ?? this.mapTilesEnabled,
coverageOverlayOpacity:
coverageOverlayOpacity ?? this.coverageOverlayOpacity,
disconnectAlertEnabled:
disconnectAlertEnabled ?? this.disconnectAlertEnabled,
customApiEnabled: customApiEnabled ?? this.customApiEnabled,
Expand Down Expand Up @@ -406,6 +418,7 @@ class UserPreferences {
other.gpsMarkerStyle == gpsMarkerStyle &&
other.colorVisionType == colorVisionType &&
other.mapTilesEnabled == mapTilesEnabled &&
other.coverageOverlayOpacity == coverageOverlayOpacity &&
other.disconnectAlertEnabled == disconnectAlertEnabled &&
other.customApiEnabled == customApiEnabled &&
other.customApiUrl == customApiUrl &&
Expand Down Expand Up @@ -448,6 +461,7 @@ class UserPreferences {
gpsMarkerStyle,
colorVisionType,
mapTilesEnabled,
coverageOverlayOpacity,
disconnectAlertEnabled,
customApiEnabled,
customApiUrl,
Expand Down
51 changes: 24 additions & 27 deletions lib/providers/app_state_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,9 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
'[APP] Upload success: +$uploadedCount items (total: ${_pingStats.successfulUploads})');
notifyListeners();

// Schedule overlay tile refresh after server has time to regenerate tiles
// Cache buster change + notifyListeners triggers flutter_map's reloadImages()
// which updates tile URLs in-place and refetches cleanly
// Schedule overlay tile refresh after server has time to regenerate tiles.
// The MapWidget watches _overlayCacheBust and calls _refreshCoverageOverlay()
// (remove + re-add raster source with new URL) when it changes.
_tileRefreshTimer?.cancel();
_tileRefreshTimer = Timer(const Duration(seconds: 5), () {
_overlayCacheBust = DateTime.now().millisecondsSinceEpoch;
Expand Down Expand Up @@ -4192,6 +4192,20 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
_savePreferences();
}

/// Set coverage overlay opacity (0.3–1.0) and persist.
/// MapWidget watches `preferences.coverageOverlayOpacity` and applies the
/// new value to the raster layer at runtime via setLayerProperties, so the
/// overlay fades live as the slider moves. Lower bound of 0.3 prevents the
/// overlay from disappearing entirely.
void setCoverageOverlayOpacity(double opacity) {
final clamped = opacity.clamp(0.3, 1.0);
_preferences = _preferences.copyWith(coverageOverlayOpacity: clamped);
debugLog(
'[MAP] Coverage overlay opacity set to ${clamped.toStringAsFixed(2)}');
notifyListeners();
_savePreferences();
}

/// Set app theme mode (dark/light) and persist
void setThemeMode(String mode) {
_preferences = _preferences.copyWith(themeMode: mode);
Expand Down Expand Up @@ -4720,14 +4734,15 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {

if (reason == 'gps_inaccurate') {
logError('GPS Accuracy Error\n$message', autoSwitch: false);
_zoneCheckError = message;
_zoneCheckErrorReason = 'gps_inaccurate';
notifyListeners();
// Schedule a retry so we don't depend solely on the GPS stream firing
// again — on first launch the stream may stall on a low-accuracy fix
// and the coverage tile overlay would never load.
_scheduleZoneCheckRetry(
seconds: 10, error: message, reason: 'gps_inaccurate');
} else if (reason == 'gps_stale') {
logError('GPS Stale Error\n$message', autoSwitch: false);
_zoneCheckError = message;
_zoneCheckErrorReason = 'gps_stale';
notifyListeners();
_scheduleZoneCheckRetry(
seconds: 10, error: message, reason: 'gps_stale');
} else if (reason == 'zone_disabled') {
final errorMsg = _getErrorMessage(reason, message);
logError(errorMsg);
Expand Down Expand Up @@ -5348,24 +5363,6 @@ class AppStateProvider extends ChangeNotifier with WidgetsBindingObserver {
await disconnect();
}

/// Force a repeater refetch when the cached list is empty (e.g. popup open,
/// offline session, startup race). Uses the current zone if known, otherwise
/// falls back to the user-configured IATA. No-op if neither is available or
/// if repeaters are already loaded.
Future<void> refetchRepeatersIfPossible() async {
if (_repeaters.isNotEmpty) return;
final iata = (zoneCode?.isNotEmpty == true)
? zoneCode
: _preferences.iataCode;
if (iata == null || iata.isEmpty) {
debugLog('[MAP] refetchRepeatersIfPossible: no IATA available, skipping');
return;
}
_repeatersLoaded = false;
_repeatersLoadedForIata = null;
await _fetchRepeatersForZone(iata);
}

/// Fetch repeaters for a zone (called when zone is discovered)
/// Only fetches once per IATA code to avoid redundant network requests
Future<void> _fetchRepeatersForZone(String iata) async {
Expand Down
4 changes: 1 addition & 3 deletions lib/screens/log_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,7 @@ class _AllPingsTabState extends State<_AllPingsTab> {
case PingLogType.disc:
final disc = entry.asDisc;
for (final node in disc.discoveredNodes) {
if (node.repeaterId.toLowerCase().startsWith(query)) {
return true;
}
if (node.repeaterId.toLowerCase().startsWith(query)) return true;
if (node.pubkeyHex != null &&
node.pubkeyHex!.toLowerCase().startsWith(query)) {
return true;
Expand Down
Loading
Loading