# Local Notifications

<div id="introduction"></div>

## Giới thiệu

Nylo cung cấp hệ thống thông báo cục bộ thông qua lớp `LocalNotification`. Điều này cho phép bạn gửi thông báo tức thì hoặc theo lịch với nội dung phong phú trên iOS và Android.

> Thông báo cục bộ không được hỗ trợ trên web. Cố gắng sử dụng chúng trên web sẽ ném `NotificationException`.

<div id="basic-usage"></div>

## Cách sử dụng cơ bản

Gửi thông báo bằng mẫu builder hoặc phương thức tĩnh:

``` dart
import 'package:nylo_framework/nylo_framework.dart';

// Builder pattern
await LocalNotification(title: "Hello", body: "World").send();

// Using the helper function
await localNotification("Hello", "World").send();

// Using the static method
await LocalNotification.sendNotification(
  title: "Hello",
  body: "World",
);
```

<div id="scheduled-notifications"></div>

## Thông báo theo lịch

Lên lịch thông báo để gửi vào thời điểm cụ thể:

``` dart
// Schedule for tomorrow
final tomorrow = DateTime.now().add(Duration(days: 1));

await LocalNotification(
  title: "Reminder",
  body: "Don't forget your appointment!",
).send(at: tomorrow);

// Using the static method
await LocalNotification.sendNotification(
  title: "Reminder",
  body: "Don't forget your appointment!",
  at: tomorrow,
);
```

Việc lên lịch như thế này cần quyền `SCHEDULE_EXACT_ALARM` trên Android 14+. Nếu ứng dụng của bạn không có quyền này, Nylo sẽ tự động chuyển sang phương án gần đúng gần nhất và ghi log cảnh báo, thay vì ném ra lỗi `exact_alarms_not_permitted`:

``` dart
await LocalNotification(title: "Reminder", body: "...")
    .send(at: tomorrow, androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle);
```

Tự truyền `androidScheduleMode`, như ví dụ trên, nếu bạn muốn chủ động chọn chế độ gần đúng thay vì dựa vào phương án dự phòng tự động.

<div id="builder-pattern"></div>

## Mẫu Builder

Lớp `LocalNotification` cung cấp API builder kiểu fluent. Tất cả phương thức có thể nối chuỗi đều trả về instance `LocalNotification`:

``` dart
await LocalNotification(title: "New Photo", body: "Check this out!")
    .addPayload("photo_id:123")
    .addId(42)
    .addSubtitle("From your friend")
    .addBadgeNumber(3)
    .addSound("custom_sound.wav")
    .send();
```

### Phương thức có thể nối chuỗi

| Phương thức | Tham số | Mô tả |
|-------------|---------|-------|
| `addPayload` | `String payload` | Đặt chuỗi dữ liệu tùy chỉnh cho thông báo |
| `addId` | `int id` | Đặt định danh duy nhất cho thông báo |
| `addSubtitle` | `String subtitle` | Đặt văn bản phụ đề |
| `addBadgeNumber` | `int badgeNumber` | Đặt số badge biểu tượng ứng dụng |
| `addSound` | `String sound` | Đặt tên file âm thanh tùy chỉnh |
| `addAttachment` | `String url, String fileName, {bool? showThumbnail}` | Thêm đính kèm (chỉ iOS, tải từ URL) |
| `setAndroidConfig` | `AndroidNotificationConfig config` | Đặt cấu hình riêng Android |
| `setIOSConfig` | `IOSNotificationConfig config` | Đặt cấu hình riêng iOS |

### send()

``` dart
Future<void> send({
  DateTime? at,
  AndroidScheduleMode? androidScheduleMode,
})
```

Nếu cung cấp `at`, lên lịch thông báo cho thời điểm đó. Nếu không, hiển thị ngay lập tức.

<div id="platform-configuration"></div>

## Cấu hình nền tảng

Trong v7, các tùy chọn riêng nền tảng được cấu hình thông qua đối tượng `AndroidNotificationConfig` và `IOSNotificationConfig`.

<div id="android-configuration"></div>

### Cấu hình Android

Truyền `AndroidNotificationConfig` cho `setAndroidConfig()`:

``` dart
await LocalNotification(
  title: "Android Notification",
  body: "With custom configuration",
)
.setAndroidConfig(AndroidNotificationConfig(
  channelId: "custom_channel",
  channelName: "Custom Channel",
  channelDescription: "Notifications from custom channel",
  importance: Importance.max,
  priority: Priority.high,
  enableVibration: true,
  vibrationPattern: [0, 1000, 500, 1000],
  enableLights: true,
  ledColor: Color(0xFF00FF00),
))
.send();
```

#### Thuộc tính AndroidNotificationConfig

| Thuộc tính | Kiểu | Mặc định | Mô tả |
|------------|------|----------|-------|
| `channelId` | `String?` | `'default_channel'` | ID kênh thông báo |
| `channelName` | `String?` | `'Default Channel'` | Tên kênh thông báo |
| `channelDescription` | `String?` | `'Default Channel'` | Mô tả kênh |
| `importance` | `Importance?` | `Importance.max` | Mức độ quan trọng |
| `priority` | `Priority?` | `Priority.high` | Độ ưu tiên |
| `ticker` | `String?` | `'ticker'` | Văn bản ticker cho trợ năng |
| `icon` | `String?` | `'app_icon'` | Tên resource biểu tượng nhỏ |
| `playSound` | `bool?` | `true` | Có phát âm thanh không |
| `enableVibration` | `bool?` | `true` | Có bật rung không |
| `vibrationPattern` | `List<int>?` | - | Mẫu rung tùy chỉnh (ms) |
| `groupKey` | `String?` | - | Khóa nhóm cho nhóm thông báo |
| `setAsGroupSummary` | `bool?` | `false` | Có phải tóm tắt nhóm không |
| `groupAlertBehavior` | `GroupAlertBehavior?` | `GroupAlertBehavior.all` | Hành vi cảnh báo nhóm |
| `autoCancel` | `bool?` | `true` | Tự động ẩn khi nhấn |
| `ongoing` | `bool?` | `false` | Không thể bị người dùng loại bỏ |
| `silent` | `bool?` | `false` | Thông báo im lặng |
| `color` | `Color?` | - | Màu nhấn |
| `largeIcon` | `String?` | - | Đường dẫn resource biểu tượng lớn |
| `onlyAlertOnce` | `bool?` | `false` | Chỉ cảnh báo lần đầu hiển thị |
| `showWhen` | `bool?` | `true` | Hiển thị dấu thời gian |
| `when` | `int?` | - | Dấu thời gian tùy chỉnh (ms từ epoch) |
| `usesChronometer` | `bool?` | `false` | Sử dụng hiển thị đồng hồ |
| `chronometerCountDown` | `bool?` | `false` | Đồng hồ đếm ngược |
| `channelShowBadge` | `bool?` | `true` | Hiển thị badge trên kênh |
| `showProgress` | `bool?` | `false` | Hiển thị indicator tiến trình |
| `maxProgress` | `int?` | `0` | Giá trị tiến trình tối đa |
| `progress` | `int?` | `0` | Giá trị tiến trình hiện tại |
| `indeterminate` | `bool?` | `false` | Tiến trình không xác định |
| `channelAction` | `AndroidNotificationChannelAction?` | `createIfNotExists` | Hành động kênh |
| `enableLights` | `bool?` | `false` | Bật LED thông báo |
| `ledColor` | `Color?` | - | Màu LED |
| `ledOnMs` | `int?` | - | Thời gian bật LED (ms) |
| `ledOffMs` | `int?` | - | Thời gian tắt LED (ms) |
| `visibility` | `NotificationVisibility?` | - | Hiển thị trên màn hình khóa |
| `timeoutAfter` | `int?` | - | Thời gian tự động ẩn (ms) |
| `fullScreenIntent` | `bool?` | `false` | Khởi chạy dạng toàn màn hình |
| `shortcutId` | `String?` | - | ID phím tắt |
| `additionalFlags` | `List<int>?` | - | Cờ bổ sung |
| `tag` | `String?` | - | Tag thông báo |
| `actions` | `List<AndroidNotificationAction>?` | - | Nút hành động |
| `colorized` | `bool?` | `false` | Bật tô màu |
| `audioAttributesUsage` | `AudioAttributesUsage?` | `notification` | Sử dụng thuộc tính âm thanh |

<div id="ios-configuration"></div>

### Cấu hình iOS

Truyền `IOSNotificationConfig` cho `setIOSConfig()`:

``` dart
await LocalNotification(
  title: "iOS Notification",
  body: "With custom configuration",
)
.setIOSConfig(IOSNotificationConfig(
  presentAlert: true,
  presentBanner: true,
  presentSound: true,
  threadIdentifier: "thread_1",
  interruptionLevel: InterruptionLevel.active,
))
.addBadgeNumber(1)
.addSound("custom_sound.wav")
.send();
```

#### Thuộc tính IOSNotificationConfig

| Thuộc tính | Kiểu | Mặc định | Mô tả |
|------------|------|----------|-------|
| `presentList` | `bool?` | `true` | Hiển thị trong danh sách thông báo |
| `presentAlert` | `bool?` | `true` | Hiển thị cảnh báo |
| `presentBadge` | `bool?` | `true` | Cập nhật badge ứng dụng |
| `presentSound` | `bool?` | `true` | Phát âm thanh |
| `presentBanner` | `bool?` | `true` | Hiển thị banner |
| `sound` | `String?` | - | Tên file âm thanh |
| `badgeNumber` | `int?` | - | Số badge |
| `threadIdentifier` | `String?` | - | Định danh luồng cho nhóm |
| `categoryIdentifier` | `String?` | - | Định danh danh mục cho hành động |
| `interruptionLevel` | `InterruptionLevel?` | - | Mức độ gián đoạn |

### Đính kèm (chỉ iOS)

Thêm đính kèm hình ảnh, âm thanh, hoặc video cho thông báo iOS. Đính kèm được tải từ URL:

``` dart
await LocalNotification(
  title: "New Photo",
  body: "Check out this image!",
)
.addAttachment(
  "https://example.com/image.jpg",
  "photo.jpg",
  showThumbnail: true,
)
.send();
```

<div id="managing-notifications"></div>

## Quản lý thông báo

### Hủy thông báo cụ thể

``` dart
await LocalNotification.cancelNotification(42);

// With a tag (Android)
await LocalNotification.cancelNotification(42, tag: "my_tag");
```

### Hủy tất cả thông báo

``` dart
await LocalNotification.cancelAllNotifications();
```

### Xóa số badge

``` dart
await LocalNotification.clearBadgeCount();
```

<div id="permissions"></div>

## Quyền

Yêu cầu quyền thông báo từ người dùng:

``` dart
// Request with defaults
await LocalNotification.requestPermissions();

// Request with specific options
await LocalNotification.requestPermissions(
  alert: true,
  badge: true,
  sound: true,
  provisional: false,
  critical: false,
  vibrate: true,
  enableLights: true,
  channelId: 'default_notification_channel_id',
  channelName: 'Default Notification Channel',
);
```

Quyền được tự động yêu cầu trong lần gửi thông báo đầu tiên qua `NyScheduler.taskOnce`.

<div id="platform-setup"></div>

## Thiết lập nền tảng

### Thiết lập iOS

Thêm vào `Info.plist`:

``` xml
<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>
```

### Thiết lập Android

Thêm vào `AndroidManifest.xml`:

``` xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
```

<div id="api-reference"></div>

## Tham chiếu API

### Phương thức tĩnh

| Phương thức | Chữ ký | Mô tả |
|-------------|--------|-------|
| `sendNotification` | `static Future<void> sendNotification({required String title, required String body, String? payload, DateTime? at, int? id, String? subtitle, int? badgeNumber, String? sound, AndroidNotificationConfig? androidConfig, IOSNotificationConfig? iosConfig, AndroidScheduleMode? androidScheduleMode})` | Gửi thông báo với tất cả tùy chọn |
| `cancelNotification` | `static Future<void> cancelNotification(int id, {String? tag})` | Hủy thông báo cụ thể |
| `cancelAllNotifications` | `static Future<void> cancelAllNotifications()` | Hủy tất cả thông báo |
| `requestPermissions` | `static Future<void> requestPermissions({...})` | Yêu cầu quyền thông báo |
| `clearBadgeCount` | `static Future<void> clearBadgeCount()` | Xóa số badge iOS |

### Phương thức Instance (có thể nối chuỗi)

| Phương thức | Tham số | Trả về | Mô tả |
|-------------|---------|--------|-------|
| `addPayload` | `String payload` | `LocalNotification` | Đặt dữ liệu payload |
| `addId` | `int id` | `LocalNotification` | Đặt ID thông báo |
| `addSubtitle` | `String subtitle` | `LocalNotification` | Đặt phụ đề |
| `addBadgeNumber` | `int badgeNumber` | `LocalNotification` | Đặt số badge |
| `addSound` | `String sound` | `LocalNotification` | Đặt âm thanh tùy chỉnh |
| `addAttachment` | `String url, String fileName, {bool? showThumbnail}` | `LocalNotification` | Thêm đính kèm (iOS) |
| `setAndroidConfig` | `AndroidNotificationConfig config` | `LocalNotification` | Đặt cấu hình Android |
| `setIOSConfig` | `IOSNotificationConfig config` | `LocalNotification` | Đặt cấu hình iOS |
| `send` | `{DateTime? at, AndroidScheduleMode? androidScheduleMode}` | `Future<void>` | Gửi thông báo |
