Skip to content
Closed
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
84 changes: 84 additions & 0 deletions flutter_local_notifications/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,87 @@
# 17.2.6

## [17.2.6]

* [Android] Added font scale compensation for custom notification layouts.

When the user increases the system font size (Settings → Display → Font size), text in custom
`RemoteViews`-based notification layouts can overflow because Android scales `sp` values before
rendering and `autoSizeTextType` is not supported in `RemoteViews`.

The plugin now detects `fontScale > 1.0` at notification creation time and programmatically
overrides text sizes using `RemoteViews.setTextViewTextSize()` with compensated pixel values,
keeping text at a fixed visual size regardless of the system font scale.
When `fontScale == 1.0` (default), no overrides are applied.

### How to use

**1. Define dimen resources** in `android/app/src/main/res/values/dimens.xml` that match the
`textSize` values in your custom notification layout XML. The naming convention is
`{view_id}_text_size`:

```xml
<resources>
<!-- Must match textSize in your notification layout XMLs -->
<dimen name="push_title_text_size">14sp</dimen>
<dimen name="push_text_text_size">12sp</dimen>

<!-- For action buttons (view IDs from customViewId in AndroidNotificationAction) -->
<dimen name="action_enter_text_size">15sp</dimen>
<dimen name="action_skip_text_size">15sp</dimen>
</resources>
```

For example, if your layout has:

```xml
<TextView
android:id="@+id/push_title"
android:textSize="14sp" ... />

<TextView
android:id="@+id/push_text"
android:textSize="12sp" ... />

<Button
android:id="@+id/action_enter"
android:textSize="15sp" ... />
```

Then each dimen name is `push_title` + `_text_size` = `push_title_text_size`, etc.

**2. No changes to Dart code or layout XML are required.** Existing code works as before:

```dart
await flutterLocalNotificationsPlugin.show(
id,
'Title',
'Body text',
NotificationDetails(
android: AndroidNotificationDetails(
'channel_id',
'Channel Name',
customLayoutExpandedName: 'scheduled_notification_expanded',
customLayoutCollapsedName: 'scheduled_notification_collapsed',
actions: <AndroidNotificationAction>[
AndroidNotificationAction(
'enter',
'Show',
customViewId: 'action_enter',
),
AndroidNotificationAction(
'skip',
'Close',
customViewId: 'action_skip',
),
],
),
),
);
```

**Note:** If a dimen resource for a given view ID is not found, no override is applied for that
view. This makes the feature fully backwards-compatible.

# 17.2.5

Include changes from parent repo v17.0.1 - 17.2.4
Expand Down
34 changes: 34 additions & 0 deletions flutter_local_notifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This is fork of [flutter_local_notifications](https://github.com/MaikuB/flutter_
it will save in local store if notification show.
* You can load this stored information by `FlutterLocalNotificationsPlugin.getShownNotificationsInfo()`
method and clear stored info by `FlutterLocalNotificationsPlugin.cleanShownNotificationsInfo()` method.
* Font scale compensation for custom notification layouts. See [Font scale compensation for custom layouts](#font-scale-compensation-for-custom-layouts).

### iOS

Expand Down Expand Up @@ -51,6 +52,7 @@ This is fork of [flutter_local_notifications](https://github.com/MaikuB/flutter_
- [Scheduled notifications](#scheduling-a-notification)
- [Fullscreen intent notifications](#full-screen-intent-notifications)
- [Release build configuration](#release-build-configuration)
- [Font scale compensation for custom layouts](#font-scale-compensation-for-custom-layouts)
- **[🔧 iOS setup](#-ios-setup)**
- [General setup](#general-setup)
- [Handling notifications whilst the app is in the foreground](#handling-notifications-whilst-the-app-is-in-the-foreground)
Expand Down Expand Up @@ -330,6 +332,38 @@ Before creating the release build of your app (which is the default setting when

⚠️ Ensure that you have configured the resources that should be kept so that resources like your notification icons aren't discarded by the R8 compiler by following the instructions [here](https://developer.android.com/studio/build/shrink-code#keep-resources). If you have chosen to use `@mipmap/ic_launcher` as the notification icon (against the official Android guidance), be sure to include this in the `keep.xml` file. If you fail to do this, notifications might be broken. In the worst case they will never show, instead silently failing when the system looks for a resource that has been removed. If they do still show, you might not see the icon you specified. The configuration used by the example app can be found [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/res/raw/keep.xml) where it is specifying that all drawable resources should be kept, as well as the file used to play a custom notification sound (sound file is located [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/res/raw/slow_spring_board.mp3)).

### Font scale compensation for custom layouts

When users increase the system font size (Settings → Display → Font size), text in custom `RemoteViews`-based notification layouts can overflow. This happens because Android scales all `sp` values globally before rendering `RemoteViews`, and `autoSizeTextType` is not supported in `RemoteViews`.

The plugin detects `fontScale > 1.0` at notification creation time and programmatically overrides text sizes via `RemoteViews.setTextViewTextSize()` with compensated pixel values. At `fontScale == 1.0` (default) no overrides are applied.

#### Setup

Define dimen resources in `android/app/src/main/res/values/dimens.xml` whose values match the `textSize` in your layout XML. The naming convention is **`{view_id}_text_size`**, where `{view_id}` is the `android:id` of the view.

For a layout with:

```xml
<TextView android:id="@+id/push_title" android:textSize="14sp" ... />
<TextView android:id="@+id/push_text" android:textSize="12sp" ... />
<Button android:id="@+id/action_enter" android:textSize="15sp" ... />
<Button android:id="@+id/action_skip" android:textSize="15sp" ... />
```

Add dimens:

```xml
<resources>
<dimen name="push_title_text_size">14sp</dimen>
<dimen name="push_text_text_size">12sp</dimen>
<dimen name="action_enter_text_size">15sp</dimen>
<dimen name="action_skip_text_size">15sp</dimen>
</resources>
```

No changes to Dart code or layout XML are required. If a dimen resource for a given view ID is not found, no override is applied — the feature is fully backwards-compatible.

## 🔧 iOS setup

### General setup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;
import android.util.Log;
Expand Down Expand Up @@ -426,17 +427,21 @@ protected static Notification createNotification(
Resources resources = null;
resources = manager.getResourcesForApplication(packageName);

float fontScale = context.getResources().getConfiguration().fontScale;

if (VERSION.SDK_INT < VERSION_CODES.S) {
RemoteViews contentView =
setCustomContentView(
context,
resources,
notificationDetails,
notificationDetails.customLayoutLegacyName,
packageName);
packageName,
fontScale);
if (notificationDetails.actions != null) {
setCustomContentViewActions(
contentView, resources, packageName, actionIntents, notificationDetails.actions);
contentView, resources, packageName, actionIntents, notificationDetails.actions,
fontScale);
}
builder.setCustomContentView(contentView);
} else {
Expand All @@ -448,7 +453,8 @@ protected static Notification createNotification(
resources,
notificationDetails,
customLayoutCollapsedName,
packageName);
packageName,
fontScale);
builder.setCustomContentView(contentView);
}

Expand All @@ -460,14 +466,16 @@ protected static Notification createNotification(
resources,
notificationDetails,
customLayoutExpandedName,
packageName);
packageName,
fontScale);
if (notificationDetails.actions != null) {
setCustomContentViewActions(
contentView,
resources,
packageName,
actionIntents,
notificationDetails.actions);
notificationDetails.actions,
fontScale);
}
builder.setCustomBigContentView(contentView);
}
Expand Down Expand Up @@ -519,13 +527,14 @@ private static RemoteViews setCustomContentView(
Resources resources,
NotificationDetails notificationDetails,
String layoutName,
String packageName) {
String packageName,
float fontScale) {
int layoutId = resources.getIdentifier(layoutName, "layout", packageName);
RemoteViews contentView = new RemoteViews(packageName, layoutId);
setCustomContentViewField(
contentView, resources, notificationDetails.title, "push_title", packageName);
contentView, resources, notificationDetails.title, "push_title", packageName, fontScale);
setCustomContentViewField(
contentView, resources, notificationDetails.body, "push_text", packageName);
contentView, resources, notificationDetails.body, "push_text", packageName, fontScale);
setCustomContentViewImage(
context,
contentView,
Expand All @@ -542,7 +551,8 @@ private static void setCustomContentViewActions(
Resources resources,
String packageName,
Map<String, PendingIntent> actionIntents,
List<NotificationAction> actions) {
List<NotificationAction> actions,
float fontScale) {
for (NotificationAction action : actions) {
if (!StringUtils.isNullOrEmpty(action.customViewId)) {
int viewId = resources.getIdentifier(action.customViewId, "id", packageName);
Expand All @@ -551,22 +561,39 @@ private static void setCustomContentViewActions(
if (action.titleColor != null) {
contentView.setTextColor(viewId, action.titleColor);
}
compensateTextSizeForFontScale(
contentView, resources, viewId, action.customViewId, packageName, fontScale);
contentView.setOnClickPendingIntent(viewId, actionIntents.get(action.id));
}
}
}
}

private static void setCustomContentViewField(
RemoteViews contentView, Resources resources, String value, String name, String packageName) {
RemoteViews contentView, Resources resources, String value, String name, String packageName,
float fontScale) {
int id = resources.getIdentifier(name, "id", packageName);
if (!StringUtils.isNullOrEmpty(value)) {
contentView.setTextViewText(id, value);
compensateTextSizeForFontScale(contentView, resources, id, name, packageName, fontScale);
} else {
contentView.setViewVisibility(id, View.GONE);
}
}

private static void compensateTextSizeForFontScale(
RemoteViews contentView, Resources resources, int viewId, String viewName,
String packageName, float fontScale) {
if (fontScale > 1.0f) {
int dimenId = resources.getIdentifier(viewName + "_text_size", "dimen", packageName);
if (dimenId != 0) {
float baseSizePx = resources.getDimension(dimenId);
float compensatedSizePx = baseSizePx / fontScale;
contentView.setTextViewTextSize(viewId, TypedValue.COMPLEX_UNIT_PX, compensatedSizePx);
}
}
}

private static void setCustomContentViewImage(
Context context,
RemoteViews contentView,
Expand Down
2 changes: 1 addition & 1 deletion flutter_local_notifications/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: flutter_local_notifications_plus
description: A cross platform plugin for displaying and scheduling local
notifications for Flutter applications with the ability to customise for each
platform.
version: 17.2.5
version: 17.2.6
homepage: https://github.com/Innim/flutter_local_notifications
repository: https://github.com/Innim/flutter_local_notifications
issue_tracker: https://github.com/Innim/flutter_local_notifications/issues
Expand Down
Loading