Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Loading