mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 17:18:36 +02:00
151 lines
5.0 KiB
Dart
151 lines
5.0 KiB
Dart
// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
|
part of 'download_queue_provider.dart';
|
|
|
|
extension _DownloadQueueConnectivity on DownloadQueueNotifier {
|
|
bool _hasWifiConnection(List<ConnectivityResult> results) {
|
|
return results.contains(ConnectivityResult.wifi);
|
|
}
|
|
|
|
void _startConnectivityMonitoring() {
|
|
_connectivitySub?.cancel();
|
|
_connectivitySub = Connectivity().onConnectivityChanged.listen(
|
|
_handleConnectivityResults,
|
|
onError: (Object error, StackTrace stackTrace) {
|
|
_log.w('Connectivity monitoring failed: $error');
|
|
},
|
|
cancelOnError: false,
|
|
);
|
|
}
|
|
|
|
void _stopConnectivityMonitoring({bool clearNetworkPause = true}) {
|
|
if (clearNetworkPause) {
|
|
_networkPausedByWifiOnly = false;
|
|
}
|
|
// Keep listening while network-failed items remain so the reconnect
|
|
// retry prompt can still fire when the queue is otherwise idle.
|
|
if (_hasNetworkFailedItems) return;
|
|
_connectivitySub?.cancel();
|
|
_connectivitySub = null;
|
|
}
|
|
|
|
bool get _hasNetworkFailedItems => state.items.any(
|
|
(item) =>
|
|
item.status == DownloadStatus.failed &&
|
|
item.errorType == DownloadErrorType.network,
|
|
);
|
|
|
|
/// Offers a one-tap retry for network-failed items once connectivity
|
|
/// returns, debounced so network flapping doesn't spam snackbars.
|
|
void _maybeOfferRetryAfterReconnect(List<ConnectivityResult> results) {
|
|
if (results.every((result) => result == ConnectivityResult.none)) return;
|
|
final failedCount = state.items
|
|
.where(
|
|
(item) =>
|
|
item.status == DownloadStatus.failed &&
|
|
item.errorType == DownloadErrorType.network,
|
|
)
|
|
.length;
|
|
if (failedCount == 0) return;
|
|
final now = DateTime.now();
|
|
if (now.difference(_lastReconnectRetryPromptAt) <
|
|
const Duration(minutes: 1)) {
|
|
return;
|
|
}
|
|
_lastReconnectRetryPromptAt = now;
|
|
|
|
final context = AppNavigationService.rootNavigatorKey.currentContext;
|
|
if (context == null || !context.mounted) return;
|
|
final l10n = context.l10n;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(l10n.queueNetworkFailedOffline(failedCount)),
|
|
action: SnackBarAction(
|
|
label: l10n.dialogRetry,
|
|
onPressed: () => retryAllFailed(networkOnly: true),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleDownloadNetworkModeChanged(String mode) {
|
|
if (mode == 'wifi_only') {
|
|
if (state.isProcessing || _networkPausedByWifiOnly) {
|
|
_startConnectivityMonitoring();
|
|
}
|
|
return;
|
|
}
|
|
|
|
final shouldResume = _networkPausedByWifiOnly && state.isPaused;
|
|
// Keep monitoring active in every mode while downloading so idle
|
|
// connections are still recycled when the network switches.
|
|
if (state.isProcessing) {
|
|
_networkPausedByWifiOnly = false;
|
|
_startConnectivityMonitoring();
|
|
} else {
|
|
_stopConnectivityMonitoring();
|
|
}
|
|
if (shouldResume) {
|
|
resumeQueue();
|
|
}
|
|
}
|
|
|
|
/// Closes idle backend HTTP connections when the connectivity set changes
|
|
/// (e.g. WiFi <-> cellular). Stale sockets bound to the old interface would
|
|
/// otherwise be reused, stalling the first request after a network switch.
|
|
/// Applies to every download network mode. Fire-and-forget with a light
|
|
/// debounce so network flapping does not spam the bridge.
|
|
void _maybeCleanupOnNetworkChange(List<ConnectivityResult> results) {
|
|
final previous = _lastConnectivityResults;
|
|
final unchanged =
|
|
previous != null && _connectivitySetEquals(previous, results);
|
|
_lastConnectivityResults = List<ConnectivityResult>.unmodifiable(results);
|
|
if (previous == null || unchanged) return;
|
|
|
|
final now = DateTime.now();
|
|
if (now.difference(_lastConnectionCleanupAt) <
|
|
DownloadQueueNotifier._connectionCleanupDebounce) {
|
|
return;
|
|
}
|
|
_lastConnectionCleanupAt = now;
|
|
|
|
_log.i('Network changed, closing idle backend connections');
|
|
unawaited(
|
|
PlatformBridge.cleanupConnections().catchError((Object e) {
|
|
_log.w('Failed to clean up connections after network change: $e');
|
|
}),
|
|
);
|
|
}
|
|
|
|
bool _connectivitySetEquals(
|
|
List<ConnectivityResult> a,
|
|
List<ConnectivityResult> b,
|
|
) {
|
|
final setA = a.toSet();
|
|
final setB = b.toSet();
|
|
return setA.length == setB.length && setA.containsAll(setB);
|
|
}
|
|
|
|
void _handleConnectivityResults(List<ConnectivityResult> results) {
|
|
_maybeCleanupOnNetworkChange(results);
|
|
_maybeOfferRetryAfterReconnect(results);
|
|
|
|
final settings = ref.read(settingsProvider);
|
|
if (settings.downloadNetworkMode != 'wifi_only') return;
|
|
|
|
if (_hasWifiConnection(results)) {
|
|
if (_networkPausedByWifiOnly && state.isPaused) {
|
|
_networkPausedByWifiOnly = false;
|
|
_log.i('WiFi restored, resuming network-paused queue');
|
|
resumeQueue();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (state.isProcessing && !state.isPaused) {
|
|
_networkPausedByWifiOnly = true;
|
|
_log.w('WiFi connection lost, pausing active queue');
|
|
pauseQueue();
|
|
}
|
|
}
|
|
}
|