mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-11 13:40:20 +02:00
158 lines
4.9 KiB
Dart
158 lines
4.9 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../app_state.dart';
|
|
import '../dev_config.dart';
|
|
import '../services/localization_service.dart';
|
|
|
|
/// Applies a queue-pausing settings change (enabling offline mode or pausing
|
|
/// the upload queue), but if there's an in-flight submission (a queue item
|
|
/// that has already opened a changeset and hasn't closed it yet), first
|
|
/// shows a non-dismissible-by-tap-outside dialog explaining that the
|
|
/// submission needs to finish. Once it completes, a brief confirmation is
|
|
/// shown before the dialog auto-closes and the setting takes effect.
|
|
///
|
|
/// If nothing is in-flight, [applyChange] is invoked immediately with no
|
|
/// dialog shown at all.
|
|
///
|
|
/// The dialog can also be dismissed early by the user tapping "View in
|
|
/// Queue", in case something is stuck (e.g. a failed/erroring item) — this
|
|
/// lets the user navigate to the queue screen to investigate/clear it out,
|
|
/// rather than getting stuck waiting indefinitely.
|
|
Future<void> applyQueueSettingChangeRespectingInFlightUploads({
|
|
required BuildContext context,
|
|
required AppState appState,
|
|
required Future<void> Function() applyChange,
|
|
}) async {
|
|
if (!appState.hasInFlightUploads) {
|
|
await applyChange();
|
|
return;
|
|
}
|
|
|
|
await showDialog<void>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (_) => InFlightUploadWaitDialog(
|
|
onAllComplete: () {
|
|
// Fire and forget - the dialog doesn't need to await this.
|
|
applyChange();
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
class InFlightUploadWaitDialog extends StatefulWidget {
|
|
/// Called exactly once, as soon as no queue items are actively processing.
|
|
final VoidCallback onAllComplete;
|
|
|
|
const InFlightUploadWaitDialog({super.key, required this.onAllComplete});
|
|
|
|
@override
|
|
State<InFlightUploadWaitDialog> createState() => _InFlightUploadWaitDialogState();
|
|
}
|
|
|
|
class _InFlightUploadWaitDialogState extends State<InFlightUploadWaitDialog> {
|
|
bool _completed = false;
|
|
bool _appliedChange = false;
|
|
Timer? _autoCloseTimer;
|
|
late final AppState _appState;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_appState = AppState.instance;
|
|
_appState.addListener(_onAppStateChanged);
|
|
// Handle the (unlikely) race where the in-flight upload finishes between
|
|
// the initial check and this dialog actually mounting.
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _onAppStateChanged());
|
|
}
|
|
|
|
void _onAppStateChanged() {
|
|
if (!mounted || _completed) return;
|
|
if (_appState.hasInFlightUploads) return;
|
|
|
|
setState(() {
|
|
_completed = true;
|
|
});
|
|
|
|
if (!_appliedChange) {
|
|
_appliedChange = true;
|
|
widget.onAllComplete();
|
|
}
|
|
|
|
_autoCloseTimer = Timer(kInFlightUploadCompleteDisplayDuration, () {
|
|
if (mounted) {
|
|
Navigator.of(context, rootNavigator: true).pop();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_appState.removeListener(_onAppStateChanged);
|
|
_autoCloseTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _viewInQueue() {
|
|
Navigator.of(context, rootNavigator: true).pop();
|
|
Navigator.of(context).pushNamed('/settings/queue');
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: LocalizationService.instance,
|
|
builder: (context, child) {
|
|
final locService = LocalizationService.instance;
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
child: AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
Icon(
|
|
_completed ? Icons.check_circle : Icons.cloud_upload,
|
|
color: _completed ? Colors.green : Theme.of(context).colorScheme.primary,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
_completed
|
|
? locService.t('queue.inFlightCompleteTitle')
|
|
: locService.t('queue.inFlightWaitTitle'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
_completed
|
|
? locService.t('queue.inFlightCompleteMessage')
|
|
: locService.t('queue.inFlightWaitMessage'),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Center(
|
|
child: _completed
|
|
? const Icon(Icons.check_circle, size: 40, color: Colors.green)
|
|
: const CircularProgressIndicator(),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
if (!_completed)
|
|
TextButton(
|
|
onPressed: _viewInQueue,
|
|
child: Text(locService.t('node.viewInQueue')),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|