feat(studio): real install progress in the store dialog and the wizard
Both surfaces showed an indeterminate spinner for the whole install, because the hub only offered a unary call. They now follow HubAdmin/InstallModuleStream: - The store's install dialog shows the phase in plain language, the overall percentage, megabytes while the size is known, and a seconds counter. The counter is the second, independent signal: it keeps running even if the hub goes quiet, which is what tells 'slow' apart from 'stuck'. - The setup wizard shows the same bar per module instead of a spinner inside the button. - New strings in both locales for the phases, the byte line and the elapsed counter. progress_bar_visual_test writes the four waiting states to build/progress/ in either theme, so the result can be judged without launching the desktop app. Writing it turned up a real trap worth recording: awaiting toImage() directly in a widget test leaves the binding waiting forever - the file passed in six seconds, then sat there until the ten-minute timeout failed the whole suite. tester.runAsync() is the fix. Full suite: 186 passed. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
2bc556e0f5
commit
1549334767
9 changed files with 490 additions and 74 deletions
|
|
@ -22,6 +22,7 @@ import '../data/system_actions.dart';
|
|||
import '../l10n/app_localizations.dart';
|
||||
import '../main.dart' show StudioShellState;
|
||||
import '../theme/tokens.dart';
|
||||
import 'chain_progress_bar.dart';
|
||||
import 'chain_stores_dialog.dart';
|
||||
|
||||
/// Allowed wire values per answer — an AI suggestion is validated
|
||||
|
|
@ -570,17 +571,43 @@ class _GuidedSetupDialogState extends State<GuidedSetupDialog> {
|
|||
|
||||
/// Install one plan module by capability name — the hub resolves
|
||||
/// the bundle URL from its store index.
|
||||
/// Latest progress observation per module being installed.
|
||||
final Map<String, InstallStep> _installSteps = {};
|
||||
|
||||
Future<void> _install(String module) async {
|
||||
setState(() => _installing.add(module));
|
||||
try {
|
||||
await HubService.instance.installModule(source: module);
|
||||
// Streamed, not awaited blind: a module bundle can take a while
|
||||
// on a slow line, and a dead-looking button is what made people
|
||||
// click twice.
|
||||
await for (final step
|
||||
in HubService.instance.installModuleStreaming(source: module)) {
|
||||
if (mounted) setState(() => _installSteps[module] = step);
|
||||
}
|
||||
if (mounted) setState(() => _installed.add(module));
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
await showChainErrorDialog(context, 'install $module', e);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _installing.remove(module));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_installing.remove(module);
|
||||
_installSteps.remove(module);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plain-language label for a module's current install phase.
|
||||
String _installStage(AppLocalizations l, String module) {
|
||||
switch (_installSteps[module]?.phase) {
|
||||
case 'download':
|
||||
return l.installPhaseDownload;
|
||||
case 'install':
|
||||
return l.installPhaseInstall;
|
||||
default:
|
||||
return l.installPhaseResolve;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -966,18 +993,39 @@ class _GuidedSetupDialogState extends State<GuidedSetupDialog> {
|
|||
_installed.contains(m)
|
||||
? _doneRow(l.setupActionInstalled(m))
|
||||
: _actionRow(
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: (!_hubUp || _installing.contains(m))
|
||||
? null
|
||||
: () => _install(m),
|
||||
icon: _installing.contains(m)
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.download, size: 18),
|
||||
label: Text(l.setupActionInstall(m)),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: (!_hubUp || _installing.contains(m))
|
||||
? null
|
||||
: () => _install(m),
|
||||
icon: _installing.contains(m)
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.download, size: 18),
|
||||
label: Text(l.setupActionInstall(m)),
|
||||
),
|
||||
if (_installing.contains(m)) ...[
|
||||
const SizedBox(height: ChainSpace.sm),
|
||||
SizedBox(
|
||||
width: 320,
|
||||
child: ChainProgressBar(
|
||||
value: _installSteps[m]?.percent == null
|
||||
? null
|
||||
: _installSteps[m]!.percent! / 100,
|
||||
stage: _installStage(l, m),
|
||||
height: 6,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue