added support for simultan downloads in files and talk, support for background downloads, enhanced loading in files with retry

This commit is contained in:
2026-07-06 15:43:17 +02:00
parent 614ab159af
commit 124b1a5177
34 changed files with 1866 additions and 409 deletions
@@ -111,7 +111,10 @@ class LoadableStateConsumer<
Expanded(
child: Stack(
children: [
LoadableStatePrimaryLoading(visible: showPrimaryLoading),
LoadableStatePrimaryLoading(
visible: showPrimaryLoading,
statusText: loadableState.statusText,
),
LoadableStateBackgroundLoading(
visible: showBackgroundLoading,
),
@@ -1,17 +1,105 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../../../widget/app_progress_indicator.dart';
import 'loadable_state_consumer.dart';
class LoadableStatePrimaryLoading extends StatelessWidget {
/// Full-screen spinner for the initial load. A minutes-long spinner without
/// feedback looks stuck (the Nextcloud root listing can take that long), so
/// after [slowHintAfter] a generic slow-server note fades in below it. An
/// explicit [statusText] (e.g. retry progress from the bloc) always takes
/// precedence over the timer-based hint.
class LoadableStatePrimaryLoading extends StatefulWidget {
final bool visible;
const LoadableStatePrimaryLoading({required this.visible, super.key});
final String? statusText;
final Duration slowHintAfter;
const LoadableStatePrimaryLoading({
required this.visible,
this.statusText,
this.slowHintAfter = const Duration(seconds: 8),
super.key,
});
static const String slowHintText = 'Der Server antwortet verzögert …';
@override
Widget build(BuildContext context) => AnimatedOpacity(
opacity: visible ? 1.0 : 0.0,
duration: LoadableStateConsumer.animationDuration,
curve: Curves.easeInOut,
child: const Center(child: AppProgressIndicator.large()),
);
State<LoadableStatePrimaryLoading> createState() =>
_LoadableStatePrimaryLoadingState();
}
class _LoadableStatePrimaryLoadingState
extends State<LoadableStatePrimaryLoading> {
Timer? _slowHintTimer;
bool _showSlowHint = false;
@override
void initState() {
super.initState();
_restartSlowHintTimer();
}
@override
void didUpdateWidget(covariant LoadableStatePrimaryLoading oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.visible != oldWidget.visible) _restartSlowHintTimer();
}
void _restartSlowHintTimer() {
_slowHintTimer?.cancel();
_showSlowHint = false;
if (!widget.visible) return;
_slowHintTimer = Timer(widget.slowHintAfter, () {
if (mounted) setState(() => _showSlowHint = true);
});
}
@override
void dispose() {
_slowHintTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final status =
widget.statusText ??
(_showSlowHint ? LoadableStatePrimaryLoading.slowHintText : null);
return AnimatedOpacity(
opacity: widget.visible ? 1.0 : 0.0,
duration: LoadableStateConsumer.animationDuration,
curve: Curves.easeInOut,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const AppProgressIndicator.large(),
AnimatedSwitcher(
duration: LoadableStateConsumer.animationDuration,
child: status == null
? const SizedBox.shrink()
: Padding(
key: ValueKey(status),
padding: const EdgeInsets.only(
top: 16,
left: 24,
right: 24,
),
child: Text(
status,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).hintColor,
),
),
),
),
],
),
),
);
}
}