together_mobile/lib/notification_api.dart

352 lines
11 KiB
Dart
Raw Normal View History

2023-06-21 17:44:28 +08:00
import 'dart:async';
2023-09-10 11:30:20 +08:00
import 'dart:convert';
2023-09-09 16:48:47 +08:00
import 'dart:io';
2023-06-21 17:44:28 +08:00
2023-09-09 16:48:47 +08:00
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
2023-06-21 17:44:28 +08:00
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
2023-09-09 16:48:47 +08:00
import 'package:flutter_timezone/flutter_timezone.dart';
2023-06-21 17:44:28 +08:00
2023-09-09 16:48:47 +08:00
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
2023-09-10 11:30:20 +08:00
import 'models/init_get_it.dart';
import 'models/contact_model.dart';
import 'models/user_model.dart';
import 'request/user_profile.dart';
import 'utils/app_dir.dart';
2023-06-21 17:44:28 +08:00
2023-09-09 16:48:47 +08:00
/// Here, the first argument is the id of notification and is common to all
/// methods that would result in a notification being shown. This is typically
/// set a unique value per notification as using the same id multiple times
/// would result in a notification being updated/overwritten.
2023-06-21 17:44:28 +08:00
class ReceivedNotification {
final int id;
final String? title;
final String? body;
final String? payload;
const ReceivedNotification({
required this.id,
required this.title,
required this.body,
required this.payload,
});
}
2023-09-10 11:30:20 +08:00
/// Before release build of app, read [Release build configuration]
/// https://pub-web.flutter-io.cn/packages/flutter_local_notifications
2023-06-21 17:44:28 +08:00
2023-09-10 11:30:20 +08:00
class NotificationAPI {
static int id = 0;
static Map<String, (int, List<Message>)> messages = {};
/// Init notification plugin
static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
/// Streams are created so that app can respond to notification-related events
/// since the plugin is initialised in the `main` function
/// Only be used in IOS?
static final StreamController<ReceivedNotification?>
didReceiveNotificationStream =
StreamController<ReceivedNotification>.broadcast();
static final StreamController<String?> selectNotificationStream =
StreamController<String?>.broadcast();
static const MethodChannel platform =
MethodChannel('dexterx.dev/flutter_local_notifications_example');
static const String portName = 'notification_send_port';
String? selectedNotificationPayload;
static late final NotificationAppLaunchDetails? notificationAppLaunchDetails;
/// A notification action which triggers a url launch event
static const String urlLanunchActionId = 'id_1';
/// A notification action which triggers a App navigation event
static const String navigationActionId = 'id-3';
/// Defines a iOS/MacOS notification category for text input actions.
static const String darwinNotificationCategoryText = 'textCategory';
/// Defines a iOS/MacOS notification category for plain actions.
static const String darwinNotificationCategoryPlain = 'plainCategory';
static Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized();
await _configureLocalTimeZone();
// not null if platform is Linux
notificationAppLaunchDetails = !kIsWeb && Platform.isLinux
? null
// Use the getNotificationAppLaunchDetails method when the app starts
// if you need to handle when a notification triggering the launch for
// an app e.g. change the home route of the app for deep-linking.
: await flutterLocalNotificationsPlugin
.getNotificationAppLaunchDetails();
String initRouteName = 'Home';
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
// If the app was launched via notification
// (e.g. taps a notification out of the app).
// route change depends on what type message is. Such as receiving a chat
// message, this route should change to message screen, and receiving a apply
// request shuold change to apply screen
initRouteName =
'Message'; // TODO: Set the route according to message type
}
const AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings('app_icon');
final List<DarwinNotificationCategory> darwinNotificationCategories =
<DarwinNotificationCategory>[
DarwinNotificationCategory(
darwinNotificationCategoryText,
actions: <DarwinNotificationAction>[
DarwinNotificationAction.text(
'text_1',
'action 1',
buttonTitle: 'send',
placeholder: 'Placeholder',
),
],
),
DarwinNotificationCategory(
darwinNotificationCategoryPlain,
actions: <DarwinNotificationAction>[
DarwinNotificationAction.plain('id_1', 'Action 1'),
DarwinNotificationAction.plain(
'id_2',
'Action 2 (destructive)',
options: <DarwinNotificationActionOption>{
DarwinNotificationActionOption.destructive,
},
),
DarwinNotificationAction.plain(
navigationActionId,
'Action 3 (foreground)',
options: <DarwinNotificationActionOption>{
DarwinNotificationActionOption.foreground,
},
),
DarwinNotificationAction.plain(
'id_4',
'Action 4 (auth required)',
options: <DarwinNotificationActionOption>{
DarwinNotificationActionOption.authenticationRequired,
},
),
],
options: <DarwinNotificationCategoryOption>{
DarwinNotificationCategoryOption.hiddenPreviewShowTitle,
},
),
];
/// Note: permissions aren't requested here just to demonstrate that can be
/// done later
final DarwinInitializationSettings darwinInitializationSettings =
DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification: (
int id,
String? title,
String? body,
String? payload,
) async {
didReceiveNotificationStream.add(
ReceivedNotification(
id: id,
title: title,
body: body,
payload: payload,
),
);
},
notificationCategories: darwinNotificationCategories,
);
final InitializationSettings initializationSettings =
InitializationSettings(
android: androidInitializationSettings,
iOS: darwinInitializationSettings,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
// This callback fire when a notification has tapped
onDidReceiveNotificationResponse: (NotificationResponse notificationRes) {
switch (notificationRes.notificationResponseType) {
case NotificationResponseType.selectedNotification:
selectNotificationStream.add(notificationRes.payload);
break;
case NotificationResponseType.selectedNotificationAction:
if (notificationRes.actionId == navigationActionId) {
selectNotificationStream.add(notificationRes.payload);
}
break;
}
},
onDidReceiveBackgroundNotificationResponse: _notificationTapBackground,
);
2023-09-09 16:48:47 +08:00
}
2023-09-10 11:30:20 +08:00
@pragma('vm:entry-point')
static void _notificationTapBackground(
NotificationResponse notificationResponse) {
print('notification(${notificationResponse.id}) action tapped: '
'${notificationResponse.actionId} with'
' payload: ${notificationResponse.payload}');
if (notificationResponse.input?.isNotEmpty ?? false) {
print(
'notification action tapped with input: ${notificationResponse.input}');
}
2023-06-21 17:44:28 +08:00
}
2023-09-09 16:48:47 +08:00
2023-09-10 11:30:20 +08:00
/// Scheduling notifications now requires developers to specify a date and
/// time relative to a specific time zone.
/// This is to solve issues with daylight saving time that existed in the
/// schedule method that is now deprecated.
static Future<void> _configureLocalTimeZone() async {
if (kIsWeb || Platform.isLinux) {
return;
}
tz.initializeTimeZones();
final String timeZoneName = await FlutterTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}
2023-09-09 16:48:47 +08:00
2023-09-10 11:30:20 +08:00
static Future<void> showMessageNotifications({
required String senderId,
required String name,
required String avatar,
required String text,
String? groupChatId,
}) async {
String groupChannelId = 'TOGETHER_${getIt.get<UserAccount>().id}';
const String groupChannelName = 'together messages';
String avatarPath = '';
if (avatar.isNotEmpty) {
if (groupChatId == null) {
avatarPath = await getAvatarPath('user', avatar);
if (!File(avatarPath).existsSync()) {
File file = await File(avatarPath).create(recursive: true);
final bytes = await downloadUserAvatar(avatar);
await file.writeAsBytes(bytes);
}
} else {
// TODO: download group chat avatar. This will done after changing group chat avatar is implemented
}
}
2023-06-21 17:44:28 +08:00
2023-09-10 11:30:20 +08:00
late Person person;
2023-09-09 16:48:47 +08:00
2023-09-10 11:30:20 +08:00
if (avatarPath.isNotEmpty) {
person = Person(
name: name,
key: senderId,
bot: false,
icon: BitmapFilePathAndroidIcon(avatarPath),
2023-09-09 16:48:47 +08:00
);
2023-09-10 11:30:20 +08:00
} else {
person = Person(
name: name,
key: senderId,
bot: false,
);
}
if (groupChatId == null) {
if (messages.containsKey(senderId)) {
messages[senderId]!.$2.add(Message(text, DateTime.now(), person));
} else {
messages[senderId] = (++id, [Message(text, DateTime.now(), person)]);
2023-09-09 16:48:47 +08:00
}
2023-09-10 11:30:20 +08:00
} else {
if (messages.containsKey(groupChatId)) {
messages[groupChatId]!.$2.add(Message(text, DateTime.now(), person));
} else {
messages[groupChatId] = (++id, [Message(text, DateTime.now(), person)]);
}
}
late final MessagingStyleInformation messagingStyle;
if (groupChatId == null) {
messagingStyle = MessagingStyleInformation(
person,
messages: messages[senderId]!.$2,
conversationTitle: name,
);
} else {
String groupChatName =
getIt.get<Contact>().groupChats[groupChatId]!.nameRemark.isEmpty
? getIt.get<ContactAccountProfile>().groupChats[groupChatId]!.name
: getIt.get<Contact>().groupChats[groupChatId]!.nameRemark;
messagingStyle = MessagingStyleInformation(
person,
messages: messages[groupChatId]!.$2,
conversationTitle: groupChatName,
groupConversation: true,
);
}
AndroidNotificationDetails androidNotificationDetails =
AndroidNotificationDetails(
groupChannelId,
groupChannelName,
importance: Importance.max,
priority: Priority.high,
category: AndroidNotificationCategory.message,
styleInformation: messagingStyle,
);
NotificationDetails notificationDetails = NotificationDetails(
android: androidNotificationDetails,
);
late String payload;
if (groupChatId == null) {
payload = json.encode({
'event': 'friend-message',
'friendId': senderId,
});
await flutterLocalNotificationsPlugin.show(
messages[senderId]!.$1,
name,
text,
notificationDetails,
payload: payload,
);
} else {
payload = json.encode({
'event': 'group-chat-message',
'groupChatId': groupChatId,
});
await flutterLocalNotificationsPlugin.show(
messages[groupChatId]!.$1,
name,
text,
notificationDetails,
payload: payload,
);
}
}
2023-09-09 16:48:47 +08:00
}