Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5d371c2
fix: Add fixes for lint errors
anitta-keyvalue May 23, 2025
86453b3
feat: Add fix failing tests
anitta-keyvalue May 23, 2025
5b2cc0b
feat: Add categories initial changes
anitta-keyvalue May 23, 2025
1159c44
feat: Add param models for category
anitta-keyvalue May 27, 2025
429b1bc
feat: Add API changes to support multiple categories, add colors for …
anitta-keyvalue May 27, 2025
47cb904
feat: Add UI changes to support filter icon
anitta-keyvalue May 27, 2025
e81e225
fix: Add fix for light theme colors
anitta-keyvalue May 28, 2025
1400933
feat: Add more colors to support filters
anitta-keyvalue May 28, 2025
7e73031
fix: Add changes to keep the menu bar open
anitta-keyvalue May 28, 2025
6535d49
feat: Add custom widget for filter
anitta-keyvalue May 28, 2025
35b5970
feat: Add params to filter button badge
anitta-keyvalue May 28, 2025
a568552
refactor: Remove unused variables and params
anitta-keyvalue May 28, 2025
7e9ecde
tests: Add unit test for filter
anitta-keyvalue May 28, 2025
34019f7
docs: Add changes in Readme
anitta-keyvalue May 28, 2025
a4a710c
Merge branch 'dev' into feat/add-categories
anitta-keyvalue May 28, 2025
76e696f
refactor: Revert env back
anitta-keyvalue May 29, 2025
553ce9c
fix: Add fix for margin in app bar
anitta-keyvalue May 29, 2025
6e6f4e3
refactor: Some improvements in app bar
anitta-keyvalue May 29, 2025
be9da0a
refactor: Modify the filter params
anitta-keyvalue May 29, 2025
7a55fba
fix: Add few UI fixes related to padding
anitta-keyvalue May 29, 2025
5decc44
docs: Updated changelog, readme
anitta-keyvalue May 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
231 changes: 191 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,74 +102,225 @@ Given below are the arguments of Siren Inbox Widget.
| ----------------- | -------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 |
| 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(categoryFilterParams: CategoryFilterParams(showFilters: true, filterIconWidget: null, hideBadge: false, categoryFilterStyles: CategoryFilterStyles(dropdownTextStyle: null))) |

#### 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(
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
)
)
)
```

#### 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(
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
categoryFilterStyles: CategoryFilterStyles( // Optional custom styles for category filter
dropdownTextStyle: TextStyle(
fontSize: 14,
color: Colors.black87
)
)
)
)
)
```

#### 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

#### 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.
Expand Down
19 changes: 17 additions & 2 deletions lib/src/api/fetch_all_notification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class FetchAllNotifications {
bool? isRead,
String? start,
String? end,
List<String>? categories,
}) async {
final apiPath =
'${Generics.V2}${Generics.BASE_URL}${SirenDataProvider.instance.recipientId}/notifications';
Expand All @@ -53,8 +54,22 @@ class FetchAllNotifications {
queryParams['isRead'] = isRead.toString();
}

final queryString =
queryParams.entries.map((e) => '${e.key}=${e.value}').join('&');
// Build the query string
final queryParts = <String>[];

// 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) {
for (final category in categories) {
queryParts.add('category=${Uri.encodeComponent(category)}');
}
}

final queryString = queryParts.join('&');

if (SirenDataProvider.instance.tokenVerificationStatus != Status.SUCCESS) {
apiError = SirenDataProvider.instance.getVerificationErrorType();
Expand Down
64 changes: 64 additions & 0 deletions lib/src/api/fetch_categories.dart
Original file line number Diff line number Diff line change
@@ -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<String> convertJsonToCategoryList(List<dynamic> dataList) {
return dataList.map((dynamic json) {
if (json is String) {
return json;
}
throw const FormatException('Invalid JSON format');
}).toList();
}

Future<ApiResponse> 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<dynamic>?;
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;
}
}
Loading