runWithProgress<T> function

Future<T> runWithProgress<T>(
  1. BuildContext context,
  2. String message,
  3. Future<T> task(
    1. ValueChanged<double> onProgress
    ), {
  4. ValueListenable<String>? status,
})

Modal progress for long on-device jobs. task receives a progress callback (0..1); the dialog shows a spinner until it reports one. status, when given, replaces message as the job moves through steps.

Implementation

Future<T> runWithProgress<T>(
  BuildContext context,
  String message,
  Future<T> Function(ValueChanged<double> onProgress) task, {
  ValueListenable<String>? status,
}) async {
  final progress = ValueNotifier<double?>(null);
  final navigator = Navigator.of(context, rootNavigator: true);
  var open = true;
  showDialog<void>(
    context: context,
    barrierDismissible: false,
    useRootNavigator: true,
    // Themed explicitly: [context] may sit above the editor's dark theme.
    builder: (context) => VideoEditorTheme.wrap(PopScope(
      canPop: false,
      child: AlertDialog(
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const SizedBox(height: VideoEditorSpacing.sm),
            ValueListenableBuilder<double?>(
              valueListenable: progress,
              builder: (context, value, _) => Column(
                children: [
                  SizedBox(
                    width: 48,
                    height: 48,
                    child: CircularProgressIndicator(value: value, strokeWidth: 3),
                  ),
                  if (value != null) ...[
                    const SizedBox(height: VideoEditorSpacing.sm),
                    Text(
                      '${(value * 100).round()}%',
                      style: VideoEditorTextStyles.monoTimecode,
                    ),
                  ],
                ],
              ),
            ),
            const SizedBox(height: VideoEditorSpacing.md),
            ValueListenableBuilder<String>(
              valueListenable: status ?? ValueNotifier(message),
              builder: (context, value, _) => Text(
                value,
                textAlign: TextAlign.center,
                style: VideoEditorTextStyles.body,
              ),
            ),
            const SizedBox(height: VideoEditorSpacing.xs),
            const Text(
              'Runs on your device — keep the app open.',
              textAlign: TextAlign.center,
              style: VideoEditorTextStyles.caption,
            ),
          ],
        ),
      ),
    )),
  ).whenComplete(() => open = false);
  try {
    return await task((value) => progress.value = value.clamp(0.0, 1.0));
  } finally {
    // The notifier is left to the GC: the dialog may still be animating out.
    if (open && navigator.mounted) navigator.pop();
  }
}