From 69b4ae37f87c3a061333a1117f744c48f4cb8141 Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:13:05 +0200 Subject: [PATCH 01/11] fix _createBackgroundOverlay return --- lib/flushbar_route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/flushbar_route.dart b/lib/flushbar_route.dart index dc84df3..1c898ec 100644 --- a/lib/flushbar_route.dart +++ b/lib/flushbar_route.dart @@ -150,7 +150,7 @@ class FlushbarRoute extends OverlayRoute { } if (_filterColorAnimation != null) { - AnimatedBuilder( + return AnimatedBuilder( animation: _filterColorAnimation!, builder: (context, child) { return Container( From 7dfaa3d6e7d5c92a19e7670f93634f591ebcf097 Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:17:01 +0200 Subject: [PATCH 02/11] docs: replace FlatButton with TextButton --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6e2ae2b..c5e1e2e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ backgroundColor | Flushbar background color. Will be ignored if [backgroundGradi leftBarIndicatorColor | If not null, shows a left vertical bar to better indicate the humor of the notification. It is not possible to use it with a [Form] and I do not recommend using it with [LinearProgressIndicator]. boxShadows | The shadows generated by Flushbar. Leave it null if you don't want a shadow. You can use more than one if you feel the need. Check [this example](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/material/shadows.dart) backgroundGradient | Flushbar background gradient. Makes [backgroundColor] be ignored. -mainButton | Use if you need an action from the user. [FlatButton] is recommended here. +mainButton | Use if you need an action from the user. [TextButton] is recommended here. onTap | A callback that registers the user's click anywhere. An alternative to [mainButton] duration | How long until Flushbar will hide itself (be dismissed). To make it indefinite, leave it null. isDismissible | Determines if the user can swipe or click the overlay (if [routeBlur] > 0) to dismiss. It is recommended that you set [duration] != null if this is false. If the user swipes to dismiss or clicks the overlay, no value will be returned. @@ -134,7 +134,7 @@ Flushbar( Icons.check, color: Colors.greenAccent, ), - mainButton: FlatButton( + mainButton: TextButton( onPressed: () {}, child: Text( "CLAP", @@ -282,7 +282,7 @@ bool _wasButtonClicked; icon: Icon( Icons.info_outline, color: Colors.blue,), - mainButton: FlatButton( + mainButton: TextButton( onPressed: () { flush.dismiss(true); // result = true }, From 98fb6c15cebba04c57d87992111849ac962668d8 Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:20:06 +0200 Subject: [PATCH 03/11] docs: update maxLength property --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5e1e2e..977fc12 100644 --- a/README.md +++ b/README.md @@ -428,7 +428,7 @@ TextFormField getFormField(String text) { style: TextStyle(color: Colors.white), maxLength: 100, maxLines: 1, - maxLengthEnforced: true, + maxLengthEnforcement: MaxLengthEnforcement.enforced, decoration: InputDecoration( fillColor: Colors.white10, filled: true, From 42af0e4d170540192bee2d68ca851637483fe3fe Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:22:49 +0200 Subject: [PATCH 04/11] Update test description --- test/another_flushbar_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/another_flushbar_test.dart b/test/another_flushbar_test.dart index ba483f8..d436d8c 100644 --- a/test/another_flushbar_test.dart +++ b/test/another_flushbar_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('Test Flushbar basic inicialization', () async { + test('Test Flushbar basic initialization', () { final flushbar = Flushbar(message: 'This is a test'); expect(flushbar.title, null); expect(flushbar.message, 'This is a test'); From 57fb6a9df83f4caf60539704d318aab5151ccb12 Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:27:30 +0200 Subject: [PATCH 05/11] chore: update flutter_lints --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1877d7a..74029c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter dev_dependencies: - flutter_lints: ^1.0.4 + flutter_lints: ^2.0.0 flutter_test: sdk: flutter From 3e763986dc1ee433297c32493da75819a13eaf50 Mon Sep 17 00:00:00 2001 From: hamidwakili <90334111+hamidwakili@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:29:47 +0200 Subject: [PATCH 06/11] Add newline at EOF for gitignore and analysis options --- .gitignore | 2 +- analysis_options.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0cb8159..776b32a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ build/ -.idea/ \ No newline at end of file +.idea/ diff --git a/analysis_options.yaml b/analysis_options.yaml index 447e0aa..292f58a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,4 @@ include: package:flutter_lints/flutter.yaml linter: rules: - constant_identifier_names: false \ No newline at end of file + constant_identifier_names: false From 63dfaeb15bc06bb31e313dff78bee700d158fc3c Mon Sep 17 00:00:00 2001 From: Hamid Wakili Date: Mon, 1 Sep 2025 11:32:54 +0200 Subject: [PATCH 07/11] update read me and bump version --- CHANGELOG.md | 4 +- README.md | 12 ++ .../plugins/GeneratedPluginRegistrant.java | 24 ++-- android/local.properties | 4 +- example/pubspec.lock | 103 ++++++++++------- pubspec.lock | 109 +++++++++++------- pubspec.yaml | 4 +- 7 files changed, 160 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea20e85..1839cf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.12.31 - 2025-09-01 +- minor improvements ## 1.12.30 - 2023-11-05 - bump version ## 1.12.30-dev.1 - 2023-09-05 @@ -373,4 +375,4 @@ smooth content transition - Single button action - Status listeners - Left or right icon positioning -- Top or bottom positioning \ No newline at end of file +- Top or bottom positioning diff --git a/README.md b/README.md index 977fc12..f86393d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,18 @@ toasts and snackbars. IOS developers, I don't know what you use there, but you w See the [install instructions](https://pub.dev/packages/another_flushbar/install). +## ❤️ Support & Contact + +Maintaining open-source takes time and energy. +If this library has helped you, consider supporting my work: + +- ☕ [Buy Me a Coffee](https://buymeacoffee.com/hamidwakili) + +For questions, feedback, or collaboration: + +- 💼 [LinkedIn – Hamid Wakili](https://www.linkedin.com/in/hamid-wakili/) + + ## Quick reference Since customization requires a lot of properties, here is a quick cheatsheet: diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index d007606..539ab02 100644 --- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -1,23 +1,19 @@ package io.flutter.plugins; -import io.flutter.plugin.common.PluginRegistry; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import io.flutter.Log; + +import io.flutter.embedding.engine.FlutterEngine; /** * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Android platform. */ +@Keep public final class GeneratedPluginRegistrant { - public static void registerWith(PluginRegistry registry) { - if (alreadyRegisteredWith(registry)) { - return; - } - } - - private static boolean alreadyRegisteredWith(PluginRegistry registry) { - final String key = GeneratedPluginRegistrant.class.getCanonicalName(); - if (registry.hasPlugin(key)) { - return true; - } - registry.registrarFor(key); - return false; + private static final String TAG = "GeneratedPluginRegistrant"; + public static void registerWith(@NonNull FlutterEngine flutterEngine) { } } diff --git a/android/local.properties b/android/local.properties index 9673760..e1be8e6 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,2 +1,2 @@ -sdk.dir=/Users/hamidwakili/Library/Android/sdk -flutter.sdk=/Users/hamidwakili/flutter \ No newline at end of file +sdk.dir=/Users/hamid.wakili/Library/Android/sdk +flutter.sdk=/Users/hamid.wakili/development/flutter \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index 283816a..e4819ef 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,47 +7,47 @@ packages: path: ".." relative: true source: path - version: "1.12.30-dev.1" + version: "1.12.31" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -60,10 +60,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -74,99 +74,115 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.4" vector_math: dependency: transitive description: @@ -175,5 +191,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.lock b/pubspec.lock index eee9cd9..3d26823 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -58,116 +58,132 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.dev" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.1.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.4" vector_math: dependency: transitive description: @@ -176,5 +192,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 74029c5..6672b95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: another_flushbar description: A flexible widget for user notification. Customize your text, button, duration, animations and much more. For Android devs, it is made to replace Snackbars and Toasts. -version: 1.12.30 +version: 1.12.31 homepage: https://github.com/cmdrootaccess/another-flushbar environment: @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - + dev_dependencies: flutter_lints: ^2.0.0 flutter_test: From 34c355e353cc17e6b70e3f40460879b35f85b3f4 Mon Sep 17 00:00:00 2001 From: Hamid Wakili Date: Mon, 1 Sep 2025 11:44:59 +0200 Subject: [PATCH 08/11] format flushbar_route.dart --- lib/flushbar_route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/flushbar_route.dart b/lib/flushbar_route.dart index 1c898ec..98a6308 100644 --- a/lib/flushbar_route.dart +++ b/lib/flushbar_route.dart @@ -85,7 +85,7 @@ class FlushbarRoute extends OverlayRoute { ); } - Widget child = flushbar.isDismissible + Widget child = flushbar.isDismissible ? _getDismissibleFlushbar(_builder) : _getFlushbar(); From d0f23dbd8050e7417fe97103bc1aeced3bf76537 Mon Sep 17 00:00:00 2001 From: Hamid Wakili Date: Mon, 8 Sep 2025 15:12:35 +0200 Subject: [PATCH 09/11] Prevent crashes from null checks in Flushbar route lifecycle (isDismissed, isAppearing, isHiding) --- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 6 +++--- example/ios/Runner.xcodeproj/project.pbxproj | 8 ++++---- .../xcshareddata/xcschemes/Runner.xcscheme | 3 ++- example/ios/Runner/AppDelegate.swift | 2 +- lib/flushbar.dart | 8 ++++---- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..8c6e561 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 88359b2..279576f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3b77559..287be12 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: Flutter SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 -PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 +PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.11.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index be5644e..b4ee83d 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -325,7 +325,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -399,7 +399,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -448,7 +448,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6..4f74653 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/lib/flushbar.dart b/lib/flushbar.dart index 43c38ab..6397cf6 100644 --- a/lib/flushbar.dart +++ b/lib/flushbar.dart @@ -265,7 +265,7 @@ class Flushbar extends StatefulWidget { if (flushbarRoute == null) { return false; } - return flushbarRoute!.currentStatus == FlushbarStatus.SHOWING; + return flushbarRoute?.currentStatus == FlushbarStatus.SHOWING; } /// Checks if the flushbar is dismissed @@ -273,21 +273,21 @@ class Flushbar extends StatefulWidget { if (flushbarRoute == null) { return false; } - return flushbarRoute!.currentStatus == FlushbarStatus.DISMISSED; + return flushbarRoute?.currentStatus == FlushbarStatus.DISMISSED; } bool isAppearing() { if (flushbarRoute == null) { return false; } - return flushbarRoute!.currentStatus == FlushbarStatus.IS_APPEARING; + return flushbarRoute?.currentStatus == FlushbarStatus.IS_APPEARING; } bool isHiding() { if (flushbarRoute == null) { return false; } - return flushbarRoute!.currentStatus == FlushbarStatus.IS_HIDING; + return flushbarRoute?.currentStatus == FlushbarStatus.IS_HIDING; } @override From a334e8e2dfbe3d5e531b63f72164093aa399047b Mon Sep 17 00:00:00 2001 From: Hamid Wakili Date: Mon, 8 Sep 2025 15:14:47 +0200 Subject: [PATCH 10/11] bump version --- CHANGELOG.md | 2 ++ pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1839cf5..8021f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.12.32 - 2025-09-08 +- Prevent crashes from null checks in Flushbar route lifecycle (isDismissed, isAppearing, isHiding) ## 1.12.31 - 2025-09-01 - minor improvements ## 1.12.30 - 2023-11-05 diff --git a/pubspec.yaml b/pubspec.yaml index 6672b95..6c961c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: another_flushbar description: A flexible widget for user notification. Customize your text, button, duration, animations and much more. For Android devs, it is made to replace Snackbars and Toasts. -version: 1.12.31 +version: 1.12.32 homepage: https://github.com/cmdrootaccess/another-flushbar environment: From fe51a53515f0f5bb8f5e2fb8fafbc4d465d8e561 Mon Sep 17 00:00:00 2001 From: Hamid Wakili Date: Sun, 10 May 2026 19:40:48 +0200 Subject: [PATCH 11/11] feat: introduce remote notifications --- CHANGELOG.md | 10 + README.md | 730 ++++++++++++++------------------ example/lib/remote_example.dart | 121 ++++++ example/pubspec.lock | 34 +- lib/another_flushbar.dart | 5 + lib/flushbar_remote.dart | 281 ++++++++++++ pubspec.lock | 32 ++ pubspec.yaml | 6 +- 8 files changed, 811 insertions(+), 408 deletions(-) create mode 100644 example/lib/remote_example.dart create mode 100644 lib/another_flushbar.dart create mode 100644 lib/flushbar_remote.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 8021f64..1d373cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.0.1 - 2026-05-05 +- fix: SSE connection now pauses when app is backgrounded and resumes when app returns to foreground. Prevents unnecessary battery drain. + +## 2.0.0 - 2026-05-04 +- Add `FlushbarRemote` for optional remote notification support via FlushKit (api.flushkit.dev) +- Add `FlushbarRemoteEvent` data class for raw event access +- Add `lib/another_flushbar.dart` convenience barrel export +- Add `http: ^1.2.0` dependency +- No breaking changes to existing API + ## 1.12.32 - 2025-09-08 - Prevent crashes from null checks in Flushbar route lifecycle (isDismissed, isAppearing, isHiding) ## 1.12.31 - 2025-09-01 diff --git a/README.md b/README.md index f86393d..751e20d 100644 --- a/README.md +++ b/README.md @@ -1,530 +1,450 @@ # another_flushbar -[![Pub](https://img.shields.io/pub/v/another_flushbar.svg?style=popout)](https://pub.dartlang.org/packages/another_flushbar) - -Use this package if you need more customization when notifying your user. For Android developers, it is made to substitute -toasts and snackbars. IOS developers, I don't know what you use there, but you will like it. - -See the [install instructions](https://pub.dev/packages/another_flushbar/install). - -## ❤️ Support & Contact - -Maintaining open-source takes time and energy. -If this library has helped you, consider supporting my work: - -- ☕ [Buy Me a Coffee](https://buymeacoffee.com/hamidwakili) - -For questions, feedback, or collaboration: - -- 💼 [LinkedIn – Hamid Wakili](https://www.linkedin.com/in/hamid-wakili/) - - -## Quick reference - -Since customization requires a lot of properties, here is a quick cheatsheet: - -Property | What does it do --------- | --------------- -title | The title displayed to the user -titleColor | The title color displayed to the user -titleSize | The title size displayed to the user -message | The message displayed to the user. -messageColor | The message color displayed to the user. -messageSize | The message size displayed to the user. -titleText | Replaces [title]. Although this accepts a [widget], **it is meant to receive [Text] or [RichText]** -messageText | Replaces [message]. Although this accepts a [widget], **it is meant to receive [Text] or [RichText]** -icon | You can use any widget here, but I recommend [Icon] or [Image] as indication of what kind of message you are displaying. Other widgets may break the layout -shouldIconPulse | An option to animate the icon (if present). Defaults to true. -maxWidth | Used to limit Flushbar width (usually on large screens) -margin | Adds a custom margin to Flushbar -padding | Adds a custom padding to Flushbar. The default follows material design guide line -borderRadius | Adds a radius to specified corners of Flushbar. Best combined with [margin]. I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor] -textDirection | [TextDirection.ltr] by default. [Directionality.of(context)] to know whether it would be [TextDirection.ltr] or [TextDirection.rtl] -borderColor | Adds a border to every side of Flushbar. I do not recommend using it with [showProgressIndicator] or [leftBarIndicatorColor] -borderWidth | Changes the width of the border if [borderColor] is specified -backgroundColor | Flushbar background color. Will be ignored if [backgroundGradient] is not null. -leftBarIndicatorColor | If not null, shows a left vertical bar to better indicate the humor of the notification. It is not possible to use it with a [Form] and I do not recommend using it with [LinearProgressIndicator]. -boxShadows | The shadows generated by Flushbar. Leave it null if you don't want a shadow. You can use more than one if you feel the need. Check [this example](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/material/shadows.dart) -backgroundGradient | Flushbar background gradient. Makes [backgroundColor] be ignored. -mainButton | Use if you need an action from the user. [TextButton] is recommended here. -onTap | A callback that registers the user's click anywhere. An alternative to [mainButton] -duration | How long until Flushbar will hide itself (be dismissed). To make it indefinite, leave it null. -isDismissible | Determines if the user can swipe or click the overlay (if [routeBlur] > 0) to dismiss. It is recommended that you set [duration] != null if this is false. If the user swipes to dismiss or clicks the overlay, no value will be returned. -dismissDirection | FlushbarDismissDirection.VERTICAL by default. Can also be [FlushbarDismissDirection.HORIZONTAL] in which case both left and right dismiss are allowed. -flushbarPosition | Flushbar can be based on [FlushbarPosition.TOP] or on [FlushbarPosition.BOTTOM] of your screen. [FlushbarPosition.BOTTOM] is the default. -flushbarStyle | Flushbar can be floating or be grounded to the edge of the screen. If grounded, I do not recommend using [margin] or [borderRadius]. [FlushbarStyle.FLOATING] is the default -forwardAnimationCurve | The [Curve] animation used when show() is called. [Curves.easeOut] is default. -reverseAnimationCurve | The [Curve] animation used when dismiss() is called. [Curves.fastOutSlowIn] is default. -animationDuration | Use it to speed up or slow down the animation duration -showProgressIndicator | true if you want to show a [LinearProgressIndicator]. If [progressIndicatorController] is null, an infinite progress indicator will be shown -progressIndicatorController | An optional [AnimationController] when you want to control the progress of your [LinearProgressIndicator]. You are responsible for controlling the progress -progressIndicatorBackgroundColor | a [LinearProgressIndicator] configuration parameter. -progressIndicatorValueColor | a [LinearProgressIndicator] configuration parameter. -barBlur | Default is 0.0. If different than 0.0, blurs only Flushbar's background. To take effect, make sure your [backgroundColor] has some opacity. The greater the value, the greater the blur. -blockBackgroundInteraction | Determines if user can interact with the screen behind it. If this is false, [routeBlur] and [routeColor] will be ignored -routeBlur | Default is 0.0. If different than 0.0, creates a blurred overlay that prevents the user from interacting with the screen. The greater the value, the greater the blur. It does not take effect if [blockBackgroundInteraction] is false -routeColor | Default is [Colors.transparent]. Only takes effect if [routeBlur] > 0.0. Make sure you use a color with transparency e.g. `Colors.grey[600].withOpacity(0.2)`. It does not take effect if [blockBackgroundInteraction] is false -userInputForm | A [TextFormField] in case you want a simple user input. Every other widget is ignored if this is not null. -onStatusChanged | a callback for you to listen to the different Flushbar status - -#### Quick tip - -If you use a lot of those properties, it makes sense to make a factory to help with your Flushbar's base appearance. -Things like shadows, padding, margins, text styles usually don't change within the app. Take a look at FlushbarHelper class and use it as an example. - -## We are on YouTube! - -While studying Flutter I stumbled on two amazing tutorials on how to use Flushbar. -Make sure you show those guys some love. -1. A beginners [tutorial](https://www.youtube.com/watch?v=KNpxyyA8MDA) by **Matej Rešetár** -2. A more [advanced usage](https://www.youtube.com/watch?v=FRCvqkyeCzQ) by **Javier González Rodríguez** - -## Getting Started - -The examples bellow were updated for version 1.3.0. Changes might have been made. See the [changelog](CHANGELOG.md) if any of the examples do not -reflect Flushbar's current state. - -### The possibilities - -![Flushbar Animated](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/flushbar_animated.gif) +[![Pub Version](https://img.shields.io/pub/v/another_flushbar.svg?style=flat-square)](https://pub.dartlang.org/packages/another_flushbar) +[![Pub Likes](https://img.shields.io/pub/likes/another_flushbar?style=flat-square)](https://pub.dev/packages/another_flushbar) +[![Pub Points](https://img.shields.io/pub/points/another_flushbar?style=flat-square)](https://pub.dev/packages/another_flushbar) -### A basic Flushbar +A highly customizable Flutter notification bar for Android and iOS. +More flexible than a SnackBar. More native than a Toast. + +--- -The most basic Flushbar uses only a message. Failing to provide it before you call `show()` will result in a runtime error. -`Duration`, if not provided, will create an infinite Flushbar, only dismissible by code, back button clicks, or a drag (case `isDismissible` is set to `true`). +## 🚀 New in v2.0.0 — Remote notifications via FlushKit -- Note that only `message` is a required parameter. All the other ones are optional +Trigger Flushbar notifications **from your server** — without rebuilding or redeploying your app. ```dart -class YourAwesomeApp extends StatelessWidget { +// One line in your app +FlushbarRemote.init( + apiKey: 'fk_live_••••••••', + context: context, +); +``` - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'YourAwesomeApp', - home: Scaffold( - Container( - child: Center( - child: MaterialButton( - onPressed: (){ - Flushbar( - title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - duration: Duration(seconds: 3), - )..show(context); - }, - ), - ), - ), - ), - ); - } -} +```bash +# Trigger from anywhere — your backend, a cron job, or curl +curl -X POST https://api.flushkit.dev/v1/notify \ + -H "Authorization: Bearer fk_live_••••••••" \ + -H "Content-Type: application/json" \ + -d '{"message": "Order shipped!", "position": "top"}' ``` -![Basic Example](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/basic_bar.png) +**[Get a free API key at flushkit.dev →](https://flushkit.dev)** -### Lets get crazy Flushbar +- ✅ Real-time delivery via SSE — under 100ms +- ✅ Full Flushbar control — color, position, duration, title +- ✅ Works on Android, iOS, Web, Desktop +- ✅ Auto-reconnect with exponential backoff +- ✅ Battery-aware — pauses when app is backgrounded +- ✅ Free tier available — no credit card required -Here is how customized things can get. +--- -```dart -Flushbar( - title: "Hey Ninja", - titleColor: Colors.white, - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - flushbarPosition: FlushbarPosition.TOP, - flushbarStyle: FlushbarStyle.FLOATING, - reverseAnimationCurve: Curves.decelerate, - forwardAnimationCurve: Curves.elasticOut, - backgroundColor: Colors.red, - boxShadows: [BoxShadow(color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0)], - backgroundGradient: LinearGradient(colors: [Colors.blueGrey, Colors.black]), - isDismissible: false, - duration: Duration(seconds: 4), - icon: Icon( - Icons.check, - color: Colors.greenAccent, - ), - mainButton: TextButton( - onPressed: () {}, - child: Text( - "CLAP", - style: TextStyle(color: Colors.amber), - ), - ), - showProgressIndicator: true, - progressIndicatorBackgroundColor: Colors.blueGrey, - titleText: Text( - "Hello Hero", - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 20.0, color: Colors.yellow[600], fontFamily: "ShadowsIntoLightTwo"), - ), - messageText: Text( - "You killed that giant monster in the city. Congratulations!", - style: TextStyle(fontSize: 18.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"), - ), - ); +## Installation + +```yaml +dependencies: + another_flushbar: ^2.0.0 ``` -![Complete Example](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/complete_bar.png) +See full [install instructions](https://pub.dev/packages/another_flushbar/install). -- Don't forget to call `show()` or the bar will stay hidden. -- To deactivate any of those properties, pass `null` to it. +--- -### Styles -Flushbar can be either floating or grounded to the edge of the screen. -I don't recommend using `margin` or `borderRadius` if you chose `FlushbarStyle.GROUNDED` style. +## FlushKit — Remote Notifications + +### Setup + +Add `FlushbarRemote.init()` once in your root widget's `initState()`, after the first frame is built: ```dart -Flushbar(flushbarStyle: FlushbarStyle.FLOATING) +import 'package:another_flushbar/another_flushbar.dart'; + +class _RootState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + FlushbarRemote.init( + apiKey: 'fk_live_••••••••', + context: context, + ); + }); + } + + @override + void dispose() { + FlushbarRemote.dispose(); + super.dispose(); + } +} ``` -or + +### Optional: Listen to raw events + ```dart -Flushbar(flushbarStyle: FlushbarStyle.GROUNDED) +FlushbarRemote.events.listen((event) { + debugPrint('Received: ${event.message}'); + // Custom handling in addition to automatic display +}); ``` -Floating Style | Grounded Style -:------------------------------------------------------:|:-------------------------------------------------------: -![Floating Style](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/floating_style.png) | ![Grounded Style](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/grounded_style.png) -### Padding and Border Radius -You can give it some padding and a border radius. Works best with `FlushbarStyle.FLOATING` +### FlushbarRemote.init — parameters + +| Parameter | Type | Required | Description | +|---|---|---|---| +| `apiKey` | `String` | ✅ | Your FlushKit project API key | +| `context` | `BuildContext` | ✅ | Used to show the Flushbar overlay | +| `baseUrl` | `String?` | — | Override API URL (default: `api.flushkit.dev`) | + +### Listening to events + +To handle events beyond the automatic Flushbar display, +listen to the events stream after calling `init()`: ```dart -Flushbar( - margin: EdgeInsets.all(8), - borderRadius: BorderRadius.circular(8), +FlushbarRemote.init( +apiKey: 'fk_live_••••••••', +context: context, ); +FlushbarRemote.events.listen((event) { +debugPrint('Received: ${event.message}'); +analytics.track('notification_received'); +}); ``` -![Padding and Radius](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/padding_and_radius.png) -### Left indicator bar +### Delivery behaviour + +FlushKit delivers to **active users** — devices with your app open or recently backgrounded. + +| App state | Delivery | +|---|---| +| App open (foreground) | ✅ Instant | +| App backgrounded | ✅ On resume | +| App fully closed | ❌ Not delivered | + +> **[Full documentation at flushkit.dev/docs →](https://flushkit.dev/docs)** + +--- + +## Local Flushbar Usage + +### Quick reference + +| Property | Description | +|---|---| +| `title` | Title text | +| `titleColor` | Title color | +| `titleSize` | Title font size | +| `message` | Message text (required) | +| `messageColor` | Message color | +| `messageSize` | Message font size | +| `titleText` | Replaces `title` — accepts any widget | +| `messageText` | Replaces `message` — accepts any widget | +| `icon` | Widget shown on the left (Icon or Image recommended) | +| `shouldIconPulse` | Animate the icon. Default: `true` | +| `maxWidth` | Limit Flushbar width on large screens | +| `margin` | Custom margin | +| `padding` | Custom padding | +| `borderRadius` | Corner radius (best with `margin`) | +| `textDirection` | LTR/RTL. Use `Directionality.of(context)` for RTL | +| `borderColor` | Border color | +| `borderWidth` | Border width | +| `backgroundColor` | Background color (ignored if `backgroundGradient` set) | +| `backgroundGradient` | Background gradient | +| `leftBarIndicatorColor` | Colored left bar indicator | +| `boxShadows` | Custom shadows | +| `mainButton` | Action button (TextButton recommended) | +| `onTap` | Tap callback (alternative to `mainButton`) | +| `duration` | Auto-dismiss duration. `null` = infinite | +| `isDismissible` | Allow swipe to dismiss. Default: `true` | +| `dismissDirection` | VERTICAL (default) or HORIZONTAL | +| `flushbarPosition` | TOP or BOTTOM (default) | +| `flushbarStyle` | FLOATING (default) or GROUNDED | +| `forwardAnimationCurve` | Show animation curve. Default: `Curves.easeOut` | +| `reverseAnimationCurve` | Dismiss animation curve. Default: `Curves.fastOutSlowIn` | +| `animationDuration` | Animation speed | +| `showProgressIndicator` | Show a LinearProgressIndicator | +| `progressIndicatorController` | Control progress manually | +| `progressIndicatorBackgroundColor` | Progress bar background | +| `progressIndicatorValueColor` | Progress bar value color | +| `barBlur` | Blur Flushbar background. Default: `0.0` | +| `blockBackgroundInteraction` | Block interaction behind Flushbar | +| `routeBlur` | Blur overlay behind Flushbar | +| `routeColor` | Overlay color (requires `routeBlur > 0`) | +| `userInputForm` | Embed a TextFormField | +| `onStatusChanged` | Status change callback | + +> **Tip:** Use `FlushbarHelper` for common patterns — success, error, info, loading. + +--- -Flushbar has a lateral bar to better convey the humor of the notification. To use it, simple give `leftBarIndicatorColor` a color. +### A basic Flushbar ```dart Flushbar( + title: "Hey Ninja", message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - icon: Icon( - Icons.info_outline, - size: 28.0, - color: Colors.blue[300], - ), duration: Duration(seconds: 3), - leftBarIndicatorColor: Colors.blue[300], )..show(context); ``` -![Left indicator example](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/left_bar_indicator.png) - -### Customize your text +![Basic Example](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/basic_bar.png) -If you need a more fancy text, you can use [Text](https://docs.flutter.io/flutter/widgets/Text-class.html) or [RichText](https://api.flutter.dev/flutter/widgets/RichText-class.html) -and pass it to the `titleText` or `messageText` variables. +--- -- Note that `title` will be ignored if `titleText` is not `null` -- Note that `message` will be ignored if `messageText` is not `null` +### Fully customized ```dart Flushbar( - title: "Hey Ninja", //ignored since titleText != null - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", //ignored since messageText != null - titleText: Text("Hello Hero", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0 color: Colors.yellow[600], fontFamily:"ShadowsIntoLightTwo"),), - messageText: Text("You killed that giant monster in the city. Congratulations!", style: TextStyle(fontSize: 16.0, color: Colors.green[fontFamily: "ShadowsIntoLightTwo"),), + title: "Hey Ninja", + titleColor: Colors.white, + message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", + flushbarPosition: FlushbarPosition.TOP, + flushbarStyle: FlushbarStyle.FLOATING, + reverseAnimationCurve: Curves.decelerate, + forwardAnimationCurve: Curves.elasticOut, + backgroundColor: Colors.red, + boxShadows: [BoxShadow(color: Colors.blue[800]!, offset: Offset(0.0, 2.0), blurRadius: 3.0)], + backgroundGradient: LinearGradient(colors: [Colors.blueGrey, Colors.black]), + isDismissible: false, + duration: Duration(seconds: 4), + icon: Icon(Icons.check, color: Colors.greenAccent), + mainButton: TextButton( + onPressed: () {}, + child: Text("CLAP", style: TextStyle(color: Colors.amber)), + ), + showProgressIndicator: true, + progressIndicatorBackgroundColor: Colors.blueGrey, + titleText: Text( + "Hello Hero", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + color: Colors.yellow[600], + fontFamily: "ShadowsIntoLightTwo", + ), + ), + messageText: Text( + "You killed that giant monster in the city. Congratulations!", + style: TextStyle(fontSize: 18.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"), + ), )..show(context); ``` -![Customized Text](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/text_bar.png) +![Complete Example](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/complete_bar.png) -### Customize background and shadow +--- -You can paint the background with any color you want. You can use any shadow you want. -Just give it a `backgroundColor` and `boxShadows`. +### Styles + +```dart +Flushbar(flushbarStyle: FlushbarStyle.FLOATING) // default +Flushbar(flushbarStyle: FlushbarStyle.GROUNDED) +``` + +Floating Style | Grounded Style +:---:|:---: +![Floating](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/floating_style.png) | ![Grounded](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/grounded_style.png) + +--- + +### Padding and border radius ```dart Flushbar( - title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - backgroundColor: Colors.red, - boxShadows: [BoxShadow(color: Colors.red[800], offset: Offset(0.0, 2.0), blurRadius: 3.0,)], + margin: EdgeInsets.all(8), + borderRadius: BorderRadius.circular(8), )..show(context); ``` -![Background and Shadow](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/background_color_bar.png) +![Padding and Radius](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/padding_and_radius.png) -Want a gradient in the background? No problem. +--- -- Note that `backgroundColor` will be ignored while `backgroundGradient` is not `null` +### Left indicator bar ```dart Flushbar( - title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - backgroundGradient: LinearGradient(colors: [Colors.blue, Colors.teal]), - backgroundColor: Colors.red, - boxShadows: [BoxShadow(color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0,)], + message: "Lorem Ipsum is simply dummy text", + icon: Icon(Icons.info_outline, size: 28.0, color: Colors.blue[300]), + duration: Duration(seconds: 3), + leftBarIndicatorColor: Colors.blue[300], )..show(context); ``` -![Background Gradient](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/gradient_bar.png) +![Left Indicator](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/left_bar_indicator.png) -### Icon and button action +--- -Let us put a Icon that has a `PulseAnimation`. Icons have this animation by default and cannot be changed as of now. -Also, let us put a button. Have you noticed that `show()` returns a `Future`? -This Future will yield a value when you call `dismiss([T result])`. -I recommend that you specify the `result` generic type if you intend to collect an user input. +### Custom text ```dart -Flushbar flush; -bool _wasButtonClicked; +Flushbar( + titleText: Text( + "Hello Hero", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + color: Colors.yellow[600], + fontFamily: "ShadowsIntoLightTwo", + ), + ), + messageText: Text( + "You killed that giant monster in the city. Congratulations!", + style: TextStyle(fontSize: 16.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"), + ), +)..show(context); ``` +![Custom Text](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/text_bar.png) + +--- + +### Background color and gradient + ```dart -@override - Widget build(BuildContext context) { - return Container( - child: Center( - child: MaterialButton( - onPressed: () { - flush = Flushbar( - title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - icon: Icon( - Icons.info_outline, - color: Colors.blue,), - mainButton: TextButton( - onPressed: () { - flush.dismiss(true); // result = true - }, - child: Text( - "ADD", - style: TextStyle(color: Colors.amber), - ), - ),) // is the type of the result passed to dismiss() and collected by show().then((result){}) - ..show(context).then((result) { - setState(() { // setState() is optional here - _wasButtonClicked = result; - }); - }); - }, - ), - ), - ); - } +// Solid color +Flushbar( + title: "Hey Ninja", + message: "Lorem Ipsum", + backgroundColor: Colors.red, + boxShadows: [BoxShadow(color: Colors.red[800]!, offset: Offset(0.0, 2.0), blurRadius: 3.0)], +)..show(context); + +// Gradient +Flushbar( + title: "Hey Ninja", + message: "Lorem Ipsum", + backgroundGradient: LinearGradient(colors: [Colors.blue, Colors.teal]), + boxShadows: [BoxShadow(color: Colors.blue[800]!, offset: Offset(0.0, 2.0), blurRadius: 3.0)], +)..show(context); ``` -![Icon and Button](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/icon_and_button_bar.png) +![Background Color](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/background_color_bar.png) +![Gradient](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/gradient_bar.png) -### Flushbar position +--- -Flushbar can be at `FlushbarPosition.BOTTOM` or `FlushbarPosition.TOP`. +### Icon and button ```dart -Flushbar( - flushbarPosition: FlushbarPosition.TOP, +Flushbar( title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",)..show(context); + message: "Lorem Ipsum is simply dummy text", + icon: Icon(Icons.info_outline, color: Colors.blue), + mainButton: TextButton( + onPressed: () => flush.dismiss(true), + child: Text("ADD", style: TextStyle(color: Colors.amber)), + ), +)..show(context).then((result) { + // result = true if button tapped +}); ``` -![Bar position](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/position_bar.png) +![Icon and Button](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/icon_and_button_bar.png) -### Duration and dismiss policy +--- -By default, Flushbar is infinite. To set a duration, use the `duration` property. -By default, Flushbar is dismissible by the user. A right or left drag will dismiss it. -Set `isDismissible` to `false` to change this behaviour. +### Position ```dart Flushbar( + flushbarPosition: FlushbarPosition.TOP, title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", - duration: Duration(seconds: 3), - isDismissible: false, + message: "Lorem Ipsum", )..show(context); ``` -### Progress Indicator +![Position](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/position_bar.png) -If you are loading something, use a [LinearProgressIndicator](https://docs.flutter.io/flutter/material/LinearProgressIndicator-class.html) -If you want an undetermined progress indicator, do not set `progressIndicatorController`. -If you want a determined progress indicator, you now have full control over the progress since you own the `AnimationController` +--- -- There is no need to add a listener to your controller just to call `setState(){}`. Once you pass in your controller, `Flushbar` will do this automatically. Just make sure you call `_controller.forward()` +### Progress indicator ```dart - AnimationController _controller = AnimationController( - vsync: this, - duration: Duration(seconds: 3), - ); + vsync: this, + duration: Duration(seconds: 3), +); Flushbar( title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", + message: "Loading...", showProgressIndicator: true, progressIndicatorController: _controller, progressIndicatorBackgroundColor: Colors.grey[800], )..show(context); ``` -### Show and dismiss animation curves +--- -You can set custom animation curves using `forwardAnimationCurve` and `reverseAnimationCurve`. +### Status listener ```dart Flushbar( - forwardAnimationCurve: Curves.decelerate, - reverseAnimationCurve: Curves.easeOut, title: "Hey Ninja", - message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry", -)..show(context); + message: "Lorem Ipsum", +) + ..onStatusChanged = (FlushbarStatus status) { + switch (status) { + case FlushbarStatus.SHOWING: doSomething(); break; + case FlushbarStatus.IS_APPEARING: doSomethingElse(); break; + case FlushbarStatus.IS_HIDING: doSomethingElse(); break; + case FlushbarStatus.DISMISSED: doSomethingElse(); break; + } + } + ..show(context); ``` -### Listen to status updates +--- -You can listen to status update using the `onStatusChanged` property. - -- Note that when you pass a new listener using `onStatusChanged`, it will activate once immediately so you can check in what state the Flushbar is. +### RTL support ```dart - -Flushbar flushbar = Flushbar(title: "Hey Ninja", message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry"); - - flushbar - ..onStatusChanged = (FlushbarStatus status) { - switch (status) { - case FlushbarStatus.SHOWING: - { - doSomething(); - break; - } - case FlushbarStatus.IS_APPEARING: - { - doSomethingElse(); - break; - } - case FlushbarStatus.IS_HIDING: - { - doSomethingElse(); - break; - } - case FlushbarStatus.DISMISSED: - { - doSomethingElse(); - break; - } - } - } - ..show(context); - +Flushbar( + message: "لوريم إيبسوم هو ببساطة نص شكلي", + icon: Icon(Icons.info_outline, size: 28.0, color: Colors.blue[300]), + margin: EdgeInsets.all(6.0), + flushbarStyle: FlushbarStyle.FLOATING, + flushbarPosition: FlushbarPosition.TOP, + textDirection: Directionality.of(context), + borderRadius: BorderRadius.circular(12), + duration: Duration(seconds: 3), + leftBarIndicatorColor: Colors.blue[300], +)..show(context); ``` -### Input text +![RTL](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/rtl_bar.png) -Sometimes we just want a simple user input. Use the property`userInputForm`. +--- -- Note that buttons, messages, and icons will be ignored if `userInputForm != null` -- `dismiss(result)` will yield result. `dismiss()` will yield null. +## FlushbarHelper -```dart -Flushbar> flush; -final GlobalKey _formKey = GlobalKey(); -``` +Shortcut factory methods for common notification types: ```dart -TextFormField getFormField(String text) { - return TextFormField( - initialValue: text, - style: TextStyle(color: Colors.white), - maxLength: 100, - maxLines: 1, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - decoration: InputDecoration( - fillColor: Colors.white10, - filled: true, - icon: Icon( - Icons.label, - color: Colors.grey[500], - ), - border: UnderlineInputBorder(), - helperText: "Helper Text", - helperStyle: TextStyle(color: Colors.grey), - labelText: "Label Text", - labelStyle: TextStyle(color: Colors.grey)), - ); - } - -flush = Flushbar>( - userInputForm = Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - getTextFormField("Initial Value"), - getTextFormField("Initial Value Two"), - ] - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: MaterialButton( - textColor: Colors.amberAccent, - child: Text("SUBMIT"), - onPressed: () { - flush.dismiss([_controller1.value.text, _controller2.value.text]); - }, - ), - ), - ) - ],),), -)..show(context).then((result) { - if (result != null) { - String userInput1 = result[0]; - String userInput2 = result[1]; - } - }); +FlushbarHelper.createSuccess(message: "Done!", title: "Success"); +FlushbarHelper.createError(message: "Something went wrong", title: "Error"); +FlushbarHelper.createInformation(message: "FYI...", title: "Info"); +FlushbarHelper.createLoading(message: "Please wait..."); +FlushbarHelper.createAction(message: "Undo?", title: "Deleted", flatButton: undoButton); +FlushbarHelper.createInputFlushbar(textForm: myForm); ``` -This example tries to mimic the [Material Design style guide](https://material.io/design/components/text-fields.html#anatomy) +--- -![Bar input](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/input_bar.png) +## Video tutorials -### RTL text +1. [Beginner tutorial](https://www.youtube.com/watch?v=KNpxyyA8MDA) by **Matej Rešetár** +2. [Advanced usage](https://www.youtube.com/watch?v=FRCvqkyeCzQ) by **Javier González Rodríguez** -![RTL Text](https://github.com/cmdrootaccess/another-flushbar/raw/main/readme_resources/rtl_bar.png) +--- -You can add `textDirection: Directionality.of(context)` for rtl. - -```dart - Flushbar( -message: "لوريم إيبسوم هو ببساطة نص شكلي يستخدم في صناعة الطباعة والتنضيد", -icon: Icon( -Icons.info_outline, -size: 28.0, -color: Colors.blue[300], -), -margin: EdgeInsets.all(6.0), -flushbarStyle: FlushbarStyle.FLOATING, -flushbarPosition: FlushbarPosition.TOP, -textDirection: Directionality.of(context), -borderRadius: BorderRadius.circular(12), -duration: Duration(seconds: 3), -leftBarIndicatorColor: Colors.blue[300], -)..show(context); -``` - -## Flushbar Helper - -I made a helper class to facilitate the creation of the most common Flushbars. - -```dart -FlushbarHelper.createSuccess({message, title, duration}); -FlushbarHelper.createInformation({message, title, duration}); -FlushbarHelper.createError({message, title, duration}); -FlushbarHelper.createAction({message, title, duration flatButton}); -FlushbarHelper.createLoading({message,linearProgressIndicator, title, duration, progressIndicatorController, progressIndicatorBackgroundColor}); -FlushbarHelper.createInputFlushbar({textForm}); -``` +## Links +- 📦 [pub.dev](https://pub.dev/packages/another_flushbar) +- 🔔 [FlushKit — remote notifications](https://flushkit.dev) +- 📖 [FlushKit docs](https://flushkit.dev/docs) +- 🐛 [GitHub issues](https://github.com/cmdrootaccess/another-flushbar/issues) diff --git a/example/lib/remote_example.dart b/example/lib/remote_example.dart new file mode 100644 index 0000000..e9eb9a9 --- /dev/null +++ b/example/lib/remote_example.dart @@ -0,0 +1,121 @@ +// An end-to-end example showing how to integrate FlushbarRemote into a Flutter +// app. Replace 'YOUR_FLUSHKIT_API_KEY' with a real key from api.flushkit.dev. +// +// Run with: flutter run -t example/remote_example.dart + +import 'package:another_flushbar/another_flushbar.dart'; +import 'package:flutter/material.dart'; + +void main() => runApp(const RemoteExampleApp()); + +class RemoteExampleApp extends StatelessWidget { + const RemoteExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: 'FlushbarRemote Example', + home: RemoteHomePage(), + ); + } +} + +class RemoteHomePage extends StatefulWidget { + const RemoteHomePage({super.key}); + + @override + State createState() => _RemoteHomePageState(); +} + +class _RemoteHomePageState extends State { + FlushbarRemoteEvent? _lastEvent; + + @override + void initState() { + super.initState(); + + // Wait for the first frame so the Navigator is ready to push the Flushbar + // route before we start receiving events. + WidgetsBinding.instance.addPostFrameCallback((_) { + FlushbarRemote.init( + apiKey: 'fk_live_66964da5235433a8fb272fbb11d2c16cf62b952ef501a0379fdaaf53fd5a438d', + context: context, + ); + + // Subscribe to raw events if you need custom handling in addition to + // (or instead of) the automatic Flushbar display. + FlushbarRemote.events.listen((event) { + setState(() => _lastEvent = event); + }); + }); + } + + @override + void dispose() { + // Always call dispose() so the SSE connection and timers are cleaned up. + FlushbarRemote.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('FlushbarRemote Example')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Text( + 'Waiting for remote notifications from FlushKit…', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + const SizedBox(height: 32), + if (_lastEvent != null) ...[ + const Text( + 'Last event (via FlushbarRemote.events stream):', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Card( + color: _lastEvent!.backgroundColor, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (_lastEvent!.title != null) + Text( + _lastEvent!.title!, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + Text( + _lastEvent!.message, + style: const TextStyle(color: Colors.white), + ), + const SizedBox(height: 4), + Text( + 'Position: ${_lastEvent!.position.name} ' + 'Duration: ${_lastEvent!.duration.inSeconds}s', + style: const TextStyle( + color: Colors.white70, + fontSize: 12, + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index e4819ef..83b3a71 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: path: ".." relative: true source: path - version: "1.12.31" + version: "2.0.0" async: dependency: transitive description: @@ -74,6 +74,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -183,6 +199,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -199,6 +223,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sdks: dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/another_flushbar.dart b/lib/another_flushbar.dart new file mode 100644 index 0000000..dc3eba6 --- /dev/null +++ b/lib/another_flushbar.dart @@ -0,0 +1,5 @@ +library another_flushbar; + +export 'flushbar.dart'; +export 'flushbar_helper.dart'; +export 'flushbar_remote.dart'; diff --git a/lib/flushbar_remote.dart b/lib/flushbar_remote.dart new file mode 100644 index 0000000..007519f --- /dev/null +++ b/lib/flushbar_remote.dart @@ -0,0 +1,281 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; + +import 'flushbar.dart'; + +/// A notification payload parsed from a FlushKit SSE message. +class FlushbarRemoteEvent { + /// The optional title shown in the [Flushbar]. + final String? title; + + /// The main notification message. + final String message; + + /// The background colour of the [Flushbar]. + final Color backgroundColor; + + /// How long the [Flushbar] stays visible. + final Duration duration; + + /// Whether the [Flushbar] appears at the [FlushbarPosition.TOP] or + /// [FlushbarPosition.BOTTOM] of the screen. + final FlushbarPosition position; + + const FlushbarRemoteEvent({ + this.title, + required this.message, + required this.backgroundColor, + required this.duration, + required this.position, + }); +} + +/// Provides optional remote notification support for [Flushbar] via the +/// [FlushKit](https://api.flushkit.dev) service. +/// +/// ## Quick start +/// +/// ```dart +/// // In your root widget's initState, after MaterialApp is built: +/// WidgetsBinding.instance.addPostFrameCallback((_) { +/// FlushbarRemote.init(apiKey: 'YOUR_KEY', context: context); +/// }); +/// +/// // In your root widget's dispose(): +/// FlushbarRemote.dispose(); +/// ``` +/// +/// The class connects to the FlushKit SSE endpoint and renders each incoming +/// notification as a [Flushbar]. If the connection drops it reconnects +/// automatically with exponential back-off (2 s → 4 s → … → 30 s cap). +/// +/// The connection is automatically paused when the app is backgrounded and +/// resumed when it returns to the foreground, preventing unnecessary battery +/// drain. +/// +/// For advanced use cases you can subscribe to [events] to receive raw +/// [FlushbarRemoteEvent] objects alongside (or instead of) the default +/// [Flushbar] display. +class FlushbarRemote with WidgetsBindingObserver { + FlushbarRemote._(); + + // Singleton instance used as the WidgetsBindingObserver. + static final FlushbarRemote _instance = FlushbarRemote._(); + + static final StreamController _eventController = + StreamController.broadcast(); + + /// A broadcast [Stream] of every [FlushbarRemoteEvent] received from + /// the FlushKit server. + /// + /// Subscribe here when you need to react to events beyond the default + /// [Flushbar] display — for example, to persist a notification history or + /// update application state. + /// + /// ```dart + /// FlushbarRemote.events.listen((event) { + /// print('Received: ${event.message}'); + /// }); + /// ``` + static Stream get events => _eventController.stream; + + static BuildContext? _context; + static String? _apiKey; + static bool _disposed = true; + static http.Client? _client; + static Timer? _reconnectTimer; + + // --------------------------------------------------------------------------- + // Public API + // --------------------------------------------------------------------------- + + /// Connects to `https://api.flushkit.dev/v1/listen` and starts displaying + /// remote [Flushbar] notifications. + /// + /// Call this **once**, after the root [MaterialApp] has been mounted and a + /// valid [BuildContext] is available. The safest place is inside a + /// `WidgetsBinding.instance.addPostFrameCallback` in `initState`: + /// + /// ```dart + /// @override + /// void initState() { + /// super.initState(); + /// WidgetsBinding.instance.addPostFrameCallback((_) { + /// FlushbarRemote.init(apiKey: 'YOUR_KEY', context: context); + /// }); + /// } + /// ``` + /// + /// * [apiKey] — Your FlushKit API key. + /// * [context] — A [BuildContext] that remains valid for the lifetime of the + /// app (the root widget's context is ideal). + static Future init({ + required String apiKey, + required BuildContext context, + }) async { + _apiKey = apiKey; + _context = context; + _disposed = false; + WidgetsBinding.instance.addObserver(_instance); + _connect(); + } + + /// Pauses or resumes the SSE connection in response to app lifecycle events. + /// + /// The connection is closed when the app is backgrounded + /// ([AppLifecycleState.paused]) and re-established when it returns to the + /// foreground ([AppLifecycleState.resumed]), preventing unnecessary battery + /// drain. + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + _connect(); + break; + case AppLifecycleState.paused: + _disconnect(); + break; + default: + break; + } + } + + /// Tears down the SSE connection and cancels any pending reconnect timer. + /// + /// Call this in the `dispose()` method of the widget that owns the + /// [BuildContext] passed to [init]. + /// + /// ```dart + /// @override + /// void dispose() { + /// FlushbarRemote.dispose(); + /// super.dispose(); + /// } + /// ``` + static void dispose() { + _disposed = true; + WidgetsBinding.instance.removeObserver(_instance); + _disconnect(); + _context = null; + } + + // --------------------------------------------------------------------------- + // Internal helpers + // --------------------------------------------------------------------------- + + static void _connect({int retryDelay = 2}) { + if (_disposed || _apiKey == null) return; + if (_client != null) return; // already connected — guard against duplicates + + _client = http.Client(); + + final uri = Uri.parse( + 'https://api.flushkit.dev/v1/listen?apiKey=${Uri.encodeComponent(_apiKey!)}', + ); + + final request = http.Request('GET', uri) + ..headers['Accept'] = 'text/event-stream' + ..headers['Cache-Control'] = 'no-cache'; + + _client! + .send(request) + .then((response) => _consumeStream(response.stream)) + .catchError((Object _) { + _client = null; // allow the next retry to proceed past the guard + if (!_disposed) _scheduleReconnect(retryDelay); + }); + } + + /// Closes the active connection and cancels any pending reconnect timer. + /// + /// Called automatically when the app is backgrounded, and also by [dispose]. + static void _disconnect() { + _reconnectTimer?.cancel(); + _reconnectTimer = null; + _client?.close(); + _client = null; // null out so the guard in _connect() passes on next call + } + + static Future _consumeStream(http.ByteStream byteStream) async { + try { + await for (final line in byteStream + .transform(utf8.decoder) + .transform(const LineSplitter())) { + if (_disposed) return; + if (line.startsWith('data: ')) { + _handleData(line.substring(6).trim()); + } + } + } catch (_) { + // connection error — fall through to reconnect + } + // If _disconnect() was already called (app backgrounded or disposed), + // _client is null — skip reconnecting. It will be triggered by + // AppLifecycleState.resumed or a fresh init() call. + if (_client == null || _disposed) return; + _client = null; // null out so _connect() guard passes on the next retry + _scheduleReconnect(2); + } + + static void _handleData(String rawJson) { + try { + final map = jsonDecode(rawJson) as Map; + + final title = map['title'] as String?; + final message = (map['message'] as String?) ?? ''; + final bg = _parseColor(map['backgroundColor'] as String? ?? '#303030'); + final secs = (map['durationSeconds'] as num?)?.toInt() ?? 3; + final posStr = (map['position'] as String?)?.toUpperCase(); + final position = + posStr == 'TOP' ? FlushbarPosition.TOP : FlushbarPosition.BOTTOM; + + final event = FlushbarRemoteEvent( + title: title, + message: message, + backgroundColor: bg, + duration: Duration(seconds: secs), + position: position, + ); + + _eventController.add(event); + _showFlushbar(event); + } catch (_) { + // Malformed payload — skip silently so a bad message can't crash the app. + } + } + + static void _showFlushbar(FlushbarRemoteEvent event) { + final ctx = _context; + if (ctx == null || _disposed) return; + Flushbar( + title: event.title, + message: event.message, + backgroundColor: event.backgroundColor, + duration: event.duration, + flushbarPosition: event.position, + ).show(ctx); + } + + /// Parses a CSS hex colour string (`"#FF5733"` or `"FF5733"`) into a + /// Flutter [Color]. Falls back to `Color(0xFF303030)` on parse error. + static Color _parseColor(String hex) { + final cleaned = hex.replaceAll('#', '').trim(); + final padded = cleaned.length == 6 ? 'FF$cleaned' : cleaned; + final value = int.tryParse(padded, radix: 16); + return value != null ? Color(value) : const Color(0xFF303030); + } + + /// Schedules a reconnect after [delaySeconds] seconds and doubles the next + /// retry delay (capped at 30 s) for exponential back-off. + static void _scheduleReconnect(int delaySeconds) { + if (_disposed) return; + _reconnectTimer?.cancel(); + final nextDelay = (delaySeconds * 2).clamp(2, 30); + _reconnectTimer = Timer(Duration(seconds: delaySeconds), () { + if (!_disposed) _connect(retryDelay: nextDelay); + }); + } +} diff --git a/pubspec.lock b/pubspec.lock index 3d26823..ef65958 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -67,6 +67,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -184,6 +200,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -200,6 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sdks: dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 6c961c2..d8ca3ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,8 @@ name: another_flushbar description: A flexible widget for user notification. Customize your text, button, duration, animations and much more. For Android devs, it is made to replace Snackbars and Toasts. -version: 1.12.32 -homepage: https://github.com/cmdrootaccess/another-flushbar +version: 2.0.1 +homepage: https://flushkit.dev +issue_tracker: https://github.com/ideployed/another-flushbar/issues environment: sdk: '>=2.12.0 <4.0.0' @@ -9,6 +10,7 @@ environment: dependencies: flutter: sdk: flutter + http: ^1.2.0 dev_dependencies: flutter_lints: ^2.0.0