mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Merge branch 'chore/app-store-screenshots'
This commit is contained in:
@@ -50,6 +50,9 @@ class MeshCoreSarApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
static const bool _isScreenshotRun = bool.fromEnvironment(
|
||||
'MESHCORE_SCREENSHOTS',
|
||||
);
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
AppThemeMode _themeMode = AppThemeMode.system;
|
||||
Locale? _locale;
|
||||
@@ -87,7 +90,9 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
_pendingNotificationPayload = NotificationService().consumeLaunchPayload();
|
||||
|
||||
// Check if we need to request location permissions
|
||||
await _checkLocationPermissions();
|
||||
if (!_isScreenshotRun) {
|
||||
await _checkLocationPermissions();
|
||||
}
|
||||
|
||||
// Check for app updates (Android only) - runs in background
|
||||
// Shows notification if update is available
|
||||
|
||||
@@ -56,6 +56,7 @@ class OfflineTilesProvider extends ChangeNotifier {
|
||||
TileDownloadService? _downloadService;
|
||||
StreamSubscription<TileDownloadEvent>? _downloadSubscription;
|
||||
StreamSubscription<Set<TilePeer>>? _peersSubscription;
|
||||
bool _isDisposed = false;
|
||||
|
||||
// Drawing state
|
||||
DrawingMode _drawingMode = DrawingMode.none;
|
||||
@@ -436,7 +437,9 @@ class OfflineTilesProvider extends ChangeNotifier {
|
||||
_peersSubscription = null;
|
||||
await _sharing.stopPeerDiscovery();
|
||||
_discoveredPeers = {};
|
||||
notifyListeners();
|
||||
if (!_isDisposed) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void addManualPeer(String ipAddress) {
|
||||
@@ -569,6 +572,7 @@ class OfflineTilesProvider extends ChangeNotifier {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
_downloadSubscription?.cancel();
|
||||
_downloadService?.dispose();
|
||||
_peersSubscription?.cancel();
|
||||
|
||||
@@ -38,6 +38,9 @@ class ContactsTab extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ContactsTabState extends State<ContactsTab> {
|
||||
static const bool _isScreenshotRun = bool.fromEnvironment(
|
||||
'MESHCORE_SCREENSHOTS',
|
||||
);
|
||||
Position? _currentPosition;
|
||||
final Map<ContactSection, String> _sectionFilters = {
|
||||
ContactSection.teamMembers: '',
|
||||
@@ -62,7 +65,9 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
for (final section in ContactSection.values)
|
||||
section: TextEditingController(text: _sectionFilters[section] ?? ''),
|
||||
};
|
||||
_getCurrentLocation();
|
||||
if (!_isScreenshotRun) {
|
||||
_getCurrentLocation();
|
||||
}
|
||||
// Mark all contacts as viewed when tab is opened
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<ContactsProvider>().markAllAsViewed();
|
||||
|
||||
@@ -14,6 +14,9 @@ class NotificationService {
|
||||
factory NotificationService() => _instance;
|
||||
NotificationService._internal();
|
||||
|
||||
static const bool _skipPermissionRequests = bool.fromEnvironment(
|
||||
'MESHCORE_SCREENSHOTS',
|
||||
);
|
||||
final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static const String _prefMessagesEnabled = 'notifications_messages_enabled';
|
||||
@@ -101,9 +104,9 @@ class NotificationService {
|
||||
|
||||
// iOS initialization settings
|
||||
final darwinSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
requestAlertPermission: !_skipPermissionRequests,
|
||||
requestBadgePermission: !_skipPermissionRequests,
|
||||
requestSoundPermission: !_skipPermissionRequests,
|
||||
);
|
||||
|
||||
// Combined initialization settings
|
||||
@@ -125,7 +128,11 @@ class NotificationService {
|
||||
}
|
||||
|
||||
// Request permissions
|
||||
await _requestPermissions();
|
||||
if (_skipPermissionRequests) {
|
||||
_permissionGranted = true;
|
||||
} else {
|
||||
await _requestPermissions();
|
||||
}
|
||||
await _loadPreferences();
|
||||
|
||||
// Create notification channels (Android)
|
||||
@@ -338,6 +345,8 @@ class NotificationService {
|
||||
String? notes,
|
||||
AppLocalizations? localizations,
|
||||
}) async {
|
||||
if (_skipPermissionRequests) return;
|
||||
|
||||
if (!_isInitialized) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Not initialized, skipping notification',
|
||||
@@ -520,6 +529,8 @@ class NotificationService {
|
||||
String? channelName,
|
||||
AppLocalizations? localizations,
|
||||
}) async {
|
||||
if (_skipPermissionRequests) return;
|
||||
|
||||
if (!_isInitialized) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Not initialized, skipping notification',
|
||||
@@ -722,6 +733,8 @@ class NotificationService {
|
||||
required String downloadUrl,
|
||||
AppLocalizations? localizations,
|
||||
}) async {
|
||||
if (_skipPermissionRequests) return;
|
||||
|
||||
if (!_isInitialized) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Not initialized, skipping notification',
|
||||
@@ -822,6 +835,8 @@ class NotificationService {
|
||||
required double batteryPercent,
|
||||
required bool isCurrentDevice,
|
||||
}) async {
|
||||
if (_skipPermissionRequests) return false;
|
||||
|
||||
if (!_isInitialized) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Not initialized, skipping notification',
|
||||
@@ -913,6 +928,8 @@ class NotificationService {
|
||||
required String contactKey,
|
||||
String? contactName,
|
||||
}) async {
|
||||
if (_skipPermissionRequests) return false;
|
||||
|
||||
if (!_isInitialized) return false;
|
||||
if (!_permissionGranted) return false;
|
||||
if (!_discoveryNotificationsEnabled) return false;
|
||||
|
||||
@@ -11,6 +11,7 @@ class VoicePlayerService {
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
final StreamController<void> _events = StreamController<void>.broadcast();
|
||||
bool _isPlaying = false;
|
||||
bool _isDisposed = false;
|
||||
Duration _position = Duration.zero;
|
||||
Duration _duration = Duration.zero;
|
||||
Timer? _fallbackTicker;
|
||||
@@ -30,22 +31,22 @@ class VoicePlayerService {
|
||||
} else {
|
||||
_stopFallbackTicker();
|
||||
}
|
||||
_events.add(null);
|
||||
_emit();
|
||||
});
|
||||
_player.onLog.listen((msg) => debugPrint('🔊 [VoicePlayer] log: $msg'));
|
||||
_player.onPositionChanged.listen((position) {
|
||||
_position = position;
|
||||
_events.add(null);
|
||||
_emit();
|
||||
});
|
||||
_player.onDurationChanged.listen((duration) {
|
||||
_duration = duration;
|
||||
_events.add(null);
|
||||
_emit();
|
||||
});
|
||||
_player.onPlayerComplete.listen((_) {
|
||||
_isPlaying = false;
|
||||
_position = _duration;
|
||||
_stopFallbackTicker();
|
||||
_events.add(null);
|
||||
_emit();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ class VoicePlayerService {
|
||||
milliseconds: (pcmSamples.length * 1000) ~/ sampleRateHz,
|
||||
);
|
||||
_playbackStartedAt = DateTime.now();
|
||||
_events.add(null);
|
||||
_emit();
|
||||
|
||||
final wavBytes = _buildWav(pcmSamples, sampleRate: sampleRateHz);
|
||||
final tmpDir = await getTemporaryDirectory();
|
||||
@@ -70,14 +71,14 @@ class VoicePlayerService {
|
||||
try {
|
||||
_isPlaying = true;
|
||||
_startFallbackTicker();
|
||||
_events.add(null);
|
||||
_emit();
|
||||
await _player.play(DeviceFileSource(file.path));
|
||||
debugPrint('🔊 [VoicePlayer] play() returned (audio playing)');
|
||||
} catch (e, st) {
|
||||
debugPrint('❌ [VoicePlayer] play() error: $e\n$st');
|
||||
_isPlaying = false;
|
||||
_stopFallbackTicker();
|
||||
_events.add(null);
|
||||
_emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,15 +89,22 @@ class VoicePlayerService {
|
||||
_position = Duration.zero;
|
||||
_playbackStartedAt = null;
|
||||
_stopFallbackTicker();
|
||||
_events.add(null);
|
||||
_emit();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
_stopFallbackTicker();
|
||||
_events.close();
|
||||
_player.dispose();
|
||||
}
|
||||
|
||||
void _emit() {
|
||||
if (!_isDisposed) {
|
||||
_events.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
void _startFallbackTicker() {
|
||||
if (_fallbackTicker != null) return;
|
||||
_fallbackTicker = Timer.periodic(const Duration(milliseconds: 100), (_) {
|
||||
@@ -107,7 +115,7 @@ class VoicePlayerService {
|
||||
final clamped = elapsed > _duration ? _duration : elapsed;
|
||||
if (clamped > _position) {
|
||||
_position = clamped;
|
||||
_events.add(null);
|
||||
_emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user