diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb1997..650fa62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [1.2.0]- 10.12.2024 +* `completedMsgFuture` support for asynchronous completion messages +* `dispose()` method to properly release resources +* Deprecated old ProgressType values: normal and valuable + + ## [1.1.4] - 09.03.2024 * Added support for `useRootNavigator` parameter in the `ProgressDialog` constructor. diff --git a/README.md b/README.md index c5dbd78..05e7c90 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Progress dialog package for flutter You must add the library as a dependency to your project. ```yaml dependencies: - sn_progress_dialog: ^1.1.4 + sn_progress_dialog: ^1.2.0 ``` You should then run `flutter packages get` diff --git a/example/lib/main.dart b/example/lib/main.dart index b019b84..39c8219 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -33,21 +33,19 @@ class Home extends StatelessWidget { ); for (int i = 0; i <= 100; i++) { /// You don't need to update state, just pass the value. - /// Only value required pd.update(value: i); i++; await Future.delayed(Duration(milliseconds: 100)); } } + /// Shows a progress dialog with a determinate progress bar. _valuableProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); pd.show( max: 100, msg: 'File Downloading...', - - /// Assign the type of progress bar. progressType: ProgressType.valuable, ); for (int i = 0; i <= 100; i++) { @@ -57,50 +55,46 @@ class Home extends StatelessWidget { } } + /// Shows a progress dialog that starts with a preparation message, + /// then switches to a downloading message. _preparingProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); - /// show the state of preparation first. pd.show( max: 100, msg: 'Preparing Download...', - progressType: ProgressType.valuable, + progressType: ProgressType.determinate, ); - /// Added to test late loading starts await Future.delayed(Duration(milliseconds: 3000)); for (int i = 0; i <= 100; i++) { - /// You can indicate here that the download has started. pd.update(value: i, msg: 'File Downloading...'); i++; await Future.delayed(Duration(milliseconds: 100)); } } + /// Shows a customizable progress dialog with a dark theme. _customProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); - - /// show the state of preparation first. pd.show( max: 100, msg: 'Preparing Download...', - progressType: ProgressType.valuable, + progressType: ProgressType.determinate, backgroundColor: Color(0xff212121), progressValueColor: Color(0xff3550B4), progressBgColor: Colors.white70, msgColor: Colors.white, valueColor: Colors.white); - - /// Added to test late loading starts await Future.delayed(Duration(milliseconds: 3000)); for (int i = 0; i <= 100; i++) { - /// You can indicate here that the download has started. pd.update(value: i, msg: 'File Downloading...'); i++; await Future.delayed(Duration(milliseconds: 100)); } } + /// Shows a progress dialog that completes with a custom completion widget. _completedProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); pd.show( @@ -118,6 +112,7 @@ class Home extends StatelessWidget { } } + /// Shows a message-only progress dialog without a progress bar. _onlyMessageProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); pd.show( @@ -126,7 +121,6 @@ class Home extends StatelessWidget { hideValue: true, ); - /** You can update the message value after a certain action **/ await Future.delayed(Duration(milliseconds: 1000)); pd.update(msg: "Almost done..."); @@ -134,23 +128,25 @@ class Home extends StatelessWidget { pd.close(); } + /// Shows a message-only progress dialog that completes automatically. _onlyMessageWithCompletionProgress(context) async { ProgressDialog pd = ProgressDialog(context: context); pd.show( barrierDismissible: true, msg: "Please waiting...", hideValue: true, - completed: Completed(), // Set Completed + completed: Completed(completionDelay: 1500), // Set Completed ); - - /** You can update the message value after a certain action **/ await Future.delayed(Duration(milliseconds: 1000)); + pd.update(msg: "Almost done..."); + + await Future.delayed(Duration(milliseconds: 1000)); /** * if you can't assign value and want to use completed object. You should set the Value to 100. * The dialog will close automatically. * **/ - pd.update(msg: "Almost done...", value: 100); + pd.update(value: 100); } @override diff --git a/example/pubspec.lock b/example/pubspec.lock index b5393de..1cbf686 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -110,7 +110,7 @@ packages: path: ".." relative: true source: path - version: "1.1.4" + version: "1.2.0" source_span: dependency: transitive description: diff --git a/lib/enums/dialog_status.dart b/lib/enums/dialog_status.dart new file mode 100644 index 0000000..00de446 --- /dev/null +++ b/lib/enums/dialog_status.dart @@ -0,0 +1,11 @@ +/// Represents the current status of the dialog. +enum DialogStatus { + /// Dialog is currently displayed + opened, + + /// Dialog has been closed + closed, + + /// Dialog has completed its progress + completed +} diff --git a/lib/enums/progress_types.dart b/lib/enums/progress_types.dart new file mode 100644 index 0000000..9bd5c55 --- /dev/null +++ b/lib/enums/progress_types.dart @@ -0,0 +1,25 @@ +enum ProgressType { + /// Indeterminate progress indicator + @Deprecated( + 'Use indeterminate instead. This will be removed in the next major version.') + normal, + + /// Progress indicator with value + @Deprecated( + 'Use determinate instead. This will be removed in the next major version.') + valuable, + + /// Shows an infinite spinning progress indicator without specific progress value + indeterminate, + + /// Shows progress with specific value (0-100%) + determinate, +} + +extension ProgressTypeHelper on ProgressType { + bool get isIndeterminate => + this == ProgressType.normal || this == ProgressType.indeterminate; + + bool get isDeterminate => + this == ProgressType.valuable || this == ProgressType.determinate; +} diff --git a/lib/enums/value_position.dart b/lib/enums/value_position.dart new file mode 100644 index 0000000..00fe80d --- /dev/null +++ b/lib/enums/value_position.dart @@ -0,0 +1,8 @@ +/// Determines the position of the progress value text. +enum ValuePosition { + /// Center aligned progress value + center, + + /// Right aligned progress value + right +} diff --git a/lib/options/cancel.dart b/lib/options/cancel.dart index fbd430d..0fc25a4 100644 --- a/lib/options/cancel.dart +++ b/lib/options/cancel.dart @@ -1,23 +1,24 @@ import 'package:flutter/material.dart'; class Cancel { - /// [cancelClicked] If you want to execute an action when the dialog is canceled, pass a void function. - // (Default: null) + /// Callback function that will be executed when cancel button is tapped. final GestureTapCallback? cancelClicked; - /// [cancelImage] The default does not contain any value, if the value is assigned another asset image is created. + /// Custom image to use for the cancel button. + /// If not provided, uses default cancel icon. final AssetImage? cancelImage; - /// [cancelImageSize] set cancel image dimensions - // (Default: 15.0) + /// Size of the cancel button image in pixels. + /// Defaults to 15.0. final double cancelImageSize; - /// [cancelImageColor] set cancel image color - // (Default: black) + /// Color to apply to the cancel button image. + /// Defaults to Colors.black. final Color? cancelImageColor; - /// [autoHidden] It hides the cancel button when value and max are equal. - // (Default: true) + /// Whether to automatically hide the cancel button when progress completes. + /// When true, the button will be hidden when progress value equals max value. + /// Defaults to true. final bool autoHidden; Cancel({ diff --git a/lib/options/completed.dart b/lib/options/completed.dart index ba29258..e5e6e2e 100644 --- a/lib/options/completed.dart +++ b/lib/options/completed.dart @@ -1,18 +1,24 @@ import 'package:flutter/material.dart'; class Completed { - /// [completedMsg] Assign the completed Message - // (Default: "completed") - String completedMsg; + /// Future that resolves to the completion message. + /// If provided, this message will override [completedMsg] when resolved. + final Future? completedMsgFuture; - /// [completionDelay] The time the dialog window will wait to close - // (Default: 1500 ms) + /// Message to display when progress completes. + /// Defaults to "Completed !". + final String completedMsg; + + /// Duration to wait before closing the dialog after completion, in milliseconds. + /// Defaults to 1500ms. final int completionDelay; - /// [completedImage] The default does not contain any value, if the value is assigned another asset image is created. + /// Custom image to display when progress completes. + /// If not provided, uses default completion icon. final AssetImage? completedImage; Completed({ + this.completedMsgFuture, this.completedMsg = "Completed !", this.completionDelay = 1500, this.completedImage, diff --git a/lib/progress_dialog.dart b/lib/progress_dialog.dart index 5e4d59d..f72dc9a 100644 --- a/lib/progress_dialog.dart +++ b/lib/progress_dialog.dart @@ -1,50 +1,57 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:sn_progress_dialog/enums/dialog_status.dart'; +import 'package:sn_progress_dialog/enums/progress_types.dart'; +import 'package:sn_progress_dialog/enums/value_position.dart'; import 'package:sn_progress_dialog/options/completed.dart'; import 'package:sn_progress_dialog/options/cancel.dart'; -enum ValuePosition { center, right } - -enum ProgressType { normal, valuable } - -enum DialogStatus { opened, closed, completed } - +/// A customizable progress dialog that displays loading states and completion messages. class ProgressDialog { - /// [_progress] Listens to the value of progress. - // Not directly accessible. + /// Listens to the current progress value. final ValueNotifier _progress = ValueNotifier(0); - /// [_msg] Listens to the msg value. - // Value assignment is done later. + /// Listens to the current message text. final ValueNotifier _msg = ValueNotifier(''); - /// [_dialogIsOpen] Shows whether the dialog is open. - // Not directly accessible. + /// Listens to the completion message. + final ValueNotifier _completedMsg = ValueNotifier(''); + + /// Indicates whether the dialog is currently displayed. bool _dialogIsOpen = false; - /// [_context] Required to show the alert. - // Can only be accessed with the constructor. + /// The build context used to show the dialog. late BuildContext _context; - /// [_onStatusChanged] Keeps track of the current status of the dialog window. - // Value assignment is done later. + /// Callback triggered when dialog status changes. ValueChanged? _onStatusChanged; - /// [_useRootNavigator] open dialog in RootNavigator - // Can only be accessed with the constructor. + /// Whether to show dialog in root navigator. + /// If true, the dialog will be displayed above all other routes. late bool _useRootNavigator; + /// Creates a progress dialog with the given build context. + /// + /// [context] - Required build context for showing the dialog + /// [useRootNavigator] - Whether to show in root navigator. Defaults to true ProgressDialog({required context, bool? useRootNavigator}) { this._context = context; this._useRootNavigator = useRootNavigator ?? true; } - /// [update] Pass the new value to this method to update the status. + /// Updates the dialog's progress value and message. + /// + /// [value] - New progress value between 0 and max + /// [msg] - New message to display void update({int? value, String? msg}) { if (value != null) _progress.value = value; if (msg != null) _msg.value = msg; } - /// [close] Closes the progress dialog. + /// Closes the dialog with optional delay. + /// + /// [delay] - Milliseconds to wait before closing. Defaults to 0. void close({int? delay = 0}) { if (delay == 0 || delay == null) { _closeDialog(); @@ -55,11 +62,20 @@ class ProgressDialog { }); } - ///[isOpen] Returns whether the dialog box is open. + /// Releases resources used by the dialog. + /// Should be called when the dialog is no longer needed. + void dispose() { + _progress.dispose(); + _msg.dispose(); + _completedMsg.dispose(); + } + + /// Returns whether the dialog is currently open. bool isOpen() { return _dialogIsOpen; } + /// Private method to close the dialog and update its status. void _closeDialog() { if (_dialogIsOpen) { Navigator.of(_context, rootNavigator: _useRootNavigator).pop(); @@ -68,13 +84,12 @@ class ProgressDialog { } } - ///[setDialogStatus] Dialog window sets your new state. + /// Private method to notify status change listeners. void _setDialogStatus(DialogStatus status) { if (_onStatusChanged != null) _onStatusChanged!(status); } - /// [_valueProgress] Assigns progress properties and updates the value. - // Not directly accessible. + /// Creates a progress indicator with deterministic value. _valueProgress({Color? valueColor, Color? bgColor, required double value}) { return CircularProgressIndicator( backgroundColor: bgColor, @@ -83,8 +98,7 @@ class ProgressDialog { ); } - /// [_normalProgress] Assigns progress properties. - // Not directly accessible. + /// Creates an indeterminate progress indicator. _normalProgress({Color? valueColor, Color? bgColor}) { return CircularProgressIndicator( backgroundColor: bgColor, @@ -92,42 +106,47 @@ class ProgressDialog { ); } - /// [max] Assign the maximum value of the upload. @required - // Dialog closes automatically when its progress status equals the max value. - - /// [msg] Show a message @required - - /// [valuePosition] Location of progress value @not required - // Center or right. (Default: right) - - /// [progressType] Assign the progress bar type. - // Normal or valuable. (Default: normal) - - /// [barrierDismissible] Determines whether the dialog closes when the back button or screen is clicked. - // True or False (Default: false) - - /// [msgMaxLines] Use when text value doesn't fit - // Int (Default: 1) - - /// [completed] Widgets that will be displayed when the process is completed are assigned through this class. - // If an assignment is not made, the dialog closes without showing anything. - - /// [cancel] Use it to have a close button on the dialog. - // Manage other properties related to cancel button via this class. - - /// [hideValue] If you are not using the progress value, you can hide it. - // Default (Default: false) - - /// [closeWithDelay] The time the dialog window will wait to close - // If the dialog takes the "completion" object, the value here is ignored. - // Default (Default: 100ms) - - show({ + /// Shows the progress dialog with customizable options. + /// + /// Parameters: + /// - [max] Maximum progress value (default: 100). This value determines when the progress is complete. + /// - [msg] Message to display (default: "Default Message"). Can be updated using [update] method. + /// - [completed] Configuration for completion state. Use this to customize completion message, delay, and image. + /// If not provided, dialog will close immediately upon completion. + /// - [cancel] Configuration for cancel button. Provides options for custom image and click handling. + /// - [progressType] Type of progress indicator: + /// * indeterminate: Shows spinning indicator + /// * determinate: Shows actual progress (0-100%) + /// - [valuePosition] Position of progress value text (center/right). Only applies when [hideValue] is false. + /// - [backgroundColor] Dialog background color (default: Colors.white) + /// - [surfaceTintColor] Dialog surface tint color for Material 3 + /// - [barrierColor] Color of the barrier behind the dialog (default: transparent) + /// - [progressValueColor] Color of the progress indicator's fill (default: blueAccent) + /// - [progressBgColor] Background color of the progress track (default: blueGrey) + /// - [valueColor] Color of the progress value text (default: black87) + /// - [msgColor] Color of the message text (default: black87) + /// - [msgTextAlign] Alignment of the message text (default: center) + /// - [msgFontWeight] Font weight of the message (default: bold) + /// - [valueFontWeight] Font weight of the progress value (default: normal) + /// - [valueFontSize] Font size of the progress value in logical pixels (default: 15.0) + /// - [msgFontSize] Font size of the message in logical pixels (default: 17.0) + /// - [msgMaxLines] Maximum lines for message text before ellipsis (default: 1) + /// - [elevation] Dialog elevation in logical pixels (default: 5.0) + /// - [borderRadius] Dialog corner radius in logical pixels (default: 15.0) + /// - [barrierDismissible] Whether clicking outside closes the dialog (default: false) + /// - [hideValue] Whether to hide the progress value text (default: false) + /// - [closeWithDelay] Delay before closing in milliseconds (default: 100) + /// Note: This is ignored if [completed] is provided. + /// - [onStatusChanged] Callback for dialog status changes (opened/closed/completed) + /// + /// The dialog can be updated using the [update] method and closed manually using [close]. + /// Status changes can be monitored through the [onStatusChanged] callback. + Future show({ int max = 100, String msg = "Default Message", Completed? completed, Cancel? cancel, - ProgressType progressType = ProgressType.normal, + ProgressType progressType = ProgressType.indeterminate, ValuePosition valuePosition = ValuePosition.right, Color backgroundColor = Colors.white, Color? surfaceTintColor, @@ -153,6 +172,15 @@ class ProgressDialog { _msg.value = msg; _onStatusChanged = onStatusChanged; _setDialogStatus(DialogStatus.opened); + + if (completed?.completedMsgFuture != null) { + completed!.completedMsgFuture!.then((newMsg) { + _completedMsg.value = newMsg; + }); + } else if (completed != null) { + _completedMsg.value = completed.completedMsg; + } + return showDialog( barrierDismissible: barrierDismissible, barrierColor: barrierColor, @@ -223,7 +251,7 @@ class ProgressDialog { : Container( width: 35.0, height: 35.0, - child: progressType == ProgressType.normal + child: progressType.isIndeterminate ? _normalProgress( bgColor: progressBgColor, valueColor: progressValueColor, @@ -250,18 +278,23 @@ class ProgressDialog { valueListenable: _msg, builder: (BuildContext context, dynamic msgValue, Widget? child) { - return Text( - value == max && completed != null - ? completed.completedMsg - : msgValue, - textAlign: msgTextAlign, - maxLines: msgMaxLines, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: msgFontSize, - color: msgColor, - fontWeight: msgFontWeight, - ), + return ValueListenableBuilder( + valueListenable: _completedMsg, + builder: (context, completedMsgValue, child) { + return Text( + value == max && completed != null + ? completedMsgValue + : msgValue, + textAlign: msgTextAlign, + maxLines: msgMaxLines, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: msgFontSize, + color: msgColor, + fontWeight: msgFontWeight, + ), + ); + }, ); }, ), @@ -295,6 +328,7 @@ class ProgressDialog { onPopInvoked: (didPop) { if (didPop) { _dialogIsOpen = false; + _setDialogStatus(DialogStatus.closed); } }, ), diff --git a/lib/sn_progress_dialog.dart b/lib/sn_progress_dialog.dart index 97d0c0a..4e09add 100644 --- a/lib/sn_progress_dialog.dart +++ b/lib/sn_progress_dialog.dart @@ -4,3 +4,6 @@ library sn_progress_dialog; export 'progress_dialog.dart'; export 'options/completed.dart'; export 'options/cancel.dart'; +export 'enums/progress_types.dart'; +export 'enums/dialog_status.dart'; +export 'enums/value_position.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 32e4e43..e695273 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: sn_progress_dialog description: Customizable progress dialog package for Flutter.(Captures the progress value) -version: 1.1.4 +version: 1.2.0 homepage: https://github.com/emreesen27/Flutter-Progress-Dialog.git environment: