LCOV - code coverage report
Current view: top level - src/widgets - task_progress_widgets.dart Coverage Total Hit
Test: lcov.info Lines: 0.0 % 62 0
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            Line data    Source code
       1              : import 'package:flutter/material.dart';
       2              : import '../events.dart';
       3              : import '../task_handler.dart';
       4              : 
       5              : /// A widget that builds itself based on the latest [TaskProgress] of a task.
       6              : ///
       7              : /// Use this to easily reactive-ly update your UI as a background task
       8              : /// reports its progress, speed, and ETA.
       9              : ///
      10              : /// This is a high-level wrapper around [StreamBuilder] that automatically
      11              : /// handles the [TaskHandler.progress] stream.
      12              : ///
      13              : /// ## Example
      14              : ///
      15              : /// ```dart
      16              : /// TaskProgressBuilder(
      17              : ///   handler: myTaskHandler,
      18              : ///   builder: (context, progress) {
      19              : ///     if (progress == null) return Text('Waiting for progress...');
      20              : ///     return LinearProgressIndicator(value: progress.progress / 100);
      21              : ///   },
      22              : /// )
      23              : /// ```
      24              : class TaskProgressBuilder extends StatelessWidget {
      25              :   /// The handler for the task to track.
      26              :   final TaskHandler handler;
      27              : 
      28              :   /// The builder function that is called every time a new progress update arrives.
      29              :   ///
      30              :   /// `progress` is the latest update, or null if no update has arrived yet.
      31              :   final Widget Function(BuildContext context, TaskProgress? progress) builder;
      32              : 
      33              :   /// An optional initial progress value to use before the first update arrives.
      34              :   final TaskProgress? initialProgress;
      35              : 
      36            0 :   const TaskProgressBuilder({
      37              :     super.key,
      38              :     required this.handler,
      39              :     required this.builder,
      40              :     this.initialProgress,
      41              :   });
      42              : 
      43            0 :   @override
      44              :   Widget build(BuildContext context) {
      45            0 :     return StreamBuilder<TaskProgress>(
      46            0 :       stream: handler.progress,
      47            0 :       initialData: initialProgress,
      48            0 :       builder: (context, snapshot) {
      49            0 :         return builder(context, snapshot.data);
      50              :       },
      51              :     );
      52              :   }
      53              : }
      54              : 
      55              : /// A pre-styled Material 3 card that displays the progress of a background task.
      56              : ///
      57              : /// Displays:
      58              : /// - A title and optional subtitle/message.
      59              : /// - A progress bar.
      60              : /// - Percentage, network speed, and time remaining (ETA).
      61              : /// - Current step information (if available).
      62              : ///
      63              : /// Perfect for download managers, file processing screens, or any
      64              : /// task-heavy application.
      65              : class TaskProgressCard extends StatelessWidget {
      66              :   /// The handler for the task to display.
      67              :   final TaskHandler handler;
      68              : 
      69              :   /// An optional title for the card. Defaults to the task ID.
      70              :   final String? title;
      71              : 
      72              :   /// An optional icon to display in the header.
      73              :   final Widget? icon;
      74              : 
      75              :   /// Whether to show the network speed and time remaining.
      76              :   final bool showMetrics;
      77              : 
      78              :   /// Whether to show the current message from the task.
      79              :   final bool showMessage;
      80              : 
      81              :   /// Optional padding for the card content.
      82              :   final EdgeInsetsGeometry padding;
      83              : 
      84            0 :   const TaskProgressCard({
      85              :     super.key,
      86              :     required this.handler,
      87              :     this.title,
      88              :     this.icon,
      89              :     this.showMetrics = true,
      90              :     this.showMessage = true,
      91              :     this.padding = const EdgeInsets.all(16.0),
      92              :   });
      93              : 
      94            0 :   @override
      95              :   Widget build(BuildContext context) {
      96            0 :     final theme = Theme.of(context);
      97              : 
      98            0 :     return TaskProgressBuilder(
      99            0 :       handler: handler,
     100            0 :       builder: (context, progress) {
     101              :         final p = progress;
     102            0 :         final pct = (p?.progress ?? 0) / 100.0;
     103            0 :         final hasMetrics = p?.networkSpeed != null || p?.timeRemaining != null;
     104            0 :         final hasSteps = p?.currentStep != null && p?.totalSteps != null;
     105              : 
     106            0 :         return Card(
     107              :           clipBehavior: Clip.antiAlias,
     108            0 :           child: Padding(
     109            0 :             padding: padding,
     110            0 :             child: Column(
     111              :               crossAxisAlignment: CrossAxisAlignment.start,
     112              :               mainAxisSize: MainAxisSize.min,
     113            0 :               children: [
     114              :                 // Header
     115            0 :                 Row(
     116            0 :                   children: [
     117            0 :                     if (icon != null) ...[
     118            0 :                       icon!,
     119              :                       const SizedBox(width: 12),
     120              :                     ],
     121            0 :                     Expanded(
     122            0 :                       child: Column(
     123              :                         crossAxisAlignment: CrossAxisAlignment.start,
     124            0 :                         children: [
     125            0 :                           Text(
     126            0 :                             title ?? handler.taskId,
     127            0 :                             style: theme.textTheme.titleMedium?.copyWith(
     128              :                               fontWeight: FontWeight.bold,
     129              :                             ),
     130              :                             maxLines: 1,
     131              :                             overflow: TextOverflow.ellipsis,
     132              :                           ),
     133            0 :                           if (showMessage && p?.message != null)
     134            0 :                             Text(
     135            0 :                               p!.message!,
     136            0 :                               style: theme.textTheme.bodySmall,
     137              :                               maxLines: 1,
     138              :                               overflow: TextOverflow.ellipsis,
     139              :                             ),
     140              :                         ],
     141              :                       ),
     142              :                     ),
     143            0 :                     const SizedBox(width: 8),
     144            0 :                     Text(
     145            0 :                       '${(pct * 100).toInt()}%',
     146            0 :                       style: theme.textTheme.titleMedium?.copyWith(
     147            0 :                         color: theme.colorScheme.primary,
     148              :                         fontWeight: FontWeight.bold,
     149              :                       ),
     150              :                     ),
     151              :                   ],
     152              :                 ),
     153              :                 const SizedBox(height: 16),
     154              : 
     155              :                 // Progress Bar
     156            0 :                 LinearProgressIndicator(
     157              :                   value: pct,
     158              :                   minHeight: 8,
     159            0 :                   borderRadius: BorderRadius.circular(4),
     160              :                 ),
     161              : 
     162              :                 // Footer Metrics
     163            0 :                 if (showMetrics && (hasMetrics || hasSteps)) ...[
     164              :                   const SizedBox(height: 12),
     165            0 :                   Row(
     166            0 :                     children: [
     167            0 :                       if (hasSteps) ...[
     168            0 :                         Icon(Icons.layers_outlined,
     169            0 :                             size: 14, color: theme.hintColor),
     170              :                         const SizedBox(width: 4),
     171            0 :                         Text(
     172            0 :                           'Step ${p!.currentStep}/${p.totalSteps}',
     173            0 :                           style: theme.textTheme.labelSmall,
     174              :                         ),
     175              :                         const SizedBox(width: 16),
     176              :                       ],
     177            0 :                       if (p?.networkSpeed != null) ...[
     178            0 :                         Icon(Icons.speed, size: 14, color: theme.hintColor),
     179              :                         const SizedBox(width: 4),
     180            0 :                         Text(
     181            0 :                           p!.networkSpeedHuman,
     182            0 :                           style: theme.textTheme.labelSmall,
     183              :                         ),
     184              :                         const Spacer(),
     185              :                       ],
     186            0 :                       if (p?.timeRemaining != null) ...[
     187            0 :                         Icon(Icons.timer_outlined,
     188            0 :                             size: 14, color: theme.hintColor),
     189              :                         const SizedBox(width: 4),
     190            0 :                         Text(
     191            0 :                           p!.timeRemainingHuman,
     192            0 :                           style: theme.textTheme.labelSmall,
     193              :                         ),
     194              :                       ],
     195              :                     ],
     196              :                   ),
     197              :                 ],
     198              :               ],
     199              :             ),
     200              :           ),
     201              :         );
     202              :       },
     203              :     );
     204              :   }
     205              : }
        

Generated by: LCOV version 2.4-0