From 5d371c23047a9d8e689f2acffa5658e6827b3dde Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Fri, 23 May 2025 10:41:23 +0530 Subject: [PATCH 01/20] fix: Add fixes for lint errors --- example/pubspec.lock | 114 +++++---- lib/src/api/delete_notification_by_id.dart | 4 +- lib/src/api/fetch_all_notification.dart | 4 +- lib/src/api/read_notification_by_id.dart | 4 +- lib/src/models/notification_model.dart | 2 +- lib/src/utils/common_utils.dart | 4 +- lib/src/widgets/loader_widget.dart | 21 +- lib/src/widgets/media_error_widget.dart | 8 +- lib/src/widgets/siren_inbox.dart | 7 +- pubspec.lock | 270 +++++++++++---------- pubspec.yaml | 6 +- 11 files changed, 243 insertions(+), 201 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 15087ac..a6bf957 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,42 +5,42 @@ 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: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,18 +53,26 @@ packages: dependency: transitive description: name: dio - sha256: "639179e1cc0957779e10dd5b786ce180c477c4c0aca5aaba5d1700fa2e834801" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "5.4.3" + version: "2.1.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 @@ -87,34 +95,34 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -127,26 +135,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.16.0" network_logger: dependency: "direct main" description: @@ -159,10 +167,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sirenapp_flutter_inbox: dependency: "direct main" description: @@ -174,63 +182,63 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + 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: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.4" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -243,10 +251,18 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "13.0.0" + 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.2.0-0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/api/delete_notification_by_id.dart b/lib/src/api/delete_notification_by_id.dart index cc6d356..b73c2e4 100644 --- a/lib/src/api/delete_notification_by_id.dart +++ b/lib/src/api/delete_notification_by_id.dart @@ -22,7 +22,7 @@ class DeleteNotificationById { Future deleteNotificationById({ required String notificationId, }) async { - final _apiPath = + final apiPath = '${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/notifications'; final result = ApiResponse()..isLoading = true; var apiError = Errors.deleteFailedError; @@ -39,7 +39,7 @@ class DeleteNotificationById { } final apiResponse = await api.delete( - path: '$_apiPath/$notificationId', + path: '$apiPath/$notificationId', ); if (apiResponse.statusCode != 0 && apiResponse.data != null) { final deletionStatus = convertJsonToDeletionStatus(apiResponse.data); diff --git a/lib/src/api/fetch_all_notification.dart b/lib/src/api/fetch_all_notification.dart index 584a393..b14f496 100644 --- a/lib/src/api/fetch_all_notification.dart +++ b/lib/src/api/fetch_all_notification.dart @@ -30,7 +30,7 @@ class FetchAllNotifications { String? start, String? end, }) async { - final _apiPath = + final apiPath = '${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/notifications'; final result = ApiResponse()..isLoading = true; var apiError = Errors.notificationFetchFailedError; @@ -68,7 +68,7 @@ class FetchAllNotifications { } final apiResponse = await api.get( - path: '$_apiPath?$queryString', + path: '$apiPath?$queryString', ); if (apiResponse.statusCode != 0 && apiResponse.data != null) { diff --git a/lib/src/api/read_notification_by_id.dart b/lib/src/api/read_notification_by_id.dart index 5c65dd7..6acc8d9 100644 --- a/lib/src/api/read_notification_by_id.dart +++ b/lib/src/api/read_notification_by_id.dart @@ -16,7 +16,7 @@ class ReadNotificationById { }) async { final result = ApiResponse()..isLoading = true; var apiError = Errors.markAsReadFailedError; - final _apiPath = + final apiPath = '${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/notifications'; if (SirenDataProvider.instance.tokenVerificationStatus != Status.SUCCESS) { apiError = SirenDataProvider.instance.getVerificationErrorType(); @@ -30,7 +30,7 @@ class ReadNotificationById { } final apiResponse = await api.patch( - path: '$_apiPath/$notificationId', + path: '$apiPath/$notificationId', data: { 'isRead': true, 'isDelivered': true, diff --git a/lib/src/models/notification_model.dart b/lib/src/models/notification_model.dart index 73ce92d..c0b43cb 100644 --- a/lib/src/models/notification_model.dart +++ b/lib/src/models/notification_model.dart @@ -72,7 +72,7 @@ class MessageData { try { additionalData = json.decode(data?['additionalData'] as String) as Map?; - } catch (error) { + } on FormatException { additionalData = null; } } diff --git a/lib/src/utils/common_utils.dart b/lib/src/utils/common_utils.dart index 04809e2..2b205b7 100644 --- a/lib/src/utils/common_utils.dart +++ b/lib/src/utils/common_utils.dart @@ -1,4 +1,4 @@ -import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter/services.dart' show PlatformException, rootBundle; import 'package:sirenapp_flutter_inbox/src/constants/generics.dart'; /// Generates elapsed time text based on the difference between the target time and the current time. @@ -64,7 +64,7 @@ Future> loadEnv() async { } return envVariables; - } catch (e) { + } on PlatformException { return {}; } } diff --git a/lib/src/widgets/loader_widget.dart b/lib/src/widgets/loader_widget.dart index bc13ffa..2bb364a 100644 --- a/lib/src/widgets/loader_widget.dart +++ b/lib/src/widgets/loader_widget.dart @@ -99,7 +99,7 @@ class CardLoaderWidgetState extends State decoration: BoxDecoration( shape: BoxShape.circle, color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + .withValues(alpha: 0.5 + 0.5 * _controller.value), ), ), ), @@ -117,7 +117,7 @@ class CardLoaderWidgetState extends State height: 18, decoration: BoxDecoration( color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + .withValues(alpha: 0.5 + 0.5 * _controller.value), borderRadius: BorderRadius.circular(4), ), ), @@ -128,7 +128,7 @@ class CardLoaderWidgetState extends State height: 18, decoration: BoxDecoration( color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + .withValues(alpha: 0.5 + 0.5 * _controller.value), borderRadius: BorderRadius.circular(4), ), ), @@ -139,7 +139,7 @@ class CardLoaderWidgetState extends State height: 18, decoration: BoxDecoration( color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + .withValues(alpha: 0.5 + 0.5 * _controller.value), borderRadius: BorderRadius.circular(4), ), ), @@ -153,8 +153,9 @@ class CardLoaderWidgetState extends State height: 10, decoration: BoxDecoration( shape: BoxShape.circle, - color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + color: defaultColors.skeletonLoaderColor.withValues( + alpha: 0.5 + 0.5 * _controller.value, + ), ), ), ), @@ -164,8 +165,10 @@ class CardLoaderWidgetState extends State builder: (context, child) => Container( height: 12, decoration: BoxDecoration( - color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + color: + defaultColors.skeletonLoaderColor.withValues( + alpha: 0.5 + 0.5 * _controller.value, + ), borderRadius: BorderRadius.circular(4), ), ), @@ -183,7 +186,7 @@ class CardLoaderWidgetState extends State height: 16, decoration: BoxDecoration( color: defaultColors.skeletonLoaderColor - .withOpacity(0.5 + 0.5 * _controller.value), + .withValues(alpha: 0.5 + 0.5 * _controller.value), borderRadius: BorderRadius.circular(4), ), ), diff --git a/lib/src/widgets/media_error_widget.dart b/lib/src/widgets/media_error_widget.dart index 4696233..cbb6c11 100644 --- a/lib/src/widgets/media_error_widget.dart +++ b/lib/src/widgets/media_error_widget.dart @@ -1,5 +1,3 @@ -// ignore_for_file: cascade_invocations - import 'package:flutter/material.dart'; import 'package:sirenapp_flutter_inbox/src/theme/app_theme.dart'; @@ -75,16 +73,14 @@ class DiagonalPainter extends CustomPainter { ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; - final lightColorPath = Path(); - lightColorPath + final lightColorPath = Path() ..moveTo( size.width + 2, size.height + 2, ) ..lineTo(-2, -2); - final darkColorPath = Path(); - darkColorPath + final darkColorPath = Path() ..moveTo( size.width + gap, size.height, diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index 84b12b7..d77caa9 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -200,8 +200,11 @@ class _SirenInboxState extends State _initialize(); break; + + // ignore: no_default_cases default: + } } else if (streamResponse.response?.isError ?? false) { widget.onError @@ -501,7 +504,7 @@ class _SirenInboxState extends State } Widget _buildInboxBody( - ScrollController _controller, + ScrollController controller, List data, bool isTabInactive, ) { @@ -527,7 +530,7 @@ class _SirenInboxState extends State onCardClick: widget.onCardClick, onEndReached: onEndReached, onRefresh: onRefresh, - scrollController: _controller, + scrollController: controller, ); } diff --git a/pubspec.lock b/pubspec.lock index 678763c..e1942c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,90 +5,90 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "82.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.4.5" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" 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" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "8.0.0" built_collection: dependency: transitive description: @@ -101,18 +101,18 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.5" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -125,82 +125,90 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" convert: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.1.0" dio: dependency: "direct main" description: name: dio - sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.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" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -231,34 +239,42 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" husky: dependency: "direct dev" description: @@ -271,18 +287,18 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: transitive description: @@ -295,26 +311,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -327,50 +343,50 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.16.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" mockito: dependency: "direct dev" description: name: mockito - sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + sha256: "4546eac99e8967ea91bae633d2ca7698181d008e95fa4627330cf903d573277a" url: "https://pub.dev" source: hosted - version: "5.4.4" + version: "5.4.6" network_image_mock: dependency: "direct dev" description: @@ -383,18 +399,18 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" pool: dependency: transitive description: @@ -407,119 +423,119 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.5.0" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" 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: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.4" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -532,50 +548,58 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: cecd7a0e92978dbece97c255502c8965f2db3439cde5a11f4b2c65f1955911ee + sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "7.0.0" vm_service: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.3.1" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "3.0.3" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 6ba0d33..9c287b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,19 +11,19 @@ keywords: - notifications environment: - sdk: '>=2.17.0 <4.0.0' + sdk: '>=2.17.0' flutter: ">=3.0.0" dependencies: flutter: sdk: flutter - dio: '>=5.2.0 <=5.4.3+1' + dio: '>=5.2.0' dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - very_good_analysis: ^2.4.0 + very_good_analysis: ^7.0.0 husky: ^0.1.7 mockito: ^5.0.0 network_image_mock: ^2.0.1 From 86453b357e92100d478bb6fda1e89bdae0decc1a Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Fri, 23 May 2025 12:50:06 +0530 Subject: [PATCH 02/20] feat: Add fix failing tests --- example/pubspec.lock | 16 +- pubspec.lock | 16 +- test/data/siren_data_provider_test.dart | 49 ++- test/data/siren_data_provider_test.mocks.dart | 260 +++++++++--- test/services/api_client_test.mocks.dart | 369 ++++++++++-------- test/services/network_service_test.mocks.dart | 3 +- test/widgets/siren_inbox_icon_test.mocks.dart | 57 +-- test/widgets/siren_inbox_test.mocks.dart | 57 +-- 8 files changed, 540 insertions(+), 287 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index a6bf957..d3c3527 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -103,10 +103,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -251,10 +251,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index e1942c8..f372df4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" file: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -556,10 +556,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: diff --git a/test/data/siren_data_provider_test.dart b/test/data/siren_data_provider_test.dart index 2b2fe6d..b613030 100644 --- a/test/data/siren_data_provider_test.dart +++ b/test/data/siren_data_provider_test.dart @@ -5,23 +5,38 @@ import 'package:sirenapp_flutter_inbox/sirenapp_flutter_inbox.dart'; import 'package:sirenapp_flutter_inbox/src/api/verify_token.dart'; import 'package:sirenapp_flutter_inbox/src/constants/generics.dart'; import 'package:sirenapp_flutter_inbox/src/data/siren_data_provider.dart'; +import 'package:sirenapp_flutter_inbox/src/models/api_response.dart'; +import 'package:sirenapp_flutter_inbox/src/services/api_client.dart'; import 'siren_data_provider_test.mocks.dart'; @GenerateNiceMocks([ - MockSpec(), + MockSpec(), ]) void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late SirenDataProvider sirenDataProvider; - late MockVerifyToken mockVerifyToken; + late MockApiClient mockApiClient; setUp(() { - sirenDataProvider = SirenDataProvider.instance..initialize(); - mockVerifyToken = MockVerifyToken(); + mockApiClient = MockApiClient(); + sirenDataProvider = SirenDataProvider.instance; + VerifyToken().api = mockApiClient; + sirenDataProvider.initialize(); }); group('SirenDataProvider', () { test('UpdateParams updates user token and recipient ID', () async { + when(mockApiClient.get(path: anyNamed('path'))).thenAnswer( + (_) async => DioResponse( + data: { + 'data': {'status': 'SUCCESS'} + }, + statusCode: 200, + ), + ); + sirenDataProvider.updateParams( userToken: 'token', recipientId: 'recipientId', @@ -32,25 +47,31 @@ void main() { }); test('IconDispose closes icon controller', () { + final controller = sirenDataProvider.iconController; sirenDataProvider.iconDispose(); - - expect(sirenDataProvider.iconController.isClosed, false); + expect(controller.isClosed, true); }); test('InboxDispose closes inbox controller', () { + final controller = sirenDataProvider.inboxController; sirenDataProvider.inboxDispose(); - - expect(sirenDataProvider.inboxController.isClosed, false); + expect(controller.isClosed, true); }); test('Handles retry logic on token verification failure', () async { - final failedResponse = ApiResponse(data: false); - when(mockVerifyToken.verifyToken()) - .thenAnswer((_) async => failedResponse); - - await sirenDataProvider.initialize(); + when(mockApiClient.get(path: anyNamed('path'))).thenAnswer( + (_) async => DioResponse( + data: { + 'data': {'status': 'FAILED'} + }, + statusCode: 401, + ), + ); - sirenDataProvider.updateParams(userToken: 'token', recipientId: '123'); + sirenDataProvider.updateParams( + userToken: 'token', + recipientId: 'recipientId', + ); await Future.delayed( const Duration(seconds: Generics.DATA_FETCH_INTERVAL) * diff --git a/test/data/siren_data_provider_test.mocks.dart b/test/data/siren_data_provider_test.mocks.dart index f4a1d01..91a7927 100644 --- a/test/data/siren_data_provider_test.mocks.dart +++ b/test/data/siren_data_provider_test.mocks.dart @@ -1,15 +1,14 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in sirenapp_flutter_inbox/test/data/siren_data_provider_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; +import 'package:dio/dio.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:sirenapp_flutter_inbox/src/api/verify_token.dart' as _i4; -import 'package:sirenapp_flutter_inbox/src/constants/generics.dart' as _i5; -import 'package:sirenapp_flutter_inbox/src/models/api_response.dart' as _i3; -import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i2; +import 'package:sirenapp_flutter_inbox/src/models/api_response.dart' as _i2; +import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -19,13 +18,14 @@ import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i2; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeApiClient_0 extends _i1.SmartFake implements _i2.ApiClient { - _FakeApiClient_0( +class _FakeDioResponse_0 extends _i1.SmartFake implements _i2.DioResponse { + _FakeDioResponse_0( Object parent, Invocation parentInvocation, ) : super( @@ -34,73 +34,237 @@ class _FakeApiClient_0 extends _i1.SmartFake implements _i2.ApiClient { ); } -class _FakeApiResponse_1 extends _i1.SmartFake implements _i3.ApiResponse { - _FakeApiResponse_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [VerifyToken]. +/// A class which mocks [ApiClient]. /// /// See the documentation for Mockito's code generation for more information. -class MockVerifyToken extends _i1.Mock implements _i4.VerifyToken { +class MockApiClient extends _i1.Mock implements _i3.ApiClient { @override - _i2.ApiClient get api => (super.noSuchMethod( - Invocation.getter(#api), - returnValue: _FakeApiClient_0( - this, - Invocation.getter(#api), + bool isServerError(_i4.Response? response) => (super.noSuchMethod( + Invocation.method( + #isServerError, + [response], ), - returnValueForMissingStub: _FakeApiClient_0( - this, - Invocation.getter(#api), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + + @override + _i5.Future<_i2.DioResponse> get({ + String? path, + Map? queryParameters, + _i4.Options? options, + _i4.CancelToken? cancelToken, + _i4.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, ), - ) as _i2.ApiClient); + returnValue: _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #get, + [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #get, + [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i5.Future<_i2.DioResponse>); @override - set api(_i2.ApiClient? _api) => super.noSuchMethod( - Invocation.setter( - #api, - _api, + _i5.Future<_i2.DioResponse> post({ + String? path, + dynamic data, + Map? queryParameters, + _i4.Options? options, + _i4.CancelToken? cancelToken, + _i4.ProgressCallback? onSendProgress, + _i4.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, ), - returnValueForMissingStub: null, - ); + returnValue: _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #post, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #post, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i5.Future<_i2.DioResponse>); @override - _i5.Status convertJsonToVerificationStatus(dynamic response) => + _i5.Future<_i2.DioResponse> patch({ + String? path, + dynamic data, + Map? queryParameters, + _i4.Options? options, + _i4.CancelToken? cancelToken, + _i4.ProgressCallback? onSendProgress, + _i4.ProgressCallback? onReceiveProgress, + }) => (super.noSuchMethod( Invocation.method( - #convertJsonToVerificationStatus, - [response], + #patch, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, ), - returnValue: _i5.Status.PENDING, - returnValueForMissingStub: _i5.Status.PENDING, - ) as _i5.Status); + returnValue: _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #patch, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( + this, + Invocation.method( + #patch, + [], + { + #path: path, + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i5.Future<_i2.DioResponse>); @override - _i6.Future<_i3.ApiResponse> verifyToken() => (super.noSuchMethod( + _i5.Future<_i2.DioResponse> delete({ + String? path, + Map? queryParameters, + _i4.Options? options, + _i4.CancelToken? cancelToken, + _i4.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( Invocation.method( - #verifyToken, + #delete, [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, ), - returnValue: _i6.Future<_i3.ApiResponse>.value(_FakeApiResponse_1( + returnValue: _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( this, Invocation.method( - #verifyToken, + #delete, [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, ), )), returnValueForMissingStub: - _i6.Future<_i3.ApiResponse>.value(_FakeApiResponse_1( + _i5.Future<_i2.DioResponse>.value(_FakeDioResponse_0( this, Invocation.method( - #verifyToken, + #delete, [], + { + #path: path, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, ), )), - ) as _i6.Future<_i3.ApiResponse>); + ) as _i5.Future<_i2.DioResponse>); } diff --git a/test/services/api_client_test.mocks.dart b/test/services/api_client_test.mocks.dart index b2d0ecc..df124b4 100644 --- a/test/services/api_client_test.mocks.dart +++ b/test/services/api_client_test.mocks.dart @@ -1,20 +1,20 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in sirenapp_flutter_inbox/test/services/api_client_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; +import 'dart:async' as _i8; import 'package:dio/src/adapter.dart' as _i3; import 'package:dio/src/cancel_token.dart' as _i10; -import 'package:dio/src/dio.dart' as _i9; +import 'package:dio/src/dio.dart' as _i7; import 'package:dio/src/dio_mixin.dart' as _i5; import 'package:dio/src/options.dart' as _i2; import 'package:dio/src/response.dart' as _i6; import 'package:dio/src/transformer.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i12; -import 'package:sirenapp_flutter_inbox/sirenapp_flutter_inbox.dart' as _i8; +import 'package:sirenapp_flutter_inbox/sirenapp_flutter_inbox.dart' as _i9; import 'package:sirenapp_flutter_inbox/src/constants/generics.dart' as _i13; import 'package:sirenapp_flutter_inbox/src/data/siren_data_provider.dart' as _i11; @@ -27,6 +27,7 @@ import 'package:sirenapp_flutter_inbox/src/data/siren_data_provider.dart' // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types @@ -83,9 +84,8 @@ class _FakeResponse_4 extends _i1.SmartFake implements _i6.Response { ); } -class _FakeStreamController_5 extends _i1.SmartFake - implements _i7.StreamController { - _FakeStreamController_5( +class _FakeDio_5 extends _i1.SmartFake implements _i7.Dio { + _FakeDio_5( Object parent, Invocation parentInvocation, ) : super( @@ -94,9 +94,20 @@ class _FakeStreamController_5 extends _i1.SmartFake ); } -class _FakeSirenErrorType_6 extends _i1.SmartFake - implements _i8.SirenErrorType { - _FakeSirenErrorType_6( +class _FakeStreamController_6 extends _i1.SmartFake + implements _i8.StreamController { + _FakeStreamController_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSirenErrorType_7 extends _i1.SmartFake + implements _i9.SirenErrorType { + _FakeSirenErrorType_7( Object parent, Invocation parentInvocation, ) : super( @@ -108,7 +119,7 @@ class _FakeSirenErrorType_6 extends _i1.SmartFake /// A class which mocks [Dio]. /// /// See the documentation for Mockito's code generation for more information. -class MockDio extends _i1.Mock implements _i9.Dio { +class MockDio extends _i1.Mock implements _i7.Dio { @override _i2.BaseOptions get options => (super.noSuchMethod( Invocation.getter(#options), @@ -122,15 +133,6 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), ) as _i2.BaseOptions); - @override - set options(_i2.BaseOptions? _options) => super.noSuchMethod( - Invocation.setter( - #options, - _options, - ), - returnValueForMissingStub: null, - ); - @override _i3.HttpClientAdapter get httpClientAdapter => (super.noSuchMethod( Invocation.getter(#httpClientAdapter), @@ -144,16 +146,6 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), ) as _i3.HttpClientAdapter); - @override - set httpClientAdapter(_i3.HttpClientAdapter? _httpClientAdapter) => - super.noSuchMethod( - Invocation.setter( - #httpClientAdapter, - _httpClientAdapter, - ), - returnValueForMissingStub: null, - ); - @override _i4.Transformer get transformer => (super.noSuchMethod( Invocation.getter(#transformer), @@ -167,15 +159,6 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), ) as _i4.Transformer); - @override - set transformer(_i4.Transformer? _transformer) => super.noSuchMethod( - Invocation.setter( - #transformer, - _transformer, - ), - returnValueForMissingStub: null, - ); - @override _i5.Interceptors get interceptors => (super.noSuchMethod( Invocation.getter(#interceptors), @@ -189,6 +172,34 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), ) as _i5.Interceptors); + @override + set options(_i2.BaseOptions? _options) => super.noSuchMethod( + Invocation.setter( + #options, + _options, + ), + returnValueForMissingStub: null, + ); + + @override + set httpClientAdapter(_i3.HttpClientAdapter? _httpClientAdapter) => + super.noSuchMethod( + Invocation.setter( + #httpClientAdapter, + _httpClientAdapter, + ), + returnValueForMissingStub: null, + ); + + @override + set transformer(_i4.Transformer? _transformer) => super.noSuchMethod( + Invocation.setter( + #transformer, + _transformer, + ), + returnValueForMissingStub: null, + ); + @override void close({bool? force = false}) => super.noSuchMethod( Invocation.method( @@ -200,7 +211,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ); @override - _i7.Future<_i6.Response> head( + _i8.Future<_i6.Response> head( String? path, { Object? data, Map? queryParameters, @@ -218,7 +229,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #cancelToken: cancelToken, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #head, @@ -232,7 +243,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #head, @@ -245,10 +256,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> headUri( + _i8.Future<_i6.Response> headUri( Uri? uri, { Object? data, _i2.Options? options, @@ -264,7 +275,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #cancelToken: cancelToken, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #headUri, @@ -277,7 +288,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #headUri, @@ -289,10 +300,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> get( + _i8.Future<_i6.Response> get( String? path, { Object? data, Map? queryParameters, @@ -312,7 +323,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #get, @@ -327,7 +338,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #get, @@ -341,10 +352,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> getUri( + _i8.Future<_i6.Response> getUri( Uri? uri, { Object? data, _i2.Options? options, @@ -362,7 +373,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #getUri, @@ -376,7 +387,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #getUri, @@ -389,10 +400,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> post( + _i8.Future<_i6.Response> post( String? path, { Object? data, Map? queryParameters, @@ -414,7 +425,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #post, @@ -430,7 +441,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #post, @@ -445,10 +456,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> postUri( + _i8.Future<_i6.Response> postUri( Uri? uri, { Object? data, _i2.Options? options, @@ -468,7 +479,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #postUri, @@ -483,7 +494,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #postUri, @@ -497,10 +508,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> put( + _i8.Future<_i6.Response> put( String? path, { Object? data, Map? queryParameters, @@ -522,7 +533,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #put, @@ -538,7 +549,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #put, @@ -553,10 +564,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> putUri( + _i8.Future<_i6.Response> putUri( Uri? uri, { Object? data, _i2.Options? options, @@ -576,7 +587,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #putUri, @@ -591,7 +602,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #putUri, @@ -605,10 +616,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> patch( + _i8.Future<_i6.Response> patch( String? path, { Object? data, Map? queryParameters, @@ -630,7 +641,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #patch, @@ -646,7 +657,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #patch, @@ -661,10 +672,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> patchUri( + _i8.Future<_i6.Response> patchUri( Uri? uri, { Object? data, _i2.Options? options, @@ -684,7 +695,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #patchUri, @@ -699,7 +710,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #patchUri, @@ -713,10 +724,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> delete( + _i8.Future<_i6.Response> delete( String? path, { Object? data, Map? queryParameters, @@ -734,7 +745,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #cancelToken: cancelToken, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #delete, @@ -748,7 +759,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #delete, @@ -761,10 +772,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> deleteUri( + _i8.Future<_i6.Response> deleteUri( Uri? uri, { Object? data, _i2.Options? options, @@ -780,7 +791,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #cancelToken: cancelToken, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #deleteUri, @@ -793,7 +804,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #deleteUri, @@ -805,17 +816,18 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> download( + _i8.Future<_i6.Response> download( String? urlPath, dynamic savePath, { _i2.ProgressCallback? onReceiveProgress, Map? queryParameters, _i10.CancelToken? cancelToken, bool? deleteOnError = true, - String? lengthHeader = r'content-length', + _i2.FileAccessMode? fileAccessMode = _i2.FileAccessMode.write, + String? lengthHeader = 'content-length', Object? data, _i2.Options? options, }) => @@ -831,13 +843,14 @@ class MockDio extends _i1.Mock implements _i9.Dio { #queryParameters: queryParameters, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, }, ), returnValue: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #download, @@ -850,6 +863,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #queryParameters: queryParameters, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, @@ -857,7 +871,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #download, @@ -870,22 +884,24 @@ class MockDio extends _i1.Mock implements _i9.Dio { #queryParameters: queryParameters, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> downloadUri( + _i8.Future<_i6.Response> downloadUri( Uri? uri, dynamic savePath, { _i2.ProgressCallback? onReceiveProgress, _i10.CancelToken? cancelToken, bool? deleteOnError = true, - String? lengthHeader = r'content-length', + _i2.FileAccessMode? fileAccessMode = _i2.FileAccessMode.write, + String? lengthHeader = 'content-length', Object? data, _i2.Options? options, }) => @@ -900,13 +916,14 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, }, ), returnValue: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #downloadUri, @@ -918,6 +935,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, @@ -925,7 +943,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #downloadUri, @@ -937,16 +955,17 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, #cancelToken: cancelToken, #deleteOnError: deleteOnError, + #fileAccessMode: fileAccessMode, #lengthHeader: lengthHeader, #data: data, #options: options, }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> request( + _i8.Future<_i6.Response> request( String? url, { Object? data, Map? queryParameters, @@ -968,7 +987,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #request, @@ -984,7 +1003,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #request, @@ -999,10 +1018,10 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> requestUri( + _i8.Future<_i6.Response> requestUri( Uri? uri, { Object? data, _i10.CancelToken? cancelToken, @@ -1022,7 +1041,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #requestUri, @@ -1037,7 +1056,7 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #requestUri, @@ -1051,16 +1070,16 @@ class MockDio extends _i1.Mock implements _i9.Dio { }, ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); @override - _i7.Future<_i6.Response> fetch(_i2.RequestOptions? requestOptions) => + _i8.Future<_i6.Response> fetch(_i2.RequestOptions? requestOptions) => (super.noSuchMethod( Invocation.method( #fetch, [requestOptions], ), - returnValue: _i7.Future<_i6.Response>.value(_FakeResponse_4( + returnValue: _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #fetch, @@ -1068,14 +1087,60 @@ class MockDio extends _i1.Mock implements _i9.Dio { ), )), returnValueForMissingStub: - _i7.Future<_i6.Response>.value(_FakeResponse_4( + _i8.Future<_i6.Response>.value(_FakeResponse_4( this, Invocation.method( #fetch, [requestOptions], ), )), - ) as _i7.Future<_i6.Response>); + ) as _i8.Future<_i6.Response>); + + @override + _i7.Dio clone({ + _i2.BaseOptions? options, + _i5.Interceptors? interceptors, + _i3.HttpClientAdapter? httpClientAdapter, + _i4.Transformer? transformer, + }) => + (super.noSuchMethod( + Invocation.method( + #clone, + [], + { + #options: options, + #interceptors: interceptors, + #httpClientAdapter: httpClientAdapter, + #transformer: transformer, + }, + ), + returnValue: _FakeDio_5( + this, + Invocation.method( + #clone, + [], + { + #options: options, + #interceptors: interceptors, + #httpClientAdapter: httpClientAdapter, + #transformer: transformer, + }, + ), + ), + returnValueForMissingStub: _FakeDio_5( + this, + Invocation.method( + #clone, + [], + { + #options: options, + #interceptors: interceptors, + #httpClientAdapter: httpClientAdapter, + #transformer: transformer, + }, + ), + ), + ) as _i7.Dio); } /// A class which mocks [SirenDataProvider]. @@ -1095,15 +1160,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i11.SirenDataProvider { ), ) as String); - @override - set userToken(String? _userToken) => super.noSuchMethod( - Invocation.setter( - #userToken, - _userToken, - ), - returnValueForMissingStub: null, - ); - @override String get recipientId => (super.noSuchMethod( Invocation.getter(#recipientId), @@ -1117,15 +1173,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i11.SirenDataProvider { ), ) as String); - @override - set recipientId(String? _recipientId) => super.noSuchMethod( - Invocation.setter( - #recipientId, - _recipientId, - ), - returnValueForMissingStub: null, - ); - @override String get apiDomain => (super.noSuchMethod( Invocation.getter(#apiDomain), @@ -1140,41 +1187,32 @@ class MockSirenDataProvider extends _i1.Mock implements _i11.SirenDataProvider { ) as String); @override - set apiDomain(String? _apiDomain) => super.noSuchMethod( - Invocation.setter( - #apiDomain, - _apiDomain, - ), - returnValueForMissingStub: null, - ); - - @override - _i7.StreamController<_i8.StreamResponse> get inboxController => + _i8.StreamController<_i9.StreamResponse> get inboxController => (super.noSuchMethod( Invocation.getter(#inboxController), - returnValue: _FakeStreamController_5<_i8.StreamResponse>( + returnValue: _FakeStreamController_6<_i9.StreamResponse>( this, Invocation.getter(#inboxController), ), - returnValueForMissingStub: _FakeStreamController_5<_i8.StreamResponse>( + returnValueForMissingStub: _FakeStreamController_6<_i9.StreamResponse>( this, Invocation.getter(#inboxController), ), - ) as _i7.StreamController<_i8.StreamResponse>); + ) as _i8.StreamController<_i9.StreamResponse>); @override - _i7.StreamController<_i8.StreamResponse> get iconController => + _i8.StreamController<_i9.StreamResponse> get iconController => (super.noSuchMethod( Invocation.getter(#iconController), - returnValue: _FakeStreamController_5<_i8.StreamResponse>( + returnValue: _FakeStreamController_6<_i9.StreamResponse>( this, Invocation.getter(#iconController), ), - returnValueForMissingStub: _FakeStreamController_5<_i8.StreamResponse>( + returnValueForMissingStub: _FakeStreamController_6<_i9.StreamResponse>( this, Invocation.getter(#iconController), ), - ) as _i7.StreamController<_i8.StreamResponse>); + ) as _i8.StreamController<_i9.StreamResponse>); @override _i13.Status get tokenVerificationStatus => (super.noSuchMethod( @@ -1191,14 +1229,41 @@ class MockSirenDataProvider extends _i1.Mock implements _i11.SirenDataProvider { ) as bool); @override - _i7.Future initialize() => (super.noSuchMethod( + set userToken(String? _userToken) => super.noSuchMethod( + Invocation.setter( + #userToken, + _userToken, + ), + returnValueForMissingStub: null, + ); + + @override + set recipientId(String? _recipientId) => super.noSuchMethod( + Invocation.setter( + #recipientId, + _recipientId, + ), + returnValueForMissingStub: null, + ); + + @override + set apiDomain(String? _apiDomain) => super.noSuchMethod( + Invocation.setter( + #apiDomain, + _apiDomain, + ), + returnValueForMissingStub: null, + ); + + @override + _i8.Future initialize() => (super.noSuchMethod( Invocation.method( #initialize, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void updateParams({ @@ -1227,26 +1292,26 @@ class MockSirenDataProvider extends _i1.Mock implements _i11.SirenDataProvider { ); @override - _i8.SirenErrorType getVerificationErrorType() => (super.noSuchMethod( + _i9.SirenErrorType getVerificationErrorType() => (super.noSuchMethod( Invocation.method( #getVerificationErrorType, [], ), - returnValue: _FakeSirenErrorType_6( + returnValue: _FakeSirenErrorType_7( this, Invocation.method( #getVerificationErrorType, [], ), ), - returnValueForMissingStub: _FakeSirenErrorType_6( + returnValueForMissingStub: _FakeSirenErrorType_7( this, Invocation.method( #getVerificationErrorType, [], ), ), - ) as _i8.SirenErrorType); + ) as _i9.SirenErrorType); @override void iconDispose() => super.noSuchMethod( diff --git a/test/services/network_service_test.mocks.dart b/test/services/network_service_test.mocks.dart index ee1e372..142127c 100644 --- a/test/services/network_service_test.mocks.dart +++ b/test/services/network_service_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in sirenapp_flutter_inbox/test/services/network_service_test.dart. // Do not manually edit this file. @@ -18,6 +18,7 @@ import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i3; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types diff --git a/test/widgets/siren_inbox_icon_test.mocks.dart b/test/widgets/siren_inbox_icon_test.mocks.dart index cc7c558..dd05da8 100644 --- a/test/widgets/siren_inbox_icon_test.mocks.dart +++ b/test/widgets/siren_inbox_icon_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in sirenapp_flutter_inbox/test/widgets/siren_inbox_icon_test.dart. // Do not manually edit this file. @@ -23,6 +23,7 @@ import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i4; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types @@ -87,15 +88,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set userToken(String? _userToken) => super.noSuchMethod( - Invocation.setter( - #userToken, - _userToken, - ), - returnValueForMissingStub: null, - ); - @override String get recipientId => (super.noSuchMethod( Invocation.getter(#recipientId), @@ -105,15 +97,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set recipientId(String? _recipientId) => super.noSuchMethod( - Invocation.setter( - #recipientId, - _recipientId, - ), - returnValueForMissingStub: null, - ); - @override String get apiDomain => (super.noSuchMethod( Invocation.getter(#apiDomain), @@ -123,15 +106,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set apiDomain(String? _apiDomain) => super.noSuchMethod( - Invocation.setter( - #apiDomain, - _apiDomain, - ), - returnValueForMissingStub: null, - ); - @override _i2.StreamController<_i3.StreamResponse> get inboxController => (super.noSuchMethod( @@ -164,6 +138,33 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { returnValue: false, ) as bool); + @override + set userToken(String? _userToken) => super.noSuchMethod( + Invocation.setter( + #userToken, + _userToken, + ), + returnValueForMissingStub: null, + ); + + @override + set recipientId(String? _recipientId) => super.noSuchMethod( + Invocation.setter( + #recipientId, + _recipientId, + ), + returnValueForMissingStub: null, + ); + + @override + set apiDomain(String? _apiDomain) => super.noSuchMethod( + Invocation.setter( + #apiDomain, + _apiDomain, + ), + returnValueForMissingStub: null, + ); + @override _i2.Future initialize() => (super.noSuchMethod( Invocation.method( diff --git a/test/widgets/siren_inbox_test.mocks.dart b/test/widgets/siren_inbox_test.mocks.dart index 6f46027..49279b6 100644 --- a/test/widgets/siren_inbox_test.mocks.dart +++ b/test/widgets/siren_inbox_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in sirenapp_flutter_inbox/test/widgets/siren_inbox_test.dart. // Do not manually edit this file. @@ -23,6 +23,7 @@ import 'package:sirenapp_flutter_inbox/src/services/api_client.dart' as _i4; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types @@ -87,15 +88,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set userToken(String? _userToken) => super.noSuchMethod( - Invocation.setter( - #userToken, - _userToken, - ), - returnValueForMissingStub: null, - ); - @override String get recipientId => (super.noSuchMethod( Invocation.getter(#recipientId), @@ -109,15 +101,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set recipientId(String? _recipientId) => super.noSuchMethod( - Invocation.setter( - #recipientId, - _recipientId, - ), - returnValueForMissingStub: null, - ); - @override String get apiDomain => (super.noSuchMethod( Invocation.getter(#apiDomain), @@ -131,15 +114,6 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { ), ) as String); - @override - set apiDomain(String? _apiDomain) => super.noSuchMethod( - Invocation.setter( - #apiDomain, - _apiDomain, - ), - returnValueForMissingStub: null, - ); - @override _i2.StreamController<_i3.StreamResponse> get inboxController => (super.noSuchMethod( @@ -182,6 +156,33 @@ class MockSirenDataProvider extends _i1.Mock implements _i5.SirenDataProvider { returnValueForMissingStub: false, ) as bool); + @override + set userToken(String? _userToken) => super.noSuchMethod( + Invocation.setter( + #userToken, + _userToken, + ), + returnValueForMissingStub: null, + ); + + @override + set recipientId(String? _recipientId) => super.noSuchMethod( + Invocation.setter( + #recipientId, + _recipientId, + ), + returnValueForMissingStub: null, + ); + + @override + set apiDomain(String? _apiDomain) => super.noSuchMethod( + Invocation.setter( + #apiDomain, + _apiDomain, + ), + returnValueForMissingStub: null, + ); + @override _i2.Future initialize() => (super.noSuchMethod( Invocation.method( From 5b2cc0b2a344082e74ca8edbf19338b66b6bbbe8 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Fri, 23 May 2025 16:32:28 +0530 Subject: [PATCH 03/20] feat: Add categories initial changes --- lib/src/api/fetch_categories.dart | 64 +++++++++++++++++++++++++++++++ lib/src/widgets/app_bar.dart | 53 +++++++++++++++++++++++++ lib/src/widgets/siren_inbox.dart | 46 ++++++++++++++++++++-- 3 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 lib/src/api/fetch_categories.dart diff --git a/lib/src/api/fetch_categories.dart b/lib/src/api/fetch_categories.dart new file mode 100644 index 0000000..d636211 --- /dev/null +++ b/lib/src/api/fetch_categories.dart @@ -0,0 +1,64 @@ +import 'package:sirenapp_flutter_inbox/src/constants/generics.dart'; +import 'package:sirenapp_flutter_inbox/src/data/siren_data_provider.dart'; +import 'package:sirenapp_flutter_inbox/src/errors/errors.dart'; +import 'package:sirenapp_flutter_inbox/src/models/api_response.dart'; +import 'package:sirenapp_flutter_inbox/src/services/api_client.dart'; +import 'package:sirenapp_flutter_inbox/src/services/api_provider.dart'; + +class FetchCategories { + FetchCategories._internal(); + static final FetchCategories instance = FetchCategories._internal(); + final ApiClient api = ApiClient(apiProvider()); + + List convertJsonToCategoryList(List dataList) { + return dataList.map((dynamic json) { + if (json is String) { + return json; + } + throw const FormatException('Invalid JSON format'); + }).toList(); + } + + Future fetchCategories() async { + final apiPath = + '${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/categories'; + final result = ApiResponse()..isLoading = true; + var apiError = Errors.notificationFetchFailedError; + + if (SirenDataProvider.instance.tokenVerificationStatus != Status.SUCCESS) { + apiError = SirenDataProvider.instance.getVerificationErrorType(); + result + ..isLoading = false + ..isError = true + ..data = null + ..rawResponse = Errors.rawResponseError + ..error = apiError; + return result; + } + + final apiResponse = await api.get( + path: apiPath, + ); + + if (apiResponse.statusCode != 0 && apiResponse.data != null) { + final dataList = + ApiResponse.fromJson(apiResponse.data).data as List?; + result + ..isLoading = false + ..isSuccess = apiResponse.statusCode == 200 + ..isError = apiResponse.statusCode != 200 + ..data = convertJsonToCategoryList(dataList ?? []) + ..rawResponse = apiResponse + ..error = apiError; + } else { + result + ..isLoading = false + ..isSuccess = false + ..isError = true + ..rawResponse = apiResponse + ..error = Errors.defaultError; + } + + return result; + } +} diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index da5df3a..bf4ec1e 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -11,14 +11,21 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.styles, this.colors, this.isDarkMode, + this.categories = const [], + this.selectedValues = const [], + this.onCategorySelected, super.key, }); + final VoidCallback? onClearAllPressed; final bool isNonEmptyNotifications; final HeaderParams? headerParams; final CustomStyles? styles; final CustomThemeColors? colors; final bool? isDarkMode; + final List categories; + final List selectedValues; + final void Function(String)? onCategorySelected; @override Size get preferredSize { @@ -94,6 +101,52 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { ], ), ), + if (categories.isNotEmpty) + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: null, + hint: Row( + children: [ + Expanded( + child: selectedValues.isEmpty + ? const Text( + 'Select Options', + style: TextStyle(color: Colors.grey), + ) + : Text( + selectedValues.join(', '), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + isExpanded: true, + items: categories.map((category) { + return DropdownMenuItem( + value: category, + child: Text(category), + ); + }).toList(), + onChanged: (String? value) { + if (value != null && onCategorySelected != null) { + onCategorySelected!(value); + } + }, + ), + ), + ), + ), + ), if (!(headerParams?.hideClearAll ?? false)) Semantics( label: 'siren-header-clear-all', diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index d77caa9..a93f9c4 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:sirenapp_flutter_inbox/sirenapp_flutter_inbox.dart'; import 'package:sirenapp_flutter_inbox/src/api/delete_notification_by_id.dart'; import 'package:sirenapp_flutter_inbox/src/api/fetch_all_notification.dart'; +import 'package:sirenapp_flutter_inbox/src/api/fetch_categories.dart'; import 'package:sirenapp_flutter_inbox/src/api/mark_all_notifications_as_viewed.dart'; import 'package:sirenapp_flutter_inbox/src/api/notifications_bulk_update.dart'; import 'package:sirenapp_flutter_inbox/src/api/read_notification_by_id.dart'; @@ -35,6 +36,7 @@ class SirenInbox extends StatefulWidget { this.theme, this.customStyles, this.customTabIndicator, + this.showCategories = false, }); /// Flag for enabling dark mode. @@ -81,6 +83,9 @@ class SirenInbox extends StatefulWidget { final BoxDecoration? customTabIndicator; + /// Flag to show categories dropdown in the app bar. + final bool showCategories; + @override State createState() => _SirenInboxState(); } @@ -98,8 +103,11 @@ class _SirenInboxState extends State bool _enableClearAll = true; List notifications = []; + List allCategories = ['1', '2', '3']; + List selectedCategories = []; late final DeleteNotificationById _deleteNotificationById; late final ReadNotificationById _readNotificationById; + late final FetchCategories _fetchCategories; late Timer? _periodicUpdateRef; late StreamSubscription _subscription; late ScrollController _inboxScrollController; @@ -135,7 +143,10 @@ class _SirenInboxState extends State Future _initialize() async { if (SirenDataProvider.instance.tokenVerificationStatus == Status.SUCCESS) { - await initialFetchNotification(); + await Future.wait([ + initialFetchNotification(), + _fetchAllCategories(), + ]); } else if (SirenDataProvider.instance.tokenVerificationStatus == Status.FAILED || !SirenDataProvider.instance.isProviderInitialized) { @@ -146,6 +157,17 @@ class _SirenInboxState extends State } } + Future _fetchAllCategories() async { + final response = await _fetchCategories.fetchCategories(); + if (response.isSuccess && response.data != null) { + safeSetState(() { + allCategories = response.data as List; + }); + } else { + widget.onError?.call(response.error ?? SirenErrorType()); + } + } + void _initializeVariables() { pageSize = max(min(widget.itemsPerFetch ?? Generics.PAGE_SIZE, 50), 0); _periodicUpdateRef = Timer(const Duration(days: 1), () {}); @@ -163,6 +185,7 @@ class _SirenInboxState extends State _deleteNotificationById = DeleteNotificationById.instance; _readNotificationById = ReadNotificationById.instance; + _fetchCategories = FetchCategories.instance; _tabController = TabController( length: InboxTabs.values.length, vsync: this, @@ -200,11 +223,8 @@ class _SirenInboxState extends State _initialize(); break; - - // ignore: no_default_cases default: - } } else if (streamResponse.response?.isError ?? false) { widget.onError @@ -503,6 +523,16 @@ class _SirenInboxState extends State } } + void _updateSelectedCategories(String category) { + setState(() { + if (selectedCategories.contains(category)) { + selectedCategories.remove(category); + } else { + selectedCategories.add(category); + } + }); + } + Widget _buildInboxBody( ScrollController controller, List data, @@ -550,6 +580,10 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, + categories: widget.showCategories ? allCategories : const [], + selectedValues: widget.showCategories ? selectedCategories : const [], + onCategorySelected: + widget.showCategories ? _updateSelectedCategories : null, ), body: Column( children: [ @@ -655,6 +689,10 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, + categories: widget.showCategories ? allCategories : const [], + selectedValues: widget.showCategories ? selectedCategories : const [], + onCategorySelected: + widget.showCategories ? _updateSelectedCategories : null, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); From 1159c44979900496a07af6d374b9e432900e79b4 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Tue, 27 May 2025 10:42:07 +0530 Subject: [PATCH 04/20] feat: Add param models for category --- lib/src/api/fetch_all_notification.dart | 5 +++ lib/src/models/ui_models.dart | 43 ++++++++++++++++++++++++ lib/src/widgets/app_bar.dart | 32 +++++++++++++----- lib/src/widgets/siren_inbox.dart | 44 ++++++++++++++++++------- 4 files changed, 103 insertions(+), 21 deletions(-) diff --git a/lib/src/api/fetch_all_notification.dart b/lib/src/api/fetch_all_notification.dart index b14f496..9d8f6aa 100644 --- a/lib/src/api/fetch_all_notification.dart +++ b/lib/src/api/fetch_all_notification.dart @@ -29,6 +29,7 @@ class FetchAllNotifications { bool? isRead, String? start, String? end, + List? categories, }) async { final apiPath = '${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/notifications'; @@ -53,6 +54,10 @@ class FetchAllNotifications { queryParams['isRead'] = isRead.toString(); } + if (categories != null && categories.isNotEmpty) { + queryParams['categories'] = categories.join(','); + } + final queryString = queryParams.entries.map((e) => '${e.key}=${e.value}').join('&'); diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 5631392..44dae10 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -468,3 +468,46 @@ class TabColors { /// The color of the tab indicator. final Color? indicatorColor; } + +/// Properties for configuring the appearance of the category dropdown. +class CategoryStyle { + /// Constructs a [CategoryStyle] with optional parameters. + const CategoryStyle({ + this.container, + this.dropdownTextStyle, + this.selectedTextStyle, + this.placeholderTextStyle, + }); + + /// The container style for the category dropdown. + final ContainerStyle? container; + + /// The text style for dropdown items. + final TextStyle? dropdownTextStyle; + + /// The text style for selected items in the dropdown. + final TextStyle? selectedTextStyle; + + /// The text style for the placeholder text. + final TextStyle? placeholderTextStyle; +} + +/// Properties for configuring the appearance and behavior of the category dropdown. +class CategoryParams { + /// Constructs a [CategoryParams] with optional parameters. + const CategoryParams({ + this.showCategories = false, + this.dropdownItemBuilder, + this.style, + }); + + /// Flag to show the categories dropdown in the app bar. + final bool showCategories; + + /// Custom builder for dropdown menu items. + /// If not provided, a default Text widget will be used. + final Widget Function(String)? dropdownItemBuilder; + + /// Style properties for the category dropdown. + final CategoryStyle? style; +} diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index bf4ec1e..f6b2fbb 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -14,6 +14,8 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.categories = const [], this.selectedValues = const [], this.onCategorySelected, + this.dropdownItemBuilder, + this.categoryStyle, super.key, }); @@ -26,6 +28,8 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final List categories; final List selectedValues; final void Function(String)? onCategorySelected; + final Widget Function(String)? dropdownItemBuilder; + final CategoryStyle? categoryStyle; @override Size get preferredSize { @@ -104,13 +108,16 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { if (categories.isNotEmpty) Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - ), + padding: categoryStyle?.container?.padding ?? + const EdgeInsets.symmetric(horizontal: 12), + margin: categoryStyle?.container?.margin, + decoration: categoryStyle?.container?.decoration ?? + BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), child: DropdownButtonHideUnderline( child: DropdownButton( value: null, @@ -118,14 +125,17 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { children: [ Expanded( child: selectedValues.isEmpty - ? const Text( + ? Text( 'Select Options', - style: TextStyle(color: Colors.grey), + style: categoryStyle + ?.placeholderTextStyle ?? + const TextStyle(color: Colors.grey), ) : Text( selectedValues.join(', '), overflow: TextOverflow.ellipsis, maxLines: 1, + style: categoryStyle?.selectedTextStyle, ), ), ], @@ -134,7 +144,11 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { items: categories.map((category) { return DropdownMenuItem( value: category, - child: Text(category), + child: dropdownItemBuilder?.call(category) ?? + Text( + category, + style: categoryStyle?.dropdownTextStyle, + ), ); }).toList(), onChanged: (String? value) { diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index a93f9c4..ad6f48a 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -36,7 +36,7 @@ class SirenInbox extends StatefulWidget { this.theme, this.customStyles, this.customTabIndicator, - this.showCategories = false, + this.categoryParams, }); /// Flag for enabling dark mode. @@ -83,8 +83,8 @@ class SirenInbox extends StatefulWidget { final BoxDecoration? customTabIndicator; - /// Flag to show categories dropdown in the app bar. - final bool showCategories; + /// Properties for configuring the category dropdown. + final CategoryParams? categoryParams; @override State createState() => _SirenInboxState(); @@ -103,7 +103,7 @@ class _SirenInboxState extends State bool _enableClearAll = true; List notifications = []; - List allCategories = ['1', '2', '3']; + List allCategories = []; List selectedCategories = []; late final DeleteNotificationById _deleteNotificationById; late final ReadNotificationById _readNotificationById; @@ -332,6 +332,7 @@ class _SirenInboxState extends State notifications[0].createdAt, ) : null, + categories: selectedCategories, ); if (fetchedNotifications.isSuccess) { final newNotifications = @@ -366,6 +367,7 @@ class _SirenInboxState extends State end: DateTime.now().toUtc().toIso8601String(), size: pageSize, isRead: getIsRead(), + categories: selectedCategories, ); if (fetchedNotifications.isSuccess) { @@ -470,6 +472,7 @@ class _SirenInboxState extends State ), size: pageSize, isRead: getIsRead(), + categories: selectedCategories, ); if (fetchedNotifications.isSuccess) { final newNotifications = @@ -531,6 +534,9 @@ class _SirenInboxState extends State selectedCategories.add(category); } }); + // Reset and fetch notifications with new category selection + _reset(cancelFetch: true); + initialFetchNotification(); } Widget _buildInboxBody( @@ -580,10 +586,17 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.showCategories ? allCategories : const [], - selectedValues: widget.showCategories ? selectedCategories : const [], - onCategorySelected: - widget.showCategories ? _updateSelectedCategories : null, + categories: widget.categoryParams?.showCategories ?? false + ? allCategories + : const [], + selectedValues: widget.categoryParams?.showCategories ?? false + ? selectedCategories + : const [], + onCategorySelected: widget.categoryParams?.showCategories ?? false + ? _updateSelectedCategories + : null, + dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, + categoryStyle: widget.categoryParams?.style, ), body: Column( children: [ @@ -689,10 +702,17 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.showCategories ? allCategories : const [], - selectedValues: widget.showCategories ? selectedCategories : const [], - onCategorySelected: - widget.showCategories ? _updateSelectedCategories : null, + categories: widget.categoryParams?.showCategories ?? false + ? allCategories + : const [], + selectedValues: widget.categoryParams?.showCategories ?? false + ? selectedCategories + : const [], + onCategorySelected: widget.categoryParams?.showCategories ?? false + ? _updateSelectedCategories + : null, + dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, + categoryStyle: widget.categoryParams?.style, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); From 429b1bc22d1427396ebe7dcba2f3fbf6b5e38d2f Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Tue, 27 May 2025 16:22:46 +0530 Subject: [PATCH 05/20] feat: Add API changes to support multiple categories, add colors for drop down changes --- lib/src/api/fetch_all_notification.dart | 16 +++++++-- lib/src/models/ui_models.dart | 25 ++++++++++++++ lib/src/theme/app_colors.dart | 2 ++ lib/src/theme/colors.dart | 2 ++ lib/src/theme/dark_colors.dart | 1 + lib/src/theme/light_colors.dart | 1 + lib/src/widgets/app_bar.dart | 43 +++++++++++++++++++++---- lib/src/widgets/siren_inbox.dart | 2 ++ 8 files changed, 82 insertions(+), 10 deletions(-) diff --git a/lib/src/api/fetch_all_notification.dart b/lib/src/api/fetch_all_notification.dart index 9d8f6aa..70b79fa 100644 --- a/lib/src/api/fetch_all_notification.dart +++ b/lib/src/api/fetch_all_notification.dart @@ -54,12 +54,22 @@ class FetchAllNotifications { queryParams['isRead'] = isRead.toString(); } + // Build the query string + final queryParts = []; + + // Add all non-category parameters + queryParams.forEach((key, value) { + queryParts.add('$key=${Uri.encodeComponent(value)}'); + }); + + // Add each category as a separate parameter if (categories != null && categories.isNotEmpty) { - queryParams['categories'] = categories.join(','); + for (final category in categories) { + queryParts.add('category=${Uri.encodeComponent(category)}'); + } } - final queryString = - queryParams.entries.map((e) => '${e.key}=${e.value}').join('&'); + final queryString = queryParts.join('&'); if (SirenDataProvider.instance.tokenVerificationStatus != Status.SUCCESS) { apiError = SirenDataProvider.instance.getVerificationErrorType(); diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 44dae10..465a0ca 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -187,6 +187,7 @@ class CustomThemeColors { this.badgeColors, this.cardColors, this.tabColors, + this.categoryColors, }); /// The background color for Siren inbox. @@ -233,6 +234,18 @@ class CustomThemeColors { /// The colors for tab bar final TabColors? tabColors; + + /// The colors for category dropdown + final CategoryColors? categoryColors; +} + +class CategoryColors { + CategoryColors({ + this.dropdownHighlightColor, + }); + + /// The highlight color for selected items in the dropdown + final Color? dropdownHighlightColor; } /// Custom theme colors to configure the appearance inbox list item. @@ -477,6 +490,8 @@ class CategoryStyle { this.dropdownTextStyle, this.selectedTextStyle, this.placeholderTextStyle, + this.boxDecoration, + this.placeholderText, }); /// The container style for the category dropdown. @@ -490,6 +505,12 @@ class CategoryStyle { /// The text style for the placeholder text. final TextStyle? placeholderTextStyle; + + /// The box decoration for the category dropdown. + final BoxDecoration? boxDecoration; + + /// The placeholder text to show when no category is selected. + final String? placeholderText; } /// Properties for configuring the appearance and behavior of the category dropdown. @@ -499,6 +520,7 @@ class CategoryParams { this.showCategories = false, this.dropdownItemBuilder, this.style, + this.placeholderText, }); /// Flag to show the categories dropdown in the app bar. @@ -510,4 +532,7 @@ class CategoryParams { /// Style properties for the category dropdown. final CategoryStyle? style; + + /// The placeholder text to show when no category is selected. + final String? placeholderText; } diff --git a/lib/src/theme/app_colors.dart b/lib/src/theme/app_colors.dart index 02e95f3..cca1785 100644 --- a/lib/src/theme/app_colors.dart +++ b/lib/src/theme/app_colors.dart @@ -44,6 +44,7 @@ class AppColors { required this.tabBarInActiveColor, required this.textColor, required this.timerIcon, + required this.dropdownHighlightColor, }); factory AppColors.lightColorTheme() => lightColors; @@ -89,4 +90,5 @@ class AppColors { Color tabBarInActiveColor; Color textColor; Color timerIcon; + Color dropdownHighlightColor; } diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 39245a9..98f5a2b 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -34,4 +34,6 @@ class SirenAppColors { static const Color avatarIconLight = Color(0xFF98A2B3); static const Color avatarPlaceholderBgDark = Color(0xFF4C4C4C); static const Color avatarIconDark = Color(0xFF999999); + + static const Color dropdownHighlightColor = Color(0xFFF1F2F5); } diff --git a/lib/src/theme/dark_colors.dart b/lib/src/theme/dark_colors.dart index 387fd93..3f0c2ce 100644 --- a/lib/src/theme/dark_colors.dart +++ b/lib/src/theme/dark_colors.dart @@ -43,4 +43,5 @@ final darkColors = AppColors( tabBarInActiveColor: SirenAppColors.grey300, textColor: SirenAppColors.grey700Complementary, timerIcon: SirenAppColors.grey400, + dropdownHighlightColor: SirenAppColors.grey300Complementary, ); diff --git a/lib/src/theme/light_colors.dart b/lib/src/theme/light_colors.dart index 17eb429..4c90862 100644 --- a/lib/src/theme/light_colors.dart +++ b/lib/src/theme/light_colors.dart @@ -43,4 +43,5 @@ final lightColors = AppColors( tabBarInActiveColor: SirenAppColors.grey700, textColor: SirenAppColors.grey700, timerIcon: SirenAppColors.grey500, + dropdownHighlightColor: SirenAppColors.grey300, ); diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index f6b2fbb..829ebb2 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -16,6 +16,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.onCategorySelected, this.dropdownItemBuilder, this.categoryStyle, + this.placeholderText, super.key, }); @@ -30,6 +31,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final void Function(String)? onCategorySelected; final Widget Function(String)? dropdownItemBuilder; final CategoryStyle? categoryStyle; + final String? placeholderText; @override Size get preferredSize { @@ -44,6 +46,12 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { return const SizedBox.shrink(); } final defaultColors = SirenAppTheme.colors(isDarkMode: isDarkMode ?? false); + print("colors: ${colors}"); + print("colors defaultColors: ${defaultColors}"); + print("colors cat: ${colors?.categoryColors}"); + print("colors cat: ${colors?.categoryColors}"); + print( + "colors cat dropdownHighlightColor: ${colors?.categoryColors?.dropdownHighlightColor}"); return Container( decoration: BoxDecoration( color: colors?.inboxHeaderColors?.background ?? @@ -120,13 +128,12 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { ), child: DropdownButtonHideUnderline( child: DropdownButton( - value: null, hint: Row( children: [ Expanded( child: selectedValues.isEmpty ? Text( - 'Select Options', + placeholderText ?? 'Select Category', style: categoryStyle ?.placeholderTextStyle ?? const TextStyle(color: Colors.grey), @@ -141,14 +148,36 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { ], ), isExpanded: true, + borderRadius: BorderRadius.circular(8), items: categories.map((category) { + final isSelected = + selectedValues.contains(category); return DropdownMenuItem( value: category, - child: dropdownItemBuilder?.call(category) ?? - Text( - category, - style: categoryStyle?.dropdownTextStyle, - ), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + decoration: categoryStyle?.boxDecoration ?? + BoxDecoration( + color: isSelected + ? (colors?.categoryColors + ?.dropdownHighlightColor ?? + defaultColors + .dropdownHighlightColor) + : Colors.transparent, + borderRadius: BorderRadius.circular(4), + ), + child: SizedBox( + width: double.infinity, + child: dropdownItemBuilder?.call(category) ?? + Text( + category, + style: categoryStyle?.dropdownTextStyle, + ), + ), + ), ); }).toList(), onChanged: (String? value) { diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index ad6f48a..4391a01 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -597,6 +597,7 @@ class _SirenInboxState extends State : null, dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, categoryStyle: widget.categoryParams?.style, + placeholderText: widget.categoryParams?.placeholderText, ), body: Column( children: [ @@ -713,6 +714,7 @@ class _SirenInboxState extends State : null, dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, categoryStyle: widget.categoryParams?.style, + placeholderText: widget.categoryParams?.placeholderText, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); From 47cb904cef2d1713a35644cf4b1dbc990bf05841 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Tue, 27 May 2025 21:15:04 +0530 Subject: [PATCH 06/20] feat: Add UI changes to support filter icon --- lib/src/widgets/app_bar.dart | 274 ++++++++++++++++++++--------------- 1 file changed, 160 insertions(+), 114 deletions(-) diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 829ebb2..345cdd3 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -46,12 +46,6 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { return const SizedBox.shrink(); } final defaultColors = SirenAppTheme.colors(isDarkMode: isDarkMode ?? false); - print("colors: ${colors}"); - print("colors defaultColors: ${defaultColors}"); - print("colors cat: ${colors?.categoryColors}"); - print("colors cat: ${colors?.categoryColors}"); - print( - "colors cat dropdownHighlightColor: ${colors?.categoryColors?.dropdownHighlightColor}"); return Container( decoration: BoxDecoration( color: colors?.inboxHeaderColors?.background ?? @@ -113,125 +107,177 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { ], ), ), - if (categories.isNotEmpty) - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Container( - padding: categoryStyle?.container?.padding ?? - const EdgeInsets.symmetric(horizontal: 12), - margin: categoryStyle?.container?.margin, - decoration: categoryStyle?.container?.decoration ?? - BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - ), - child: DropdownButtonHideUnderline( - child: DropdownButton( - hint: Row( + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (categories.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _CategoryFilter( + categories: categories, + selectedValues: selectedValues, + onSelectionChanged: onCategorySelected, + colors: colors, + ), + ), + if (!(headerParams?.hideClearAll ?? false)) + Semantics( + label: 'siren-header-clear-all', + hint: 'Tap to clear all notifications', + child: GestureDetector( + key: const Key('siren-header-clear-all'), + onTap: () { + if (isNonEmptyNotifications && + onClearAllPressed != null) { + onClearAllPressed!(); + } + }, + child: Opacity( + opacity: isNonEmptyNotifications ? 1 : 0.4, + child: Row( children: [ - Expanded( - child: selectedValues.isEmpty - ? Text( - placeholderText ?? 'Select Category', - style: categoryStyle - ?.placeholderTextStyle ?? - const TextStyle(color: Colors.grey), - ) - : Text( - selectedValues.join(', '), - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: categoryStyle?.selectedTextStyle, - ), - ), - ], - ), - isExpanded: true, - borderRadius: BorderRadius.circular(8), - items: categories.map((category) { - final isSelected = - selectedValues.contains(category); - return DropdownMenuItem( - value: category, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 12, + Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.clear_all, + size: styles?.clearAllIconStyle?.size ?? 24, + color: colors?.clearAllIcon ?? + defaultColors.appBarActionText, ), - decoration: categoryStyle?.boxDecoration ?? - BoxDecoration( - color: isSelected - ? (colors?.categoryColors - ?.dropdownHighlightColor ?? - defaultColors - .dropdownHighlightColor) - : Colors.transparent, - borderRadius: BorderRadius.circular(4), - ), - child: SizedBox( - width: double.infinity, - child: dropdownItemBuilder?.call(category) ?? - Text( - category, - style: categoryStyle?.dropdownTextStyle, - ), + ), + Text( + Strings.clear_all, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colors?.inboxHeaderColors + ?.headerActionColor ?? + defaultColors.appBarActionText, ), ), - ); - }).toList(), - onChanged: (String? value) { - if (value != null && onCategorySelected != null) { - onCategorySelected!(value); - } - }, - ), - ), - ), - ), - ), - if (!(headerParams?.hideClearAll ?? false)) - Semantics( - label: 'siren-header-clear-all', - hint: 'Tap to clear all notifications', - child: GestureDetector( - key: const Key('siren-header-clear-all'), - onTap: () { - if (isNonEmptyNotifications && - onClearAllPressed != null) { - onClearAllPressed!(); - } - }, - child: Opacity( - opacity: isNonEmptyNotifications ? 1 : 0.4, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.clear_all, - size: styles?.clearAllIconStyle?.size ?? 24, - color: colors?.clearAllIcon ?? - defaultColors.appBarActionText, - ), - ), - Text( - Strings.clear_all, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: colors - ?.inboxHeaderColors?.headerActionColor ?? - defaultColors.appBarActionText, - ), + ], ), - ], + ), ), ), + ], + ), + ], + ), + ); + } +} + +class _CategoryFilter extends StatelessWidget { + const _CategoryFilter({ + required this.categories, + required this.selectedValues, + required this.onSelectionChanged, + required this.colors, + }); + + final List categories; + final List selectedValues; + final void Function(String)? onSelectionChanged; + final CustomThemeColors? colors; + + Future _showFilterMenu(BuildContext context) async { + final button = context.findRenderObject()! as RenderBox; + final overlay = + Overlay.of(context).context.findRenderObject()! as RenderBox; + final position = button.localToGlobal(Offset.zero, ancestor: overlay); + + await showMenu( + context: context, + position: RelativeRect.fromLTRB( + position.dx, + position.dy + button.size.height, + position.dx + button.size.width, + 0, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + items: categories.map((category) { + final isSelected = selectedValues.contains(category); + return PopupMenuItem( + value: category, + child: Row( + children: [ + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: isSelected ? Colors.deepOrange : Colors.transparent, + border: Border.all( + color: + isSelected ? Colors.deepOrange : Colors.grey.shade300, + width: 2, ), + borderRadius: BorderRadius.circular(8), ), + child: isSelected + ? const Icon( + Icons.check, + color: Colors.white, + size: 16, + ) + : null, + ), + const SizedBox(width: 12), + Text( + category, + style: const TextStyle(fontSize: 18), + ), ], ), + onTap: () { + if (onSelectionChanged != null) { + onSelectionChanged!(category); + } + }, + ); + }).toList(), + ); + } + + @override + Widget build(BuildContext context) { + return Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.shade300, + ), + borderRadius: BorderRadius.circular(8), + ), + child: IconButton( + icon: const Icon(Icons.filter_alt_outlined), + onPressed: () => _showFilterMenu(context), + ), + ), + if (selectedValues.isNotEmpty) + Positioned( + right: -11, + top: -4, + child: Container( + width: 22, + height: 22, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + '${selectedValues.length}', + style: const TextStyle(color: Colors.white, fontSize: 12), + ), + ), + ), + ), + ], ); } } From e81e22562b6e5b7318104c06ae2473d332dabc7f Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 09:48:28 +0530 Subject: [PATCH 07/20] fix: Add fix for light theme colors --- env | 2 +- lib/src/models/ui_models.dart | 24 ++++++++++++++++++++++ lib/src/theme/app_colors.dart | 12 +++++++++++ lib/src/theme/colors.dart | 14 +++++++++++++ lib/src/theme/dark_colors.dart | 6 ++++++ lib/src/theme/light_colors.dart | 6 ++++++ lib/src/widgets/app_bar.dart | 35 +++++++++++++++++++++++++-------- 7 files changed, 90 insertions(+), 9 deletions(-) diff --git a/env b/env index 77317ce..3d21324 100644 --- a/env +++ b/env @@ -1 +1 @@ -API_DOMAIN = https://api.trysiren.io/api/ \ No newline at end of file +API_DOMAIN = https://api.dev.trysiren.io/api/ \ No newline at end of file diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 465a0ca..739216a 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -242,10 +242,34 @@ class CustomThemeColors { class CategoryColors { CategoryColors({ this.dropdownHighlightColor, + this.filterIconBorderColor, + this.filterBadgeColor, + this.filterDropdownBackgroundColor, + this.filterCheckboxCheckedColor, + this.filterCheckboxUncheckedColor, + this.menuActionTextColor, }); /// The highlight color for selected items in the dropdown final Color? dropdownHighlightColor; + + /// The border color for the filter icon button + final Color? filterIconBorderColor; + + /// The badge color for the filter icon + final Color? filterBadgeColor; + + /// The background color for the filter dropdown + final Color? filterDropdownBackgroundColor; + + /// The checked color for the filter checkbox + final Color? filterCheckboxCheckedColor; + + /// The unchecked color for the filter checkbox border + final Color? filterCheckboxUncheckedColor; + + /// The menu action text color + final Color? menuActionTextColor; } /// Custom theme colors to configure the appearance inbox list item. diff --git a/lib/src/theme/app_colors.dart b/lib/src/theme/app_colors.dart index cca1785..ba838cf 100644 --- a/lib/src/theme/app_colors.dart +++ b/lib/src/theme/app_colors.dart @@ -45,6 +45,12 @@ class AppColors { required this.textColor, required this.timerIcon, required this.dropdownHighlightColor, + required this.filterIconBorderColor, + required this.filterBadgeColor, + required this.filterDropdownBackgroundColor, + required this.filterCheckboxCheckedColor, + required this.filterCheckboxUncheckedColor, + required this.menuActionTextColor, }); factory AppColors.lightColorTheme() => lightColors; @@ -91,4 +97,10 @@ class AppColors { Color textColor; Color timerIcon; Color dropdownHighlightColor; + Color filterIconBorderColor; + Color filterBadgeColor; + Color filterDropdownBackgroundColor; + Color filterCheckboxCheckedColor; + Color filterCheckboxUncheckedColor; + Color menuActionTextColor; } diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 98f5a2b..2b83a14 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -36,4 +36,18 @@ class SirenAppColors { static const Color avatarIconDark = Color(0xFF999999); static const Color dropdownHighlightColor = Color(0xFFF1F2F5); + + // Filter (category) dropdown and badge colors + static const Color filterIconBorderLight = Color(0xFFE0E0E0); + static const Color filterBadgeLight = Color(0xFFD32F2F); + static const Color filterDropdownBackgroundLight = Color(0xFFFFFFFF); + static const Color filterCheckboxCheckedLight = Color(0xFFFF7043); + static const Color filterCheckboxUncheckedLight = Color(0xFFBDBDBD); + + static const Color filterIconBorderDark = Color(0xFF444444); + static const Color filterBadgeDark = Color(0xFFD32F2F); + static const Color filterDropdownBackgroundDark = Color(0xFF232323); + static const Color filterCheckboxCheckedDark = Color(0xFFFF7043); + static const Color filterCheckboxUncheckedDark = Color(0xFF888888); + static const Color menuActionTextColorLight = Color(0xFF101928); } diff --git a/lib/src/theme/dark_colors.dart b/lib/src/theme/dark_colors.dart index 3f0c2ce..da3a08b 100644 --- a/lib/src/theme/dark_colors.dart +++ b/lib/src/theme/dark_colors.dart @@ -44,4 +44,10 @@ final darkColors = AppColors( textColor: SirenAppColors.grey700Complementary, timerIcon: SirenAppColors.grey400, dropdownHighlightColor: SirenAppColors.grey300Complementary, + filterIconBorderColor: SirenAppColors.filterIconBorderDark, + filterBadgeColor: SirenAppColors.filterBadgeDark, + filterDropdownBackgroundColor: SirenAppColors.filterDropdownBackgroundDark, + filterCheckboxCheckedColor: SirenAppColors.filterCheckboxCheckedDark, + filterCheckboxUncheckedColor: SirenAppColors.filterCheckboxUncheckedDark, + menuActionTextColor: SirenAppColors.avatarPlaceholderBgLight, ); diff --git a/lib/src/theme/light_colors.dart b/lib/src/theme/light_colors.dart index 4c90862..79b6562 100644 --- a/lib/src/theme/light_colors.dart +++ b/lib/src/theme/light_colors.dart @@ -44,4 +44,10 @@ final lightColors = AppColors( textColor: SirenAppColors.grey700, timerIcon: SirenAppColors.grey500, dropdownHighlightColor: SirenAppColors.grey300, + filterIconBorderColor: SirenAppColors.filterIconBorderLight, + filterBadgeColor: SirenAppColors.filterBadgeLight, + filterDropdownBackgroundColor: SirenAppColors.filterDropdownBackgroundLight, + filterCheckboxCheckedColor: SirenAppColors.filterCheckboxCheckedLight, + filterCheckboxUncheckedColor: SirenAppColors.filterCheckboxUncheckedLight, + menuActionTextColor: SirenAppColors.menuActionTextColorLight, ); diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 345cdd3..2784440 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sirenapp_flutter_inbox/src/constants/strings.dart'; import 'package:sirenapp_flutter_inbox/src/models/ui_models.dart'; +import 'package:sirenapp_flutter_inbox/src/theme/app_colors.dart'; import 'package:sirenapp_flutter_inbox/src/theme/app_theme.dart'; class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -118,6 +119,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { selectedValues: selectedValues, onSelectionChanged: onCategorySelected, colors: colors, + defaultColors: defaultColors, ), ), if (!(headerParams?.hideClearAll ?? false)) @@ -174,12 +176,14 @@ class _CategoryFilter extends StatelessWidget { required this.selectedValues, required this.onSelectionChanged, required this.colors, + required this.defaultColors, }); final List categories; final List selectedValues; final void Function(String)? onSelectionChanged; final CustomThemeColors? colors; + final AppColors defaultColors; Future _showFilterMenu(BuildContext context) async { final button = context.findRenderObject()! as RenderBox; @@ -195,6 +199,8 @@ class _CategoryFilter extends StatelessWidget { position.dx + button.size.width, 0, ), + color: colors?.categoryColors?.filterDropdownBackgroundColor ?? + defaultColors.filterDropdownBackgroundColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), items: categories.map((category) { final isSelected = selectedValues.contains(category); @@ -206,10 +212,17 @@ class _CategoryFilter extends StatelessWidget { width: 24, height: 24, decoration: BoxDecoration( - color: isSelected ? Colors.deepOrange : Colors.transparent, + color: isSelected + ? colors?.categoryColors?.filterCheckboxCheckedColor ?? + defaultColors.filterCheckboxCheckedColor + : Colors.transparent, border: Border.all( - color: - isSelected ? Colors.deepOrange : Colors.grey.shade300, + color: isSelected + ? colors?.categoryColors?.filterCheckboxCheckedColor ?? + defaultColors.filterCheckboxCheckedColor + : colors?.categoryColors + ?.filterCheckboxUncheckedColor ?? + defaultColors.filterCheckboxUncheckedColor, width: 2, ), borderRadius: BorderRadius.circular(8), @@ -225,13 +238,17 @@ class _CategoryFilter extends StatelessWidget { const SizedBox(width: 12), Text( category, - style: const TextStyle(fontSize: 18), + style: TextStyle( + fontSize: 14, + color: colors?.categoryColors?.menuActionTextColor ?? + defaultColors.menuActionTextColor, + ), ), ], ), onTap: () { if (onSelectionChanged != null) { - onSelectionChanged!(category); + onSelectionChanged?.call(category); } }, ); @@ -249,7 +266,8 @@ class _CategoryFilter extends StatelessWidget { height: 44, decoration: BoxDecoration( border: Border.all( - color: Colors.grey.shade300, + color: colors?.categoryColors?.filterIconBorderColor ?? + defaultColors.filterIconBorderColor, ), borderRadius: BorderRadius.circular(8), ), @@ -265,8 +283,9 @@ class _CategoryFilter extends StatelessWidget { child: Container( width: 22, height: 22, - decoration: const BoxDecoration( - color: Colors.red, + decoration: BoxDecoration( + color: colors?.categoryColors?.filterBadgeColor ?? + defaultColors.filterBadgeColor, shape: BoxShape.circle, ), child: Center( From 1400933e52ea30e478c91f0f48bcb95b06a42361 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 10:38:40 +0530 Subject: [PATCH 08/20] feat: Add more colors to support filters --- lib/src/models/ui_models.dart | 32 ++++++++++++++++++-------------- lib/src/theme/app_colors.dart | 10 ++++++---- lib/src/theme/colors.dart | 8 +++++--- lib/src/theme/dark_colors.dart | 5 +++-- lib/src/theme/light_colors.dart | 5 +++-- lib/src/widgets/app_bar.dart | 30 +++++++++++++++++------------- 6 files changed, 52 insertions(+), 38 deletions(-) diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 739216a..313b2f4 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -187,7 +187,7 @@ class CustomThemeColors { this.badgeColors, this.cardColors, this.tabColors, - this.categoryColors, + this.filterColors, }); /// The background color for Siren inbox. @@ -236,23 +236,21 @@ class CustomThemeColors { final TabColors? tabColors; /// The colors for category dropdown - final CategoryColors? categoryColors; + final FilterColors? filterColors; } -class CategoryColors { - CategoryColors({ - this.dropdownHighlightColor, +class FilterColors { + FilterColors({ this.filterIconBorderColor, this.filterBadgeColor, this.filterDropdownBackgroundColor, this.filterCheckboxCheckedColor, this.filterCheckboxUncheckedColor, - this.menuActionTextColor, + this.filterActionTextColor, + this.filterIconColor, + this.checkIconColor, }); - /// The highlight color for selected items in the dropdown - final Color? dropdownHighlightColor; - /// The border color for the filter icon button final Color? filterIconBorderColor; @@ -269,7 +267,13 @@ class CategoryColors { final Color? filterCheckboxUncheckedColor; /// The menu action text color - final Color? menuActionTextColor; + final Color? filterActionTextColor; + + /// The text color for the filter icon + final Color? filterIconColor; + + /// The color for the check icon in the filter dropdown + final Color? checkIconColor; } /// Custom theme colors to configure the appearance inbox list item. @@ -507,9 +511,9 @@ class TabColors { } /// Properties for configuring the appearance of the category dropdown. -class CategoryStyle { - /// Constructs a [CategoryStyle] with optional parameters. - const CategoryStyle({ +class FilterStyles { + /// Constructs a [FilterStyles] with optional parameters. + const FilterStyles({ this.container, this.dropdownTextStyle, this.selectedTextStyle, @@ -555,7 +559,7 @@ class CategoryParams { final Widget Function(String)? dropdownItemBuilder; /// Style properties for the category dropdown. - final CategoryStyle? style; + final FilterStyles? style; /// The placeholder text to show when no category is selected. final String? placeholderText; diff --git a/lib/src/theme/app_colors.dart b/lib/src/theme/app_colors.dart index ba838cf..66661b0 100644 --- a/lib/src/theme/app_colors.dart +++ b/lib/src/theme/app_colors.dart @@ -44,13 +44,14 @@ class AppColors { required this.tabBarInActiveColor, required this.textColor, required this.timerIcon, - required this.dropdownHighlightColor, required this.filterIconBorderColor, required this.filterBadgeColor, required this.filterDropdownBackgroundColor, required this.filterCheckboxCheckedColor, required this.filterCheckboxUncheckedColor, - required this.menuActionTextColor, + required this.filterActionTextColor, + required this.filterIconColor, + required this.checkIconColor, }); factory AppColors.lightColorTheme() => lightColors; @@ -96,11 +97,12 @@ class AppColors { Color tabBarInActiveColor; Color textColor; Color timerIcon; - Color dropdownHighlightColor; Color filterIconBorderColor; Color filterBadgeColor; Color filterDropdownBackgroundColor; Color filterCheckboxCheckedColor; Color filterCheckboxUncheckedColor; - Color menuActionTextColor; + Color filterActionTextColor; + Color filterIconColor; + Color checkIconColor; } diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 2b83a14..648d3a3 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -35,19 +35,21 @@ class SirenAppColors { static const Color avatarPlaceholderBgDark = Color(0xFF4C4C4C); static const Color avatarIconDark = Color(0xFF999999); - static const Color dropdownHighlightColor = Color(0xFFF1F2F5); - // Filter (category) dropdown and badge colors static const Color filterIconBorderLight = Color(0xFFE0E0E0); static const Color filterBadgeLight = Color(0xFFD32F2F); static const Color filterDropdownBackgroundLight = Color(0xFFFFFFFF); static const Color filterCheckboxCheckedLight = Color(0xFFFF7043); static const Color filterCheckboxUncheckedLight = Color(0xFFBDBDBD); + static const Color filterTextColorLight = Color(0xFF344054); + static const Color checkIconColorLight = Colors.white; static const Color filterIconBorderDark = Color(0xFF444444); static const Color filterBadgeDark = Color(0xFFD32F2F); - static const Color filterDropdownBackgroundDark = Color(0xFF232323); + static const Color filterDropdownBackgroundDark = Color(0xFF2F2F2F); static const Color filterCheckboxCheckedDark = Color(0xFFFF7043); static const Color filterCheckboxUncheckedDark = Color(0xFF888888); static const Color menuActionTextColorLight = Color(0xFF101928); + static const Color filterTextColorDark = Colors.white; + static const Color checkIconColorDark = Colors.white; } diff --git a/lib/src/theme/dark_colors.dart b/lib/src/theme/dark_colors.dart index da3a08b..a43a892 100644 --- a/lib/src/theme/dark_colors.dart +++ b/lib/src/theme/dark_colors.dart @@ -43,11 +43,12 @@ final darkColors = AppColors( tabBarInActiveColor: SirenAppColors.grey300, textColor: SirenAppColors.grey700Complementary, timerIcon: SirenAppColors.grey400, - dropdownHighlightColor: SirenAppColors.grey300Complementary, filterIconBorderColor: SirenAppColors.filterIconBorderDark, filterBadgeColor: SirenAppColors.filterBadgeDark, filterDropdownBackgroundColor: SirenAppColors.filterDropdownBackgroundDark, filterCheckboxCheckedColor: SirenAppColors.filterCheckboxCheckedDark, filterCheckboxUncheckedColor: SirenAppColors.filterCheckboxUncheckedDark, - menuActionTextColor: SirenAppColors.avatarPlaceholderBgLight, + filterActionTextColor: SirenAppColors.avatarPlaceholderBgLight, + filterIconColor: SirenAppColors.grey500Complementary, + checkIconColor: SirenAppColors.checkIconColorDark, ); diff --git a/lib/src/theme/light_colors.dart b/lib/src/theme/light_colors.dart index 79b6562..12d7cd9 100644 --- a/lib/src/theme/light_colors.dart +++ b/lib/src/theme/light_colors.dart @@ -43,11 +43,12 @@ final lightColors = AppColors( tabBarInActiveColor: SirenAppColors.grey700, textColor: SirenAppColors.grey700, timerIcon: SirenAppColors.grey500, - dropdownHighlightColor: SirenAppColors.grey300, filterIconBorderColor: SirenAppColors.filterIconBorderLight, filterBadgeColor: SirenAppColors.filterBadgeLight, filterDropdownBackgroundColor: SirenAppColors.filterDropdownBackgroundLight, filterCheckboxCheckedColor: SirenAppColors.filterCheckboxCheckedLight, filterCheckboxUncheckedColor: SirenAppColors.filterCheckboxUncheckedLight, - menuActionTextColor: SirenAppColors.menuActionTextColorLight, + filterActionTextColor: SirenAppColors.menuActionTextColorLight, + filterIconColor: SirenAppColors.grey500, + checkIconColor: SirenAppColors.checkIconColorLight, ); diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 2784440..662e878 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -31,7 +31,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final List selectedValues; final void Function(String)? onCategorySelected; final Widget Function(String)? dropdownItemBuilder; - final CategoryStyle? categoryStyle; + final FilterStyles? categoryStyle; final String? placeholderText; @override @@ -199,7 +199,7 @@ class _CategoryFilter extends StatelessWidget { position.dx + button.size.width, 0, ), - color: colors?.categoryColors?.filterDropdownBackgroundColor ?? + color: colors?.filterColors?.filterDropdownBackgroundColor ?? defaultColors.filterDropdownBackgroundColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), items: categories.map((category) { @@ -213,24 +213,24 @@ class _CategoryFilter extends StatelessWidget { height: 24, decoration: BoxDecoration( color: isSelected - ? colors?.categoryColors?.filterCheckboxCheckedColor ?? + ? colors?.filterColors?.filterCheckboxCheckedColor ?? defaultColors.filterCheckboxCheckedColor : Colors.transparent, border: Border.all( color: isSelected - ? colors?.categoryColors?.filterCheckboxCheckedColor ?? + ? colors?.filterColors?.filterCheckboxCheckedColor ?? defaultColors.filterCheckboxCheckedColor - : colors?.categoryColors - ?.filterCheckboxUncheckedColor ?? + : colors?.filterColors?.filterCheckboxUncheckedColor ?? defaultColors.filterCheckboxUncheckedColor, width: 2, ), borderRadius: BorderRadius.circular(8), ), child: isSelected - ? const Icon( + ? Icon( Icons.check, - color: Colors.white, + color: colors?.filterColors?.checkIconColor ?? + defaultColors.checkIconColor, size: 16, ) : null, @@ -240,8 +240,8 @@ class _CategoryFilter extends StatelessWidget { category, style: TextStyle( fontSize: 14, - color: colors?.categoryColors?.menuActionTextColor ?? - defaultColors.menuActionTextColor, + color: colors?.filterColors?.filterActionTextColor ?? + defaultColors.filterActionTextColor, ), ), ], @@ -266,13 +266,17 @@ class _CategoryFilter extends StatelessWidget { height: 44, decoration: BoxDecoration( border: Border.all( - color: colors?.categoryColors?.filterIconBorderColor ?? + color: colors?.filterColors?.filterIconBorderColor ?? defaultColors.filterIconBorderColor, ), borderRadius: BorderRadius.circular(8), ), child: IconButton( - icon: const Icon(Icons.filter_alt_outlined), + icon: Icon( + Icons.filter_alt_outlined, + color: colors?.filterColors?.filterIconColor ?? + defaultColors.filterIconColor, + ), onPressed: () => _showFilterMenu(context), ), ), @@ -284,7 +288,7 @@ class _CategoryFilter extends StatelessWidget { width: 22, height: 22, decoration: BoxDecoration( - color: colors?.categoryColors?.filterBadgeColor ?? + color: colors?.filterColors?.filterBadgeColor ?? defaultColors.filterBadgeColor, shape: BoxShape.circle, ), From 7e73031779a30db24b2c8d5360213866c1b5ab7e Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 14:27:44 +0530 Subject: [PATCH 09/20] fix: Add changes to keep the menu bar open --- lib/src/widgets/app_bar.dart | 295 +++++++++++++++++++++++------------ 1 file changed, 197 insertions(+), 98 deletions(-) diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 662e878..a738961 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -170,7 +170,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { } } -class _CategoryFilter extends StatelessWidget { +class _CategoryFilter extends StatefulWidget { const _CategoryFilter({ required this.categories, required this.selectedValues, @@ -185,122 +185,221 @@ class _CategoryFilter extends StatelessWidget { final CustomThemeColors? colors; final AppColors defaultColors; - Future _showFilterMenu(BuildContext context) async { - final button = context.findRenderObject()! as RenderBox; - final overlay = - Overlay.of(context).context.findRenderObject()! as RenderBox; - final position = button.localToGlobal(Offset.zero, ancestor: overlay); + @override + State<_CategoryFilter> createState() => _CategoryFilterState(); +} - await showMenu( - context: context, - position: RelativeRect.fromLTRB( - position.dx, - position.dy + button.size.height, - position.dx + button.size.width, - 0, - ), - color: colors?.filterColors?.filterDropdownBackgroundColor ?? - defaultColors.filterDropdownBackgroundColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - items: categories.map((category) { - final isSelected = selectedValues.contains(category); - return PopupMenuItem( - value: category, - child: Row( +class _CategoryFilterState extends State<_CategoryFilter> { + final LayerLink _layerLink = LayerLink(); + OverlayEntry? _overlayEntry; + bool _isDropdownOpen = false; + + void _toggleDropdown() { + if (_isDropdownOpen) { + _removeOverlay(); + } else { + _showOverlay(); + } + } + + void _showOverlay() { + _overlayEntry = _createOverlayEntry(); + Overlay.of(context).insert(_overlayEntry!); + _isDropdownOpen = true; + } + + void _removeOverlay() { + _overlayEntry?.remove(); + _overlayEntry = null; + _isDropdownOpen = false; + } + + void _handleItemSelection(String category) { + if (widget.onSelectionChanged != null) { + widget.onSelectionChanged?.call(category); + // Update the overlay to reflect the new selection + _overlayEntry?.markNeedsBuild(); + } + } + + OverlayEntry _createOverlayEntry() { + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + return OverlayEntry( + builder: (context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _removeOverlay, + child: Stack( children: [ - Container( - width: 24, - height: 24, - decoration: BoxDecoration( - color: isSelected - ? colors?.filterColors?.filterCheckboxCheckedColor ?? - defaultColors.filterCheckboxCheckedColor - : Colors.transparent, - border: Border.all( - color: isSelected - ? colors?.filterColors?.filterCheckboxCheckedColor ?? - defaultColors.filterCheckboxCheckedColor - : colors?.filterColors?.filterCheckboxUncheckedColor ?? - defaultColors.filterCheckboxUncheckedColor, - width: 2, + Positioned( + left: offset.dx, + top: offset.dy + size.height + 8, + width: 220, + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: Offset(-220 + size.width, size.height), + child: Material( + color: widget.colors?.filterColors + ?.filterDropdownBackgroundColor ?? + widget.defaultColors.filterDropdownBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 4, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: NotificationListener( + onNotification: (_) => true, + child: ListView.builder( + shrinkWrap: true, + itemCount: widget.categories.length, + itemBuilder: (context, index) { + final category = widget.categories[index]; + final isSelected = + widget.selectedValues.contains(category); + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _handleItemSelection(category), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + children: [ + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: isSelected + ? widget.colors?.filterColors + ?.filterCheckboxCheckedColor ?? + widget.defaultColors + .filterCheckboxCheckedColor + : Colors.transparent, + border: Border.all( + color: isSelected + ? widget.colors?.filterColors + ?.filterCheckboxCheckedColor ?? + widget.defaultColors + .filterCheckboxCheckedColor + : widget.colors?.filterColors + ?.filterCheckboxUncheckedColor ?? + widget.defaultColors + .filterCheckboxUncheckedColor, + width: 2, + ), + borderRadius: + BorderRadius.circular(8), + ), + child: isSelected + ? Center( + child: Icon( + Icons.check, + color: widget + .colors + ?.filterColors + ?.checkIconColor ?? + widget.defaultColors + .checkIconColor, + size: 16, + ), + ) + : null, + ), + const SizedBox(width: 12), + Text( + category, + style: TextStyle( + fontSize: 14, + color: widget.colors?.filterColors + ?.filterActionTextColor ?? + widget.defaultColors + .filterActionTextColor, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ), + ), ), - borderRadius: BorderRadius.circular(8), - ), - child: isSelected - ? Icon( - Icons.check, - color: colors?.filterColors?.checkIconColor ?? - defaultColors.checkIconColor, - size: 16, - ) - : null, - ), - const SizedBox(width: 12), - Text( - category, - style: TextStyle( - fontSize: 14, - color: colors?.filterColors?.filterActionTextColor ?? - defaultColors.filterActionTextColor, ), ), ], ), - onTap: () { - if (onSelectionChanged != null) { - onSelectionChanged?.call(category); - } - }, ); - }).toList(), + }, ); } + @override + void dispose() { + _removeOverlay(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Stack( - clipBehavior: Clip.none, - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - border: Border.all( - color: colors?.filterColors?.filterIconBorderColor ?? - defaultColors.filterIconBorderColor, + return CompositedTransformTarget( + link: _layerLink, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + border: Border.all( + color: widget.colors?.filterColors?.filterIconBorderColor ?? + widget.defaultColors.filterIconBorderColor, + ), + borderRadius: BorderRadius.circular(8), ), - borderRadius: BorderRadius.circular(8), - ), - child: IconButton( - icon: Icon( - Icons.filter_alt_outlined, - color: colors?.filterColors?.filterIconColor ?? - defaultColors.filterIconColor, + child: IconButton( + icon: Icon( + Icons.filter_alt_outlined, + color: widget.colors?.filterColors?.filterIconColor ?? + widget.defaultColors.filterIconColor, + ), + onPressed: _toggleDropdown, + padding: EdgeInsets.zero, + iconSize: 24, ), - onPressed: () => _showFilterMenu(context), ), - ), - if (selectedValues.isNotEmpty) - Positioned( - right: -11, - top: -4, - child: Container( - width: 22, - height: 22, - decoration: BoxDecoration( - color: colors?.filterColors?.filterBadgeColor ?? - defaultColors.filterBadgeColor, - shape: BoxShape.circle, - ), - child: Center( - child: Text( - '${selectedValues.length}', - style: const TextStyle(color: Colors.white, fontSize: 12), + if (widget.selectedValues.isNotEmpty) + Positioned( + right: -11, + top: -4, + child: Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: widget.colors?.filterColors?.filterBadgeColor ?? + widget.defaultColors.filterBadgeColor, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + '${widget.selectedValues.length}', + style: const TextStyle(color: Colors.white, fontSize: 12), + ), ), ), ), - ), - ], + ], + ), ); } } From 6535d49c8ebb6789bc7e1e1d32fb267039abd9ca Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 16:30:23 +0530 Subject: [PATCH 10/20] feat: Add custom widget for filter --- lib/src/models/ui_models.dart | 36 ++++++------- lib/src/widgets/app_bar.dart | 91 +++++++++++++++++++------------- lib/src/widgets/siren_inbox.dart | 28 +++++----- 3 files changed, 84 insertions(+), 71 deletions(-) diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 313b2f4..a7e5377 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -100,6 +100,7 @@ class CustomStyles { this.clearAllIconStyle, this.tabStyles, this.hideTabMargin, + this.filterStyles, }); /// The decoration for the Siren inbox list. @@ -126,9 +127,14 @@ class CustomStyles { /// Style of clear all icon in inbox default header final ClearAllIconStyle? clearAllIconStyle; + /// Styles for customizing the appearance of tabs in the Siren inbox final TabStyles? tabStyles; + /// Controls whether to hide margins above and below the tabs final HideTabMargin? hideTabMargin; + + /// Styles for customizing the appearance of category filters + final FilterStyles? filterStyles; } class HideTabMargin { @@ -516,10 +522,7 @@ class FilterStyles { const FilterStyles({ this.container, this.dropdownTextStyle, - this.selectedTextStyle, - this.placeholderTextStyle, this.boxDecoration, - this.placeholderText, }); /// The container style for the category dropdown. @@ -528,35 +531,26 @@ class FilterStyles { /// The text style for dropdown items. final TextStyle? dropdownTextStyle; - /// The text style for selected items in the dropdown. - final TextStyle? selectedTextStyle; - - /// The text style for the placeholder text. - final TextStyle? placeholderTextStyle; - /// The box decoration for the category dropdown. final BoxDecoration? boxDecoration; - - /// The placeholder text to show when no category is selected. - final String? placeholderText; } /// Properties for configuring the appearance and behavior of the category dropdown. -class CategoryParams { - /// Constructs a [CategoryParams] with optional parameters. - const CategoryParams({ - this.showCategories = false, - this.dropdownItemBuilder, +class FilterParams { + /// Constructs a [FilterParams] with optional parameters. + const FilterParams({ + this.showFilters = false, + this.filterWidget, this.style, this.placeholderText, }); /// Flag to show the categories dropdown in the app bar. - final bool showCategories; + final bool showFilters; - /// Custom builder for dropdown menu items. - /// If not provided, a default Text widget will be used. - final Widget Function(String)? dropdownItemBuilder; + /// Custom widget to display the filter UI. + /// If not provided, a default filter UI will be used. + final Widget? filterWidget; /// Style properties for the category dropdown. final FilterStyles? style; diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index a738961..a899491 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -15,7 +15,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.categories = const [], this.selectedValues = const [], this.onCategorySelected, - this.dropdownItemBuilder, + this.filterWidget, this.categoryStyle, this.placeholderText, super.key, @@ -30,7 +30,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final List categories; final List selectedValues; final void Function(String)? onCategorySelected; - final Widget Function(String)? dropdownItemBuilder; + final Widget? filterWidget; final FilterStyles? categoryStyle; final String? placeholderText; @@ -114,12 +114,18 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { if (categories.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: _CategoryFilter( - categories: categories, - selectedValues: selectedValues, - onSelectionChanged: onCategorySelected, - colors: colors, - defaultColors: defaultColors, + child: SizedBox( + width: 44, + height: 44, + child: _CategoryFilter( + categories: categories, + selectedValues: selectedValues, + onSelectionChanged: onCategorySelected, + colors: colors, + defaultColors: defaultColors, + styles: styles, + filterWidget: filterWidget, + ), ), ), if (!(headerParams?.hideClearAll ?? false)) @@ -177,6 +183,8 @@ class _CategoryFilter extends StatefulWidget { required this.onSelectionChanged, required this.colors, required this.defaultColors, + required this.styles, + this.filterWidget, }); final List categories; @@ -184,6 +192,8 @@ class _CategoryFilter extends StatefulWidget { final void Function(String)? onSelectionChanged; final CustomThemeColors? colors; final AppColors defaultColors; + final Widget? filterWidget; + final CustomStyles? styles; @override State<_CategoryFilter> createState() => _CategoryFilterState(); @@ -223,7 +233,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { } OverlayEntry _createOverlayEntry() { - final renderBox = context.findRenderObject() as RenderBox; + final renderBox = context.findRenderObject()! as RenderBox; final size = renderBox.size; final offset = renderBox.localToGlobal(Offset.zero); @@ -317,13 +327,21 @@ class _CategoryFilterState extends State<_CategoryFilter> { const SizedBox(width: 12), Text( category, - style: TextStyle( - fontSize: 14, - color: widget.colors?.filterColors - ?.filterActionTextColor ?? - widget.defaultColors - .filterActionTextColor, - ), + style: widget.styles?.filterStyles + ?.dropdownTextStyle + ?.copyWith( + color: widget.colors?.filterColors + ?.filterActionTextColor ?? + widget.defaultColors + .filterActionTextColor, + ) ?? + TextStyle( + fontSize: 14, + color: widget.colors?.filterColors + ?.filterActionTextColor ?? + widget.defaultColors + .filterActionTextColor, + ), ), ], ), @@ -357,27 +375,28 @@ class _CategoryFilterState extends State<_CategoryFilter> { child: Stack( clipBehavior: Clip.none, children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - border: Border.all( - color: widget.colors?.filterColors?.filterIconBorderColor ?? - widget.defaultColors.filterIconBorderColor, - ), - borderRadius: BorderRadius.circular(8), - ), - child: IconButton( - icon: Icon( - Icons.filter_alt_outlined, - color: widget.colors?.filterColors?.filterIconColor ?? - widget.defaultColors.filterIconColor, + widget.filterWidget ?? + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + border: Border.all( + color: widget.colors?.filterColors?.filterIconBorderColor ?? + widget.defaultColors.filterIconBorderColor, + ), + borderRadius: BorderRadius.circular(8), + ), + child: IconButton( + icon: Icon( + Icons.filter_alt_outlined, + color: widget.colors?.filterColors?.filterIconColor ?? + widget.defaultColors.filterIconColor, + ), + onPressed: _toggleDropdown, + padding: EdgeInsets.zero, + iconSize: 24, + ), ), - onPressed: _toggleDropdown, - padding: EdgeInsets.zero, - iconSize: 24, - ), - ), if (widget.selectedValues.isNotEmpty) Positioned( right: -11, diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index 4391a01..c450572 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -36,7 +36,7 @@ class SirenInbox extends StatefulWidget { this.theme, this.customStyles, this.customTabIndicator, - this.categoryParams, + this.filterParams, }); /// Flag for enabling dark mode. @@ -84,7 +84,7 @@ class SirenInbox extends StatefulWidget { final BoxDecoration? customTabIndicator; /// Properties for configuring the category dropdown. - final CategoryParams? categoryParams; + final FilterParams? filterParams; @override State createState() => _SirenInboxState(); @@ -586,18 +586,18 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.categoryParams?.showCategories ?? false + categories: widget.filterParams?.showFilters ?? false ? allCategories : const [], - selectedValues: widget.categoryParams?.showCategories ?? false + selectedValues: widget.filterParams?.showFilters ?? false ? selectedCategories : const [], - onCategorySelected: widget.categoryParams?.showCategories ?? false + onCategorySelected: widget.filterParams?.showFilters ?? false ? _updateSelectedCategories : null, - dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, - categoryStyle: widget.categoryParams?.style, - placeholderText: widget.categoryParams?.placeholderText, + filterWidget: widget.filterParams?.filterWidget, + categoryStyle: widget.filterParams?.style, + placeholderText: widget.filterParams?.placeholderText, ), body: Column( children: [ @@ -703,18 +703,18 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.categoryParams?.showCategories ?? false + categories: widget.filterParams?.showFilters ?? false ? allCategories : const [], - selectedValues: widget.categoryParams?.showCategories ?? false + selectedValues: widget.filterParams?.showFilters ?? false ? selectedCategories : const [], - onCategorySelected: widget.categoryParams?.showCategories ?? false + onCategorySelected: widget.filterParams?.showFilters ?? false ? _updateSelectedCategories : null, - dropdownItemBuilder: widget.categoryParams?.dropdownItemBuilder, - categoryStyle: widget.categoryParams?.style, - placeholderText: widget.categoryParams?.placeholderText, + filterWidget: widget.filterParams?.filterWidget, + categoryStyle: widget.filterParams?.style, + placeholderText: widget.filterParams?.placeholderText, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); From 35b5970689dc85cbcfb2a7c63020332a256fc5f2 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 16:38:18 +0530 Subject: [PATCH 11/20] feat: Add params to filter button badge --- lib/src/models/ui_models.dart | 4 ++++ lib/src/widgets/app_bar.dart | 38 ++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index a7e5377..e6083d1 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -543,6 +543,7 @@ class FilterParams { this.filterWidget, this.style, this.placeholderText, + this.hideBadge = false, }); /// Flag to show the categories dropdown in the app bar. @@ -557,4 +558,7 @@ class FilterParams { /// The placeholder text to show when no category is selected. final String? placeholderText; + + /// Flag to hide the badge showing number of selected filters. + final bool hideBadge; } diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index a899491..164a09b 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -18,6 +18,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.filterWidget, this.categoryStyle, this.placeholderText, + this.hideBadge = false, super.key, }); @@ -33,6 +34,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final Widget? filterWidget; final FilterStyles? categoryStyle; final String? placeholderText; + final bool hideBadge; @override Size get preferredSize { @@ -112,19 +114,25 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ if (categories.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: SizedBox( - width: 44, - height: 44, - child: _CategoryFilter( - categories: categories, - selectedValues: selectedValues, - onSelectionChanged: onCategorySelected, - colors: colors, - defaultColors: defaultColors, - styles: styles, - filterWidget: filterWidget, + Semantics( + label: 'siren-filter', + hint: 'Tap to filter notifications by categories', + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SizedBox( + key: const Key('siren-filter'), + width: 44, + height: 44, + child: _CategoryFilter( + categories: categories, + selectedValues: selectedValues, + onSelectionChanged: onCategorySelected, + colors: colors, + defaultColors: defaultColors, + styles: styles, + filterWidget: filterWidget, + hideBadge: hideBadge, + ), ), ), ), @@ -185,6 +193,7 @@ class _CategoryFilter extends StatefulWidget { required this.defaultColors, required this.styles, this.filterWidget, + this.hideBadge = false, }); final List categories; @@ -194,6 +203,7 @@ class _CategoryFilter extends StatefulWidget { final AppColors defaultColors; final Widget? filterWidget; final CustomStyles? styles; + final bool hideBadge; @override State<_CategoryFilter> createState() => _CategoryFilterState(); @@ -397,7 +407,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { iconSize: 24, ), ), - if (widget.selectedValues.isNotEmpty) + if (widget.selectedValues.isNotEmpty && !widget.hideBadge) Positioned( right: -11, top: -4, From a568552ec8fb923c9fdf2dc8b6e681fe301712f2 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 17:31:11 +0530 Subject: [PATCH 12/20] refactor: Remove unused variables and params --- lib/src/models/ui_models.dart | 18 ++-------- lib/src/widgets/app_bar.dart | 58 +++++++++++++++++--------------- lib/src/widgets/siren_inbox.dart | 7 ++-- 3 files changed, 36 insertions(+), 47 deletions(-) diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index e6083d1..38d2de8 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -520,29 +520,20 @@ class TabColors { class FilterStyles { /// Constructs a [FilterStyles] with optional parameters. const FilterStyles({ - this.container, this.dropdownTextStyle, - this.boxDecoration, }); - /// The container style for the category dropdown. - final ContainerStyle? container; - /// The text style for dropdown items. final TextStyle? dropdownTextStyle; - - /// The box decoration for the category dropdown. - final BoxDecoration? boxDecoration; } /// Properties for configuring the appearance and behavior of the category dropdown. class FilterParams { /// Constructs a [FilterParams] with optional parameters. const FilterParams({ - this.showFilters = false, - this.filterWidget, + this.showFilters = true, + this.filterIconWidget, this.style, - this.placeholderText, this.hideBadge = false, }); @@ -551,14 +542,11 @@ class FilterParams { /// Custom widget to display the filter UI. /// If not provided, a default filter UI will be used. - final Widget? filterWidget; + final Widget? filterIconWidget; /// Style properties for the category dropdown. final FilterStyles? style; - /// The placeholder text to show when no category is selected. - final String? placeholderText; - /// Flag to hide the badge showing number of selected filters. final bool hideBadge; } diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 164a09b..f390e77 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -15,9 +15,8 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.categories = const [], this.selectedValues = const [], this.onCategorySelected, - this.filterWidget, + this.filterIconWidget, this.categoryStyle, - this.placeholderText, this.hideBadge = false, super.key, }); @@ -31,9 +30,8 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final List categories; final List selectedValues; final void Function(String)? onCategorySelected; - final Widget? filterWidget; + final Widget? filterIconWidget; final FilterStyles? categoryStyle; - final String? placeholderText; final bool hideBadge; @override @@ -130,7 +128,7 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { colors: colors, defaultColors: defaultColors, styles: styles, - filterWidget: filterWidget, + filterIconWidget: filterIconWidget, hideBadge: hideBadge, ), ), @@ -192,7 +190,7 @@ class _CategoryFilter extends StatefulWidget { required this.colors, required this.defaultColors, required this.styles, - this.filterWidget, + this.filterIconWidget, this.hideBadge = false, }); @@ -201,7 +199,7 @@ class _CategoryFilter extends StatefulWidget { final void Function(String)? onSelectionChanged; final CustomThemeColors? colors; final AppColors defaultColors; - final Widget? filterWidget; + final Widget? filterIconWidget; final CustomStyles? styles; final bool hideBadge; @@ -385,28 +383,32 @@ class _CategoryFilterState extends State<_CategoryFilter> { child: Stack( clipBehavior: Clip.none, children: [ - widget.filterWidget ?? - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - border: Border.all( - color: widget.colors?.filterColors?.filterIconBorderColor ?? - widget.defaultColors.filterIconBorderColor, - ), - borderRadius: BorderRadius.circular(8), - ), - child: IconButton( - icon: Icon( - Icons.filter_alt_outlined, - color: widget.colors?.filterColors?.filterIconColor ?? - widget.defaultColors.filterIconColor, - ), - onPressed: _toggleDropdown, - padding: EdgeInsets.zero, - iconSize: 24, - ), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + border: Border.all( + color: widget.colors?.filterColors?.filterIconBorderColor ?? + widget.defaultColors.filterIconBorderColor, ), + borderRadius: BorderRadius.circular(8), + ), + child: widget.filterIconWidget != null + ? GestureDetector( + onTap: _toggleDropdown, + child: widget.filterIconWidget, + ) + : IconButton( + icon: Icon( + Icons.filter_alt_outlined, + color: widget.colors?.filterColors?.filterIconColor ?? + widget.defaultColors.filterIconColor, + ), + onPressed: _toggleDropdown, + padding: EdgeInsets.zero, + iconSize: 24, + ), + ), if (widget.selectedValues.isNotEmpty && !widget.hideBadge) Positioned( right: -11, diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index c450572..2632c7e 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -595,9 +595,9 @@ class _SirenInboxState extends State onCategorySelected: widget.filterParams?.showFilters ?? false ? _updateSelectedCategories : null, - filterWidget: widget.filterParams?.filterWidget, + filterIconWidget: widget.filterParams?.filterIconWidget, categoryStyle: widget.filterParams?.style, - placeholderText: widget.filterParams?.placeholderText, + hideBadge: widget.filterParams?.hideBadge ?? false, ), body: Column( children: [ @@ -712,9 +712,8 @@ class _SirenInboxState extends State onCategorySelected: widget.filterParams?.showFilters ?? false ? _updateSelectedCategories : null, - filterWidget: widget.filterParams?.filterWidget, + filterIconWidget: widget.filterParams?.filterIconWidget, categoryStyle: widget.filterParams?.style, - placeholderText: widget.filterParams?.placeholderText, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); From 7e9ecded60931878a24aee35962d2a5f4f67de8b Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 17:55:03 +0530 Subject: [PATCH 13/20] tests: Add unit test for filter --- test/models/ui_models_test.dart | 61 ++++++++++++++ test/widgets/app_bar_test.dart | 141 ++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/test/models/ui_models_test.dart b/test/models/ui_models_test.dart index 7ab5d81..e0e0a80 100644 --- a/test/models/ui_models_test.dart +++ b/test/models/ui_models_test.dart @@ -189,4 +189,65 @@ void main() { expect(tabColors.inactiveTabTextColor, Colors.black); }); }); + + group('FilterStyles', () { + test('constructor should initialize dropdownTextStyle with provided value', + () { + const textStyle = TextStyle(color: Colors.red, fontSize: 16); + const filterStyles = FilterStyles(dropdownTextStyle: textStyle); + + expect(filterStyles.dropdownTextStyle, textStyle); + }); + }); + + group('FilterParams', () { + test('constructor should initialize properties with default values', () { + const filterParams = FilterParams(); + + expect(filterParams.showFilters, true); + expect(filterParams.filterIconWidget, null); + expect(filterParams.style, null); + expect(filterParams.hideBadge, false); + }); + + test('constructor should initialize properties with provided values', () { + const customIcon = Icon(Icons.tune); + const customStyle = FilterStyles(); + const filterParams = FilterParams( + showFilters: false, + filterIconWidget: customIcon, + style: customStyle, + hideBadge: true, + ); + + expect(filterParams.showFilters, false); + expect(filterParams.filterIconWidget, customIcon); + expect(filterParams.style, customStyle); + expect(filterParams.hideBadge, true); + }); + }); + + group('FilterColors', () { + test('constructor should initialize properties with provided values', () { + final filterColors = FilterColors( + filterIconBorderColor: Colors.red, + filterBadgeColor: Colors.blue, + filterDropdownBackgroundColor: Colors.green, + filterCheckboxCheckedColor: Colors.yellow, + filterCheckboxUncheckedColor: Colors.purple, + filterActionTextColor: Colors.orange, + filterIconColor: Colors.pink, + checkIconColor: Colors.brown, + ); + + expect(filterColors.filterIconBorderColor, Colors.red); + expect(filterColors.filterBadgeColor, Colors.blue); + expect(filterColors.filterDropdownBackgroundColor, Colors.green); + expect(filterColors.filterCheckboxCheckedColor, Colors.yellow); + expect(filterColors.filterCheckboxUncheckedColor, Colors.purple); + expect(filterColors.filterActionTextColor, Colors.orange); + expect(filterColors.filterIconColor, Colors.pink); + expect(filterColors.checkIconColor, Colors.brown); + }); + }); } diff --git a/test/widgets/app_bar_test.dart b/test/widgets/app_bar_test.dart index 5e2af73..0cd9a92 100644 --- a/test/widgets/app_bar_test.dart +++ b/test/widgets/app_bar_test.dart @@ -132,4 +132,145 @@ void main() { await tester.tap(find.text('Clear All')); expect(clearAllPressed, true); }); + + // New tests for filter functionality + testWidgets('SirenAppBar displays filter icon when categories are provided', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + categories: const ['Category 1', 'Category 2'], + ), + ), + ), + ); + + expect(find.byIcon(Icons.filter_alt_outlined), findsOneWidget); + }); + + testWidgets( + 'SirenAppBar does not display filter icon when no categories are provided', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + ), + ), + ), + ); + + expect(find.byIcon(Icons.filter_alt_outlined), findsNothing); + }); + + testWidgets('SirenAppBar displays filter badge with selected count', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + categories: const ['Category 1', 'Category 2'], + selectedValues: const ['Category 1'], + ), + ), + ), + ); + + expect(find.text('1'), findsOneWidget); + }); + + testWidgets( + 'SirenAppBar does not display filter badge when hideBadge is true', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + categories: const ['Category 1', 'Category 2'], + selectedValues: const ['Category 1'], + hideBadge: true, + ), + ), + ), + ); + + expect(find.text('1'), findsNothing); + }); + + testWidgets( + 'SirenAppBar calls onCategorySelected when filter item is selected', + (WidgetTester tester) async { + String? selectedCategory; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + categories: const ['Category 1', 'Category 2'], + onCategorySelected: (category) { + selectedCategory = category; + }, + ), + ), + ), + ); + + // Open filter dropdown + await tester.tap(find.byIcon(Icons.filter_alt_outlined)); + await tester.pumpAndSettle(); + + // Select a category + await tester.tap(find.text('Category 1')); + await tester.pumpAndSettle(); + + expect(selectedCategory, 'Category 1'); + }); + + testWidgets('SirenAppBar uses custom filter icon widget when provided', + (WidgetTester tester) async { + const customIcon = Icon(Icons.tune); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: SirenAppBar( + headerParams: HeaderParams( + title: 'Title', + showBackButton: false, + ), + isNonEmptyNotifications: false, + categories: const ['Category 1', 'Category 2'], + filterIconWidget: customIcon, + ), + ), + ), + ); + + expect(find.byIcon(Icons.tune), findsOneWidget); + expect(find.byIcon(Icons.filter_alt_outlined), findsNothing); + }); } From 34019f7a7d33757c7324a95b44f6505004ef7914 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Wed, 28 May 2025 18:19:48 +0530 Subject: [PATCH 14/20] docs: Add changes in Readme --- README.md | 186 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 144 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 6745f4f..a870206 100644 --- a/README.md +++ b/README.md @@ -101,78 +101,180 @@ Given below are the arguments of Siren Inbox Widget. | Arguments | Description | Type | Default value | | ----------------- | -------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | darkMode | Toggle to enable dark mode when custom theme is not passed | bool | false | -| hideTab | Toggle to enable all and unread tabs | bool | false | -| itemsPerFetch | Number of notifications fetch per api request (have a max cap of 50) | int | 20 | +| hideTab | Toggle to hide the tab bar | bool | false | +| itemsPerFetch | Number of notifications fetch per api request (max 50) | int | 20 | | listEmptyWidget | Custom widget for empty notification list | Widget | null | -| customCard | Custom widget to display the notification cards | Widget | null | +| customCard | Custom builder for notification cards | Widget Function(NotificationType) | null | | customLoader | Custom widget to display the initial loading state | Widget | null | | customErrorWidget | Custom error widget | Widget | null | | cardParams | Properties of notification card | CardParams | CardParams(hideAvatar: false, disableAutoMarkAsRead: false, hideDelete: false, deleteIcon: Icon(Icons.close), onAvatarClick: Function(NotificationType), hideMediaThumbnail: false, onMediaThumbnailClick: Function(NotificationType)) | -| headerParams | Properties of notification window header | HeaderParams | HeaderParams(hideHeader: false, hideClearAll: false,title: 'Notifications', customHeader: null showBackButton:false, backButton: null, onBackPress: ()=> null ) | -| tabParams | Properties of tab bar | TabParams | TabParams(tabs: [TabItem(key: 'ALL', title: 'All'), TabItem(key: 'UNREAD', title: 'Unread')], activeTabIndex:0,) | +| headerParams | Properties of notification window header | HeaderParams | HeaderParams(hideHeader: false, hideClearAll: false, title: 'Notifications', customHeader: null, showBackButton: false, backButton: null, onBackPress: null) | +| tabParams | Properties of tab bar | TabParams | TabParams(tabs: [TabItem(key: 'ALL', title: 'All'), TabItem(key: 'UNREAD', title: 'Unread')], activeTabIndex: 0) | | onCardClick | Custom click handler for notification cards | Function(NotificationType) | null | | onError | Callback for handling errors | Function(SirenErrorType) | null | | theme | Theme properties for custom color theme | CustomThemeColors | null | | customStyles | Style properties for custom styling | CustomStyles | null | +| customTabIndicator| Custom decoration for tab indicator | BoxDecoration | null | +| filterParams | Properties for configuring the filter dropdown | FilterParams | FilterParams(showFilters: true, filterIconWidget: null, style: null, hideBadge: false) | #### Theme customization -Here are some of the available theme options: +Here are the available theme options: ```dart theme: CustomThemeColors( - primary: Colors.blue, - highlightedCardColor: Colors.blueAccent, - textColor: Colors.green, - cardColors: CardColors( - titleColor: Colors.grey, - subtitleColor: Colors.grey, - ), - inboxHeaderColors: InboxHeaderColors( - titleColor: Colors.redAccent, - headerActionColor: Colors.purpleAccent, - borderColor: Colors.cyanAccent - ), - ), + backgroundColor: Colors.blue, + primary: Colors.blueAccent, + highlightedCardColor: Colors.blue.shade100, + borderColor: Colors.grey.shade300, + deleteIcon: Colors.red, + clearAllIcon: Colors.grey, + textColor: Colors.black87, + dateColor: Colors.grey, + timerIcon: Colors.blue, + notificationIconColor: Colors.blue, + loaderColor: Colors.blue, + inboxHeaderColors: InboxHeaderColors( + background: Colors.white, + titleColor: Colors.black87, + headerActionColor: Colors.blue, + borderColor: Colors.grey.shade300 + ), + badgeColors: BadgeColors( + backgroundColor: Colors.red, + color: Colors.white + ), + cardColors: CardColors( + borderColor: Colors.grey.shade300, + background: Colors.white, + titleColor: Colors.black87, + subtitleColor: Colors.grey, + descriptionColor: Colors.black54 + ), + tabColors: TabColors( + containerBackgroundColor: Colors.white, + activeTabBackgroundColor: Colors.blue.shade50, + activeTabTextColor: Colors.blue, + inactiveTabTextColor: Colors.grey, + indicatorColor: Colors.blue + ), + filterColors: FilterColors( + filterIconBorderColor: Colors.grey.shade300, + filterBadgeColor: Colors.blue, + filterDropdownBackgroundColor: Colors.white, + filterCheckboxCheckedColor: Colors.blue, + filterCheckboxUncheckedColor: Colors.grey.shade300, + filterActionTextColor: Colors.black87, + filterIconColor: Colors.blue, + checkIconColor: Colors.white + ) +) ``` -#### Style options +#### Style customization -Here are some of the custom style options for the notification inbox: +Here are the custom style options for the notification inbox: ```dart customStyles: CustomStyles( container: ContainerStyle( - padding: EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.yellow)), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8) + ), + margin: EdgeInsets.all(8) + ), cardStyle: CardStyle( cardContainer: ContainerStyle( - padding: EdgeInsets.all(20), + padding: EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.yellow, - border: Border.all(color: Colors.red))), - cardTitle: TextStyle(fontSize: 22, fontWeight: FontWeight.w800), - cardSubtitle: - TextStyle(fontSize: 20, fontWeight: FontWeight.w700), - cardDescription: - TextStyle(fontSize: 18, fontWeight: FontWeight.w600), - dateStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - avatarSize: 30, + color: Colors.white, + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8) + ) + ), + cardTitle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87 + ), + cardSubtitle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey + ), + cardDescription: TextStyle( + fontSize: 14, + color: Colors.black54 ), + dateStyle: TextStyle( + fontSize: 12, + color: Colors.grey + ), + avatarSize: 40 + ), appBarStyle: InboxHeaderStyle( - headerTextStyle: - TextStyle(fontSize: 20, fontWeight: FontWeight.w900), - titlePadding: EdgeInsets.symmetric(horizontal: 30), - borderWidth: 5), - timerIconStyle: TimerIconStyle(size: 30), - deleteIconStyle: DeleteIconStyle(size: 30), - clearAllIconStyle: ClearAllIconStyle(size: 30), -), + headerTextStyle: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87 + ), + titlePadding: EdgeInsets.symmetric(horizontal: 16), + borderWidth: 1 + ), + notificationIconStyle: NotificationIconStyle( + size: 24 + ), + badgeStyle: BadgeStyle( + fontSize: 12, + size: 20, + top: 0, + right: 2 + ), + timerIconStyle: TimerIconStyle( + size: 20 + ), + deleteIconStyle: DeleteIconStyle( + size: 20 + ), + clearAllIconStyle: ClearAllIconStyle( + size: 20 + ), + tabStyles: TabStyles( + containerStyle: ContainerStyle( + padding: EdgeInsets.symmetric(horizontal: 16), + margin: EdgeInsets.only(bottom: 8) + ), + activeTabTextStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.blue + ), + inActiveTabTextStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey + ), + indicatorSize: 2, + indicatorPadding: EdgeInsets.symmetric(horizontal: 16) + ), + hideTabMargin: HideTabMargin( + upper: false, + lower: false + ), + filterStyles: FilterStyles( + dropdownTextStyle: TextStyle( + fontSize: 14, + color: Colors.black87 + ) + ) +) ``` ## 3. Siren Class -The `Siren Class` provides utility functions for modifying notifications. +The ``Siren Class` provides utility functions for modifying notifications. ```dart Siren.markAsRead(id: 'notification-id'); From 76e696fe972485f5f0ec93e776e207791d4aba97 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 09:45:59 +0530 Subject: [PATCH 15/20] refactor: Revert env back --- README.md | 2 +- env | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a870206..134aded 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Given below are the arguments of Siren Inbox Widget. | Arguments | Description | Type | Default value | | ----------------- | -------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | darkMode | Toggle to enable dark mode when custom theme is not passed | bool | false | -| hideTab | Toggle to hide the tab bar | bool | false | +| hideTab | Toggle to enable all and unread tabs | bool | false | | itemsPerFetch | Number of notifications fetch per api request (max 50) | int | 20 | | listEmptyWidget | Custom widget for empty notification list | Widget | null | | customCard | Custom builder for notification cards | Widget Function(NotificationType) | null | diff --git a/env b/env index 3d21324..77317ce 100644 --- a/env +++ b/env @@ -1 +1 @@ -API_DOMAIN = https://api.dev.trysiren.io/api/ \ No newline at end of file +API_DOMAIN = https://api.trysiren.io/api/ \ No newline at end of file From 553ce9cd9e968c2a6114e3f5a3606d21a1a62a2c Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 10:10:31 +0530 Subject: [PATCH 16/20] fix: Add fix for margin in app bar --- README.md | 2 +- lib/src/widgets/app_bar.dart | 140 +++++++++++++++++------------------ 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 134aded..cf78193 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ customStyles: CustomStyles( ## 3. Siren Class -The ``Siren Class` provides utility functions for modifying notifications. +The `Siren Class` provides utility functions for modifying notifications. ```dart Siren.markAsRead(id: 'notification-id'); diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index f390e77..3537b6e 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -62,13 +62,13 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { : null, ), height: preferredSize.height, - child: headerParams?.customHeader ?? - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(right: 16, left: 20), - child: Row( + child: Container( + margin: const EdgeInsets.only(right: 16, left: 20), + child: headerParams?.customHeader ?? + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ if (headerParams?.showBackButton ?? false) Semantics( @@ -107,77 +107,77 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { ), ], ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (categories.isNotEmpty) - Semantics( - label: 'siren-filter', - hint: 'Tap to filter notifications by categories', - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: SizedBox( - key: const Key('siren-filter'), - width: 44, - height: 44, - child: _CategoryFilter( - categories: categories, - selectedValues: selectedValues, - onSelectionChanged: onCategorySelected, - colors: colors, - defaultColors: defaultColors, - styles: styles, - filterIconWidget: filterIconWidget, - hideBadge: hideBadge, + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (categories.isNotEmpty) + Semantics( + label: 'siren-filter', + hint: 'Tap to filter notifications by categories', + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SizedBox( + key: const Key('siren-filter'), + width: 44, + height: 44, + child: _CategoryFilter( + categories: categories, + selectedValues: selectedValues, + onSelectionChanged: onCategorySelected, + colors: colors, + defaultColors: defaultColors, + styles: styles, + filterIconWidget: filterIconWidget, + hideBadge: hideBadge, + ), ), ), ), - ), - if (!(headerParams?.hideClearAll ?? false)) - Semantics( - label: 'siren-header-clear-all', - hint: 'Tap to clear all notifications', - child: GestureDetector( - key: const Key('siren-header-clear-all'), - onTap: () { - if (isNonEmptyNotifications && - onClearAllPressed != null) { - onClearAllPressed!(); - } - }, - child: Opacity( - opacity: isNonEmptyNotifications ? 1 : 0.4, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(right: 4), - child: Icon( - Icons.clear_all, - size: styles?.clearAllIconStyle?.size ?? 24, - color: colors?.clearAllIcon ?? - defaultColors.appBarActionText, + if (!(headerParams?.hideClearAll ?? false)) + Semantics( + label: 'siren-header-clear-all', + hint: 'Tap to clear all notifications', + child: GestureDetector( + key: const Key('siren-header-clear-all'), + onTap: () { + if (isNonEmptyNotifications && + onClearAllPressed != null) { + onClearAllPressed!(); + } + }, + child: Opacity( + opacity: isNonEmptyNotifications ? 1 : 0.4, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon( + Icons.clear_all, + size: styles?.clearAllIconStyle?.size ?? 24, + color: colors?.clearAllIcon ?? + defaultColors.appBarActionText, + ), ), - ), - Text( - Strings.clear_all, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: colors?.inboxHeaderColors - ?.headerActionColor ?? - defaultColors.appBarActionText, + Text( + Strings.clear_all, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colors?.inboxHeaderColors + ?.headerActionColor ?? + defaultColors.appBarActionText, + ), ), - ), - ], + ], + ), ), ), ), - ), - ], - ), - ], - ), + ], + ), + ], + ), + ), ); } } From 6e6f4e3ed38f9f2e2fb5d1075fa8fd9568a92e27 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 12:46:09 +0530 Subject: [PATCH 17/20] refactor: Some improvements in app bar --- lib/src/widgets/app_bar.dart | 94 +++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index 3537b6e..ecffe0a 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -269,7 +269,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { ), elevation: 4, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.only(bottom: 8), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 300), child: NotificationListener( @@ -287,7 +287,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { onTap: () => _handleItemSelection(category), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 12, + horizontal: 14, vertical: 8, ), child: Row( @@ -315,7 +315,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { width: 2, ), borderRadius: - BorderRadius.circular(8), + BorderRadius.circular(6), ), child: isSelected ? Center( @@ -333,23 +333,32 @@ class _CategoryFilterState extends State<_CategoryFilter> { : null, ), const SizedBox(width: 12), - Text( - category, - style: widget.styles?.filterStyles - ?.dropdownTextStyle - ?.copyWith( - color: widget.colors?.filterColors - ?.filterActionTextColor ?? - widget.defaultColors - .filterActionTextColor, - ) ?? - TextStyle( - fontSize: 14, - color: widget.colors?.filterColors - ?.filterActionTextColor ?? - widget.defaultColors - .filterActionTextColor, - ), + Expanded( + child: Text( + category.isEmpty + ? 'Others' + : category, + overflow: TextOverflow.ellipsis, + style: widget.styles?.filterStyles + ?.dropdownTextStyle + ?.copyWith( + color: widget + .colors + ?.filterColors + ?.filterActionTextColor ?? + widget.defaultColors + .filterActionTextColor, + ) ?? + TextStyle( + fontSize: 14, + color: widget + .colors + ?.filterColors + ?.filterActionTextColor ?? + widget.defaultColors + .filterActionTextColor, + ), + ), ), ], ), @@ -383,31 +392,26 @@ class _CategoryFilterState extends State<_CategoryFilter> { child: Stack( clipBehavior: Clip.none, children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - border: Border.all( - color: widget.colors?.filterColors?.filterIconBorderColor ?? - widget.defaultColors.filterIconBorderColor, + GestureDetector( + onTap: _toggleDropdown, + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + border: Border.all( + color: widget.colors?.filterColors?.filterIconBorderColor ?? + widget.defaultColors.filterIconBorderColor, + ), + borderRadius: BorderRadius.circular(8), ), - borderRadius: BorderRadius.circular(8), - ), - child: widget.filterIconWidget != null - ? GestureDetector( - onTap: _toggleDropdown, - child: widget.filterIconWidget, - ) - : IconButton( - icon: Icon( - Icons.filter_alt_outlined, - color: widget.colors?.filterColors?.filterIconColor ?? - widget.defaultColors.filterIconColor, - ), - onPressed: _toggleDropdown, - padding: EdgeInsets.zero, - iconSize: 24, + child: widget.filterIconWidget ?? + Icon( + Icons.filter_alt_outlined, + color: widget.colors?.filterColors?.filterIconColor ?? + widget.defaultColors.filterIconColor, + size: 24, ), + ), ), if (widget.selectedValues.isNotEmpty && !widget.hideBadge) Positioned( @@ -423,8 +427,8 @@ class _CategoryFilterState extends State<_CategoryFilter> { ), child: Center( child: Text( - '${widget.selectedValues.length}', - style: const TextStyle(color: Colors.white, fontSize: 12), + '${widget.selectedValues.length > 99 ? '99+' : widget.selectedValues.length}', + style: const TextStyle(color: Colors.white, fontSize: 10), ), ), ), From be9da0a508aa268e0c69711c869724b690313e86 Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 16:03:08 +0530 Subject: [PATCH 18/20] refactor: Modify the filter params --- README.md | 50 ++++++++++++++++++++++------- lib/src/models/ui_models.dart | 37 +++++++++++++++++---- lib/src/widgets/app_bar.dart | 36 +++++++++++++++------ lib/src/widgets/siren_inbox.dart | 55 +++++++++++++++++++------------- test/models/ui_models_test.dart | 19 ++--------- 5 files changed, 129 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index cf78193..6f116ac 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Given below are the arguments of Siren Inbox Widget. | theme | Theme properties for custom color theme | CustomThemeColors | null | | customStyles | Style properties for custom styling | CustomStyles | null | | customTabIndicator| Custom decoration for tab indicator | BoxDecoration | null | -| filterParams | Properties for configuring the filter dropdown | FilterParams | FilterParams(showFilters: true, filterIconWidget: null, style: null, hideBadge: false) | +| filterParams | Properties for configuring the filter dropdown | FilterParams | FilterParams(categoryFilterParams: CategoryFilterParams(showFilters: true, filterIconWidget: null, hideBadge: false)) | #### Theme customization @@ -159,14 +159,16 @@ theme: CustomThemeColors( indicatorColor: Colors.blue ), filterColors: FilterColors( - filterIconBorderColor: Colors.grey.shade300, - filterBadgeColor: Colors.blue, - filterDropdownBackgroundColor: Colors.white, - filterCheckboxCheckedColor: Colors.blue, - filterCheckboxUncheckedColor: Colors.grey.shade300, - filterActionTextColor: Colors.black87, - filterIconColor: Colors.blue, - checkIconColor: Colors.white + categoryFilterColors: CategoryFilterColors( + filterIconBorderColor: Colors.grey.shade300, + filterBadgeColor: Colors.blue, + filterDropdownBackgroundColor: Colors.white, + filterCheckboxCheckedColor: Colors.blue, + filterCheckboxUncheckedColor: Colors.grey.shade300, + filterActionTextColor: Colors.black87, + filterIconColor: Colors.blue, + checkIconColor: Colors.white + ) ) ) ``` @@ -264,14 +266,38 @@ customStyles: CustomStyles( lower: false ), filterStyles: FilterStyles( - dropdownTextStyle: TextStyle( - fontSize: 14, - color: Colors.black87 + categoryFilterStyles: CategoryFilterStyles( + dropdownTextStyle: TextStyle( + fontSize: 14, + color: Colors.black87 + ) ) ) ) ``` +### 2.4. Filter Configuration + +The filter functionality allows users to filter notifications by categories. Here's how to configure it: + +```dart +SirenInbox( + filterParams: FilterParams( + categoryFilterParams: CategoryFilterParams( + showFilters: true, + filterIconWidget: Icon(Icons.filter_list), // Optional custom filter icon + hideBadge: false // Optional hide badge showing number of selected filters + ) + ) +) +``` + +#### Filter Features: +- Custom filter icon support +- Badge showing number of selected filters (99+ for more than 99 selections) +- Dropdown with checkbox selection +- Customizable colors and styles for all filter components + ## 3. Siren Class The `Siren Class` provides utility functions for modifying notifications. diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 38d2de8..958bc52 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -133,7 +133,7 @@ class CustomStyles { /// Controls whether to hide margins above and below the tabs final HideTabMargin? hideTabMargin; - /// Styles for customizing the appearance of category filters + /// Styles for customizing the appearance of filters final FilterStyles? filterStyles; } @@ -246,7 +246,14 @@ class CustomThemeColors { } class FilterColors { - FilterColors({ + const FilterColors({ + this.categoryFilterColors, + }); + final CategoryFilterColors? categoryFilterColors; +} + +class CategoryFilterColors { + CategoryFilterColors({ this.filterIconBorderColor, this.filterBadgeColor, this.filterDropdownBackgroundColor, @@ -516,10 +523,18 @@ class TabColors { final Color? indicatorColor; } -/// Properties for configuring the appearance of the category dropdown. +/// Styles for customizing the appearance of filters class FilterStyles { - /// Constructs a [FilterStyles] with optional parameters. const FilterStyles({ + this.categoryFilterStyles, + }); + final CategoryFilterStyles? categoryFilterStyles; +} + +/// Properties for configuring the appearance of the category dropdown. +class CategoryFilterStyles { + /// Constructs a [CategoryFilterStyles] with optional parameters. + const CategoryFilterStyles({ this.dropdownTextStyle, }); @@ -527,10 +542,18 @@ class FilterStyles { final TextStyle? dropdownTextStyle; } -/// Properties for configuring the appearance and behavior of the category dropdown. class FilterParams { - /// Constructs a [FilterParams] with optional parameters. const FilterParams({ + this.categoryFilterParams, + }); + + final CategoryFilterParams? categoryFilterParams; +} + +/// Properties for configuring the appearance and behavior of the category dropdown. +class CategoryFilterParams { + /// Constructs a [CategoryFilterParams] with optional parameters. + const CategoryFilterParams({ this.showFilters = true, this.filterIconWidget, this.style, @@ -545,7 +568,7 @@ class FilterParams { final Widget? filterIconWidget; /// Style properties for the category dropdown. - final FilterStyles? style; + final CategoryFilterStyles? style; /// Flag to hide the badge showing number of selected filters. final bool hideBadge; diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index ecffe0a..c420079 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -16,7 +16,6 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { this.selectedValues = const [], this.onCategorySelected, this.filterIconWidget, - this.categoryStyle, this.hideBadge = false, super.key, }); @@ -31,7 +30,6 @@ class SirenAppBar extends StatelessWidget implements PreferredSizeWidget { final List selectedValues; final void Function(String)? onCategorySelected; final Widget? filterIconWidget; - final FilterStyles? categoryStyle; final bool hideBadge; @override @@ -261,7 +259,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { showWhenUnlinked: false, offset: Offset(-220 + size.width, size.height), child: Material( - color: widget.colors?.filterColors + color: widget.colors?.filterColors?.categoryFilterColors ?.filterDropdownBackgroundColor ?? widget.defaultColors.filterDropdownBackgroundColor, shape: RoundedRectangleBorder( @@ -297,18 +295,27 @@ class _CategoryFilterState extends State<_CategoryFilter> { height: 24, decoration: BoxDecoration( color: isSelected - ? widget.colors?.filterColors + ? widget + .colors + ?.filterColors + ?.categoryFilterColors ?.filterCheckboxCheckedColor ?? widget.defaultColors .filterCheckboxCheckedColor : Colors.transparent, border: Border.all( color: isSelected - ? widget.colors?.filterColors + ? widget + .colors + ?.filterColors + ?.categoryFilterColors ?.filterCheckboxCheckedColor ?? widget.defaultColors .filterCheckboxCheckedColor - : widget.colors?.filterColors + : widget + .colors + ?.filterColors + ?.categoryFilterColors ?.filterCheckboxUncheckedColor ?? widget.defaultColors .filterCheckboxUncheckedColor, @@ -324,6 +331,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { color: widget .colors ?.filterColors + ?.categoryFilterColors ?.checkIconColor ?? widget.defaultColors .checkIconColor, @@ -339,12 +347,16 @@ class _CategoryFilterState extends State<_CategoryFilter> { ? 'Others' : category, overflow: TextOverflow.ellipsis, - style: widget.styles?.filterStyles + style: widget + .styles + ?.filterStyles + ?.categoryFilterStyles ?.dropdownTextStyle ?.copyWith( color: widget .colors ?.filterColors + ?.categoryFilterColors ?.filterActionTextColor ?? widget.defaultColors .filterActionTextColor, @@ -354,6 +366,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { color: widget .colors ?.filterColors + ?.categoryFilterColors ?.filterActionTextColor ?? widget.defaultColors .filterActionTextColor, @@ -399,7 +412,8 @@ class _CategoryFilterState extends State<_CategoryFilter> { height: 44, decoration: BoxDecoration( border: Border.all( - color: widget.colors?.filterColors?.filterIconBorderColor ?? + color: widget.colors?.filterColors?.categoryFilterColors + ?.filterIconBorderColor ?? widget.defaultColors.filterIconBorderColor, ), borderRadius: BorderRadius.circular(8), @@ -407,7 +421,8 @@ class _CategoryFilterState extends State<_CategoryFilter> { child: widget.filterIconWidget ?? Icon( Icons.filter_alt_outlined, - color: widget.colors?.filterColors?.filterIconColor ?? + color: widget.colors?.filterColors?.categoryFilterColors + ?.filterIconColor ?? widget.defaultColors.filterIconColor, size: 24, ), @@ -421,7 +436,8 @@ class _CategoryFilterState extends State<_CategoryFilter> { width: 22, height: 22, decoration: BoxDecoration( - color: widget.colors?.filterColors?.filterBadgeColor ?? + color: widget.colors?.filterColors?.categoryFilterColors + ?.filterBadgeColor ?? widget.defaultColors.filterBadgeColor, shape: BoxShape.circle, ), diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index 0918b96..dc2bc0a 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -586,18 +586,22 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.filterParams?.showFilters ?? false - ? allCategories - : const [], - selectedValues: widget.filterParams?.showFilters ?? false - ? selectedCategories - : const [], - onCategorySelected: widget.filterParams?.showFilters ?? false - ? _updateSelectedCategories - : null, - filterIconWidget: widget.filterParams?.filterIconWidget, - categoryStyle: widget.filterParams?.style, - hideBadge: widget.filterParams?.hideBadge ?? false, + categories: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? allCategories + : const [], + selectedValues: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? selectedCategories + : const [], + onCategorySelected: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? _updateSelectedCategories + : null, + filterIconWidget: + widget.filterParams?.categoryFilterParams?.filterIconWidget, + hideBadge: + widget.filterParams?.categoryFilterParams?.hideBadge ?? false, ), body: Column( children: [ @@ -703,17 +707,22 @@ class _SirenInboxState extends State isNonEmptyNotifications: _enableClearAll, headerParams: widget.headerParams, styles: widget.customStyles, - categories: widget.filterParams?.showFilters ?? false - ? allCategories - : const [], - selectedValues: widget.filterParams?.showFilters ?? false - ? selectedCategories - : const [], - onCategorySelected: widget.filterParams?.showFilters ?? false - ? _updateSelectedCategories - : null, - filterIconWidget: widget.filterParams?.filterIconWidget, - categoryStyle: widget.filterParams?.style, + categories: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? allCategories + : const [], + selectedValues: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? selectedCategories + : const [], + onCategorySelected: + widget.filterParams?.categoryFilterParams?.showFilters ?? false + ? _updateSelectedCategories + : null, + filterIconWidget: + widget.filterParams?.categoryFilterParams?.filterIconWidget, + hideBadge: + widget.filterParams?.categoryFilterParams?.hideBadge ?? false, ), body: _buildInboxBody(_inboxScrollController, notifications, false), ); diff --git a/test/models/ui_models_test.dart b/test/models/ui_models_test.dart index e0e0a80..46dd342 100644 --- a/test/models/ui_models_test.dart +++ b/test/models/ui_models_test.dart @@ -190,19 +190,9 @@ void main() { }); }); - group('FilterStyles', () { - test('constructor should initialize dropdownTextStyle with provided value', - () { - const textStyle = TextStyle(color: Colors.red, fontSize: 16); - const filterStyles = FilterStyles(dropdownTextStyle: textStyle); - - expect(filterStyles.dropdownTextStyle, textStyle); - }); - }); - group('FilterParams', () { test('constructor should initialize properties with default values', () { - const filterParams = FilterParams(); + const filterParams = CategoryFilterParams(); expect(filterParams.showFilters, true); expect(filterParams.filterIconWidget, null); @@ -212,24 +202,21 @@ void main() { test('constructor should initialize properties with provided values', () { const customIcon = Icon(Icons.tune); - const customStyle = FilterStyles(); - const filterParams = FilterParams( + const filterParams = CategoryFilterParams( showFilters: false, filterIconWidget: customIcon, - style: customStyle, hideBadge: true, ); expect(filterParams.showFilters, false); expect(filterParams.filterIconWidget, customIcon); - expect(filterParams.style, customStyle); expect(filterParams.hideBadge, true); }); }); group('FilterColors', () { test('constructor should initialize properties with provided values', () { - final filterColors = FilterColors( + final filterColors = CategoryFilterColors( filterIconBorderColor: Colors.red, filterBadgeColor: Colors.blue, filterDropdownBackgroundColor: Colors.green, From 7a55fba83bafad3f43c022d22b890cec96b2ab1c Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 16:15:53 +0530 Subject: [PATCH 19/20] fix: Add few UI fixes related to padding --- lib/src/widgets/app_bar.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/app_bar.dart b/lib/src/widgets/app_bar.dart index c420079..79b76ae 100644 --- a/lib/src/widgets/app_bar.dart +++ b/lib/src/widgets/app_bar.dart @@ -263,7 +263,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { ?.filterDropdownBackgroundColor ?? widget.defaultColors.filterDropdownBackgroundColor, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(12), ), elevation: 4, child: Padding( @@ -319,7 +319,7 @@ class _CategoryFilterState extends State<_CategoryFilter> { ?.filterCheckboxUncheckedColor ?? widget.defaultColors .filterCheckboxUncheckedColor, - width: 2, + width: 1.5, ), borderRadius: BorderRadius.circular(6), From 5decc446a587ea4e40782cb2ab34c74c555a9d2d Mon Sep 17 00:00:00 2001 From: anitta-keyvalue Date: Thu, 29 May 2025 17:19:53 +0530 Subject: [PATCH 20/20] docs: Updated changelog, readme --- CHANGELOG.md | 5 +++++ README.md | 27 +++++++++++++++++++++++++-- lib/src/models/ui_models.dart | 3 +++ lib/src/widgets/siren_inbox.dart | 4 +++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1918b23..a987aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## 1.3.0 + +### Added +- Added support to filter notifications by category. + ## 1.2.1 ### Added diff --git a/README.md b/README.md index 6f116ac..ee9f72b 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Given below are the arguments of Siren Inbox Widget. | theme | Theme properties for custom color theme | CustomThemeColors | null | | customStyles | Style properties for custom styling | CustomStyles | null | | customTabIndicator| Custom decoration for tab indicator | BoxDecoration | null | -| filterParams | Properties for configuring the filter dropdown | FilterParams | FilterParams(categoryFilterParams: CategoryFilterParams(showFilters: true, filterIconWidget: null, hideBadge: false)) | +| filterParams | Properties for configuring the filter dropdown | FilterParams | FilterParams(categoryFilterParams: CategoryFilterParams(showFilters: true, filterIconWidget: null, hideBadge: false, categoryFilterStyles: CategoryFilterStyles(dropdownTextStyle: null))) | #### Theme customization @@ -286,7 +286,13 @@ SirenInbox( categoryFilterParams: CategoryFilterParams( showFilters: true, filterIconWidget: Icon(Icons.filter_list), // Optional custom filter icon - hideBadge: false // Optional hide badge showing number of selected filters + hideBadge: false, // Optional hide badge showing number of selected filters + categoryFilterStyles: CategoryFilterStyles( // Optional custom styles for category filter + dropdownTextStyle: TextStyle( + fontSize: 14, + color: Colors.black87 + ) + ) ) ) ) @@ -298,6 +304,23 @@ SirenInbox( - Dropdown with checkbox selection - Customizable colors and styles for all filter components +#### Category Filter Styles +You can customize the appearance of the category filter dropdown using `CategoryFilterStyles`: + +```dart +CategoryFilterStyles( + dropdownTextStyle: TextStyle( + fontSize: 14, + color: Colors.black87, + fontWeight: FontWeight.w500 + ) +) +``` + +| Style Property | Description | Type | Default Value | +|------------------|------------------------------------------------|-----------|----------------------------------| +| dropdownTextStyle| Style for the category text in dropdown | TextStyle | fontSize: 14, color: Colors.black87 | + ## 3. Siren Class The `Siren Class` provides utility functions for modifying notifications. diff --git a/lib/src/models/ui_models.dart b/lib/src/models/ui_models.dart index 958bc52..4853183 100644 --- a/lib/src/models/ui_models.dart +++ b/lib/src/models/ui_models.dart @@ -245,10 +245,13 @@ class CustomThemeColors { final FilterColors? filterColors; } +/// Custom theme colors to configure the appearance of filters class FilterColors { const FilterColors({ this.categoryFilterColors, }); + + /// The colors for category dropdown final CategoryFilterColors? categoryFilterColors; } diff --git a/lib/src/widgets/siren_inbox.dart b/lib/src/widgets/siren_inbox.dart index dc2bc0a..44f767b 100644 --- a/lib/src/widgets/siren_inbox.dart +++ b/lib/src/widgets/siren_inbox.dart @@ -161,7 +161,9 @@ class _SirenInboxState extends State final response = await _fetchCategories.fetchCategories(); if (response.isSuccess && response.data != null) { safeSetState(() { - allCategories = response.data as List; + allCategories = (response.data as List) + ..removeWhere((e) => e.isEmpty) + ..addAll(['']); }); } else { widget.onError?.call(response.error ?? SirenErrorType());