Properly handle click actions on notifications
This commit is contained in:
parent
74a935f6f1
commit
e93d125431
@ -23,9 +23,20 @@
|
|||||||
- [ ] Logout
|
- [ ] Logout
|
||||||
- [ ] Send-page
|
- [ ] Send-page
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
# TODO iOS specific
|
||||||
|
|
||||||
|
- [ ] payment / pro
|
||||||
|
- [ ] show notifiactions (foreground/background/etc)
|
||||||
|
- [ ] handle click-on-notifications should open message
|
||||||
|
- [ ] share message
|
||||||
|
- [ ] scan QR
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
# TODO Server
|
||||||
|
|
||||||
- [ ] Switch server to sq style from faby
|
- [ ] Switch server to sq style from faby
|
||||||
- [ ] switch from mattn to go-sqlite
|
- [ ] switch from mattn to go-sqlite
|
||||||
- [ ] Single struct for model/db/json
|
- [ ] Single struct for model/db/json
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
@ -10,6 +11,8 @@ import 'package:simplecloudnotifier/models/channel.dart';
|
|||||||
import 'package:simplecloudnotifier/models/client.dart';
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||||
@ -174,12 +177,20 @@ void main() async {
|
|||||||
iOS: initializationSettingsDarwin,
|
iOS: initializationSettingsDarwin,
|
||||||
linux: initializationSettingsLinux,
|
linux: initializationSettingsLinux,
|
||||||
);
|
);
|
||||||
flutterLocalNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: _receiveLocalNotification);
|
flutterLocalNotificationsPlugin.initialize(
|
||||||
|
initializationSettings,
|
||||||
|
onDidReceiveNotificationResponse: _receiveLocalNotification,
|
||||||
|
onDidReceiveBackgroundNotificationResponse: _notificationTapBackground,
|
||||||
|
);
|
||||||
|
|
||||||
final appLaunchNotification = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
final appLaunchNotification = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||||
if (appLaunchNotification != null) {
|
if (appLaunchNotification != null) {
|
||||||
//TODO show message (also this only works on android+localnotifications, also handle ios)
|
// Use has launched SCN by clicking on a loca notifiaction, if it was a summary or message notifiaction open the corresponding screen
|
||||||
|
// This is android only
|
||||||
|
//TODO same on iOS, somehow??
|
||||||
ApplicationLog.info('App launched by notification: ${appLaunchNotification.notificationResponse?.id}');
|
ApplicationLog.info('App launched by notification: ${appLaunchNotification.notificationResponse?.id}');
|
||||||
|
|
||||||
|
_handleNotificationClickAction(appLaunchNotification.notificationResponse?.payload, Duration(milliseconds: 600));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +212,8 @@ void main() async {
|
|||||||
class SCNApp extends StatelessWidget {
|
class SCNApp extends StatelessWidget {
|
||||||
SCNApp({super.key});
|
SCNApp({super.key});
|
||||||
|
|
||||||
|
static var materialKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ToastificationWrapper(
|
return ToastificationWrapper(
|
||||||
@ -211,6 +224,7 @@ class SCNApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Consumer<AppTheme>(
|
child: Consumer<AppTheme>(
|
||||||
builder: (context, appTheme, child) => MaterialApp(
|
builder: (context, appTheme, child) => MaterialApp(
|
||||||
|
navigatorKey: SCNApp.materialKey,
|
||||||
title: 'SimpleCloudNotifier',
|
title: 'SimpleCloudNotifier',
|
||||||
navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver],
|
navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver],
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
@ -226,7 +240,8 @@ class SCNApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
void _notificationTapBackground(NotificationResponse notificationResponse) {
|
||||||
|
// I think only iOS triggers this, TODO
|
||||||
ApplicationLog.info('Received local notification<vm:entry-point>: ${notificationResponse.id}');
|
ApplicationLog.info('Received local notification<vm:entry-point>: ${notificationResponse.id}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,11 +283,13 @@ void setFirebaseToken(String fcmToken) async {
|
|||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _onBackgroundMessage(RemoteMessage message) async {
|
Future<void> _onBackgroundMessage(RemoteMessage message) async {
|
||||||
|
// a firebase message was received while the app was in the background or terminated
|
||||||
await _receiveMessage(message, false);
|
await _receiveMessage(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void _onForegroundMessage(RemoteMessage message) {
|
void _onForegroundMessage(RemoteMessage message) {
|
||||||
|
// a firebase message was received while the app was in the foreground
|
||||||
_receiveMessage(message, true);
|
_receiveMessage(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +322,7 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
await Hive.openBox<SCNRequest>('scn-requests');
|
await Hive.openBox<SCNRequest>('scn-requests');
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,10 +339,10 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
final channel_id = message.data['channel_id'] as String;
|
final channel_id = message.data['channel_id'] as String;
|
||||||
final body = message.data['body'] as String;
|
final body = message.data['body'] as String;
|
||||||
|
|
||||||
Notifier.showLocalNotification(channel_id, channel, 'Channel: ${channel}', title, body, timestamp);
|
Notifier.showLocalNotification(scn_msg_id, channel_id, channel, 'Channel: ${channel}', title, body, timestamp);
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +350,7 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
FBMessageLog.insert(message);
|
FBMessageLog.insert(message);
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,11 +365,41 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _receiveLocalDarwinNotification(int id, String? title, String? body, String? payload) {
|
void _receiveLocalDarwinNotification(int id, String? title, String? body, String? payload) {
|
||||||
|
//TODO iOS?
|
||||||
ApplicationLog.info('Received local notification<darwin>: $id -> [$title]');
|
ApplicationLog.info('Received local notification<darwin>: $id -> [$title]');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _receiveLocalNotification(NotificationResponse details) {
|
void _receiveLocalNotification(NotificationResponse details) {
|
||||||
ApplicationLog.info('Received local notification: ${details.id}');
|
// User has tapped a flutter_local notification, while the app was running
|
||||||
|
ApplicationLog.info('Tapped local notification: [[${details.id} | ${details.actionId} | ${details.input} | ${details.notificationResponseType} | ${details.payload}]]');
|
||||||
|
|
||||||
|
_handleNotificationClickAction(details.payload, Duration.zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleNotificationClickAction(String? payload, Duration delay) {
|
||||||
|
final parts = payload?.split('\n') ?? [];
|
||||||
|
|
||||||
|
if (parts.length == 4 && parts[0] == '@SCN_MESSAGE') {
|
||||||
|
final messageID = parts[1];
|
||||||
|
() async {
|
||||||
|
await Future.delayed(delay);
|
||||||
|
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ApplicationLog.info('Handle notification action @SCN_MESSAGE --> ${messageID}');
|
||||||
|
Navi.push(SCNApp.materialKey.currentContext!, () => MessageViewPage(messageID: messageID, preloadedData: null));
|
||||||
|
});
|
||||||
|
}();
|
||||||
|
} else if (parts.length == 3 && parts[0] == '@SCN_MESSAGE_SUMMARY') {
|
||||||
|
final channelID = parts[1];
|
||||||
|
() async {
|
||||||
|
await Future.delayed(delay);
|
||||||
|
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ApplicationLog.info('Handle notification action @SCN_MESSAGE_SUMMARY --> ${channelID}');
|
||||||
|
Navi.push(SCNApp.materialKey.currentContext!, () => ChannelViewPage(channelID: channelID, preloadedData: null, needsReload: null));
|
||||||
|
});
|
||||||
|
}();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DarwinNotificationCategory> getDarwinNotificationCategories() {
|
List<DarwinNotificationCategory> getDarwinNotificationCategories() {
|
||||||
|
@ -94,7 +94,7 @@ class _ChannelMessageViewPageState extends State<ChannelMessageViewPage> {
|
|||||||
message: item,
|
message: item,
|
||||||
allChannels: {this.widget.channel.channelID: this.widget.channel},
|
allChannels: {this.widget.channel.channelID: this.widget.channel},
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navi.push(context, () => MessageViewPage(message: item));
|
Navi.push(context, () => MessageViewPage(messageID: item.messageID, preloadedData: (item,)));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -56,7 +56,7 @@ class _DebugActionsPageState extends State<DebugActionsPage> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
UI.button(
|
UI.button(
|
||||||
big: false,
|
big: false,
|
||||||
onPressed: () => Notifier.showLocalNotification('TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null),
|
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null),
|
||||||
text: 'Show local notification',
|
text: 'Show local notification',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -249,7 +249,7 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
|
|||||||
message: item,
|
message: item,
|
||||||
allChannels: _channels ?? {},
|
allChannels: _channels ?? {},
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navi.push(context, () => MessageViewPage(message: item));
|
Navi.push(context, () => MessageViewPage(messageID: item.messageID, preloadedData: (item,)));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -18,9 +18,14 @@ import 'package:simplecloudnotifier/utils/toaster.dart';
|
|||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class MessageViewPage extends StatefulWidget {
|
class MessageViewPage extends StatefulWidget {
|
||||||
const MessageViewPage({super.key, required this.message});
|
const MessageViewPage({
|
||||||
|
super.key,
|
||||||
|
required this.messageID,
|
||||||
|
required this.preloadedData,
|
||||||
|
});
|
||||||
|
|
||||||
final SCNMessage message; // Potentially trimmed
|
final String messageID; // Potentially trimmed
|
||||||
|
final (SCNMessage,)? preloadedData; // Message is potentially trimmed, whole object is potentially null
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageViewPage> createState() => _MessageViewPageState();
|
State<MessageViewPage> createState() => _MessageViewPageState();
|
||||||
@ -33,8 +38,14 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
|
|
||||||
bool _monospaceMode = false;
|
bool _monospaceMode = false;
|
||||||
|
|
||||||
|
SCNMessage? message = null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
if (widget.preloadedData != null) {
|
||||||
|
message = widget.preloadedData!.$1;
|
||||||
|
}
|
||||||
|
|
||||||
mainFuture = fetchData();
|
mainFuture = fetchData();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@ -47,7 +58,7 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
|
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
final msg = await APIClient.getMessage(acc, widget.message.messageID);
|
final msg = await APIClient.getMessage(acc, widget.messageID);
|
||||||
|
|
||||||
final fut_chn = APIClient.getChannelPreview(acc, msg.channelID);
|
final fut_chn = APIClient.getChannelPreview(acc, msg.channelID);
|
||||||
final fut_key = APIClient.getKeyTokenPreview(acc, msg.usedKeyID);
|
final fut_key = APIClient.getKeyTokenPreview(acc, msg.usedKeyID);
|
||||||
@ -89,8 +100,8 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
return _buildMessageView(context, msg, chn, tok, usr);
|
return _buildMessageView(context, msg, chn, tok, usr);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
return Center(child: Text('${snapshot.error}')); //TODO nice error page
|
return Center(child: Text('${snapshot.error}')); //TODO nice error page
|
||||||
} else if (!widget.message.trimmed) {
|
} else if (message != null && !this.message!.trimmed) {
|
||||||
return _buildMessageView(context, widget.message, null, null, null);
|
return _buildMessageView(context, this.message!, null, null, null);
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@ -100,7 +111,9 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _share() async {
|
void _share() async {
|
||||||
var msg = widget.message;
|
if (this.message == null) return;
|
||||||
|
|
||||||
|
var msg = this.message!;
|
||||||
if (mainFutureSnapshot != null) {
|
if (mainFutureSnapshot != null) {
|
||||||
(msg, _, _, _) = mainFutureSnapshot!;
|
(msg, _, _, _) = mainFutureSnapshot!;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import 'package:simplecloudnotifier/state/application_log.dart';
|
|||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
|
||||||
class Notifier {
|
class Notifier {
|
||||||
static void showLocalNotification(String channelID, String channelName, String channelDescr, String title, String body, DateTime? timestamp) async {
|
static void showLocalNotification(String messageID, String channelID, String channelName, String channelDescr, String title, String body, DateTime? timestamp) async {
|
||||||
final nid = Globals().sharedPrefs.getInt('notifier.nextid') ?? 1000;
|
final nid = Globals().sharedPrefs.getInt('notifier.nextid') ?? 1000;
|
||||||
Globals().sharedPrefs.setInt('notifier.nextid', nid + 7);
|
Globals().sharedPrefs.setInt('notifier.nextid', nid + 7);
|
||||||
|
|
||||||
@ -25,6 +25,12 @@ class Notifier {
|
|||||||
final newSummaryNID = nid + 1;
|
final newSummaryNID = nid + 1;
|
||||||
ApplicationLog.debug('Create new summary notifications for channel ${channelID} with nid [${newSummaryNID}])');
|
ApplicationLog.debug('Create new summary notifications for channel ${channelID} with nid [${newSummaryNID}])');
|
||||||
Globals().sharedPrefs.setInt('notifier.summary.$channelID', newSummaryNID);
|
Globals().sharedPrefs.setInt('notifier.summary.$channelID', newSummaryNID);
|
||||||
|
|
||||||
|
var payload = '';
|
||||||
|
if (messageID != '') {
|
||||||
|
payload = ['@SCN_MESSAGE_SUMMARY', channelID, newSummaryNID].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
newSummaryNID,
|
newSummaryNID,
|
||||||
channelName,
|
channelName,
|
||||||
@ -40,6 +46,7 @@ class Notifier {
|
|||||||
subText: (channelName == 'main') ? null : channelName,
|
subText: (channelName == 'main') ? null : channelName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
payload: payload,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +55,11 @@ class Notifier {
|
|||||||
|
|
||||||
ApplicationLog.debug('Create new local notifications for message in channel ${channelID} with nid [${newMessageNID}])');
|
ApplicationLog.debug('Create new local notifications for message in channel ${channelID} with nid [${newMessageNID}])');
|
||||||
|
|
||||||
|
var payload = '';
|
||||||
|
if (messageID != '') {
|
||||||
|
payload = ['@SCN_MESSAGE', messageID, channelID, newMessageNID].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
// ======== SHOW NOTIFICATION ========
|
// ======== SHOW NOTIFICATION ========
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
newMessageNID,
|
newMessageNID,
|
||||||
@ -65,6 +77,7 @@ class Notifier {
|
|||||||
subText: (channelName == 'main') ? null : channelName,
|
subText: (channelName == 'main') ? null : channelName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
payload: payload,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user