Implemented structure for push Notifications
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../apiError.dart';
|
||||
@ -21,6 +22,10 @@ abstract class MhslApi<T> extends ApiRequest {
|
||||
throw ApiError("Request could not be dispatched!");
|
||||
}
|
||||
|
||||
if(data.statusCode > 299) {
|
||||
log("Non 200 Status code from mhsl services: $subpath: ${data.statusCode}");
|
||||
}
|
||||
|
||||
return assemble(utf8.decode(data.bodyBytes));
|
||||
}
|
||||
}
|
26
lib/api/mhsl/notify/register/notifyRegister.dart
Normal file
26
lib/api/mhsl/notify/register/notifyRegister.dart
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../mhslApi.dart';
|
||||
import 'notifyRegisterParams.dart';
|
||||
|
||||
class NotifyRegister extends MhslApi<void> {
|
||||
NotifyRegisterParams params;
|
||||
NotifyRegister(this.params) : super("notify/register/");
|
||||
|
||||
|
||||
@override
|
||||
void assemble(String raw) {
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.Response> request(Uri uri) {
|
||||
String requestString = jsonEncode(params.toJson());
|
||||
log(requestString);
|
||||
return http.post(uri, body: requestString);
|
||||
}
|
||||
}
|
19
lib/api/mhsl/notify/register/notifyRegisterParams.dart
Normal file
19
lib/api/mhsl/notify/register/notifyRegisterParams.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'notifyRegisterParams.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class NotifyRegisterParams {
|
||||
String username;
|
||||
String password;
|
||||
String fcmToken;
|
||||
|
||||
NotifyRegisterParams({
|
||||
required this.username,
|
||||
required this.password,
|
||||
required this.fcmToken
|
||||
});
|
||||
|
||||
factory NotifyRegisterParams.fromJson(Map<String, dynamic> json) => _$NotifyRegisterParamsFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$NotifyRegisterParamsToJson(this);
|
||||
}
|
23
lib/api/mhsl/notify/register/notifyRegisterParams.g.dart
Normal file
23
lib/api/mhsl/notify/register/notifyRegisterParams.g.dart
Normal file
@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'notifyRegisterParams.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
NotifyRegisterParams _$NotifyRegisterParamsFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
NotifyRegisterParams(
|
||||
username: json['username'] as String,
|
||||
password: json['password'] as String,
|
||||
fcmToken: json['fcmToken'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$NotifyRegisterParamsToJson(
|
||||
NotifyRegisterParams instance) =>
|
||||
<String, dynamic>{
|
||||
'username': instance.username,
|
||||
'password': instance.password,
|
||||
'fcmToken': instance.fcmToken,
|
||||
};
|
11
lib/app.dart
11
lib/app.dart
@ -1,6 +1,7 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -10,6 +11,9 @@ import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||
import 'model/breakers/Breaker.dart';
|
||||
import 'model/breakers/BreakerProps.dart';
|
||||
import 'model/chatList/chatListProps.dart';
|
||||
import 'notification/notificationController.dart';
|
||||
import 'notification/notifyUpdater.dart';
|
||||
import 'storage/base/settingsProvider.dart';
|
||||
import 'view/pages/files/files.dart';
|
||||
import 'view/pages/more/overhang.dart';
|
||||
import 'view/pages/talk/chatList.dart';
|
||||
@ -42,6 +46,13 @@ class _AppState extends State<App> {
|
||||
});
|
||||
});
|
||||
|
||||
if(Provider.of<SettingsProvider>(context, listen: false).val().notificationSettings.enabled) {
|
||||
NotifyUpdater.registerToServer();
|
||||
}
|
||||
|
||||
FirebaseMessaging.onMessage.listen((message) => NotificationController.onForegroundMessageHandler(message, context));
|
||||
FirebaseMessaging.onBackgroundMessage(NotificationController.onBackgroundMessageHandler);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
@ -27,6 +31,8 @@ import 'widget/placeholderView.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}");
|
||||
|
||||
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
|
||||
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
|
||||
|
45
lib/notification/notificationController.dart
Normal file
45
lib/notification/notificationController.dart
Normal file
@ -0,0 +1,45 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../api/marianumcloud/talk/room/getRoom.dart';
|
||||
import '../api/marianumcloud/talk/room/getRoomParams.dart';
|
||||
import '../model/accountData.dart';
|
||||
import 'notificationService.dart';
|
||||
|
||||
class NotificationController {
|
||||
@pragma('vm:entry-point')
|
||||
static Future<void> onBackgroundMessageHandler(RemoteMessage message) async {
|
||||
log("Handling a background notification: ${message.messageId}");
|
||||
|
||||
await Firebase.initializeApp();
|
||||
AccountData().waitForPopulation().then((value) {
|
||||
log("User account status: $value");
|
||||
if(value) {
|
||||
GetRoom(
|
||||
GetRoomParams(
|
||||
includeStatus: false,
|
||||
),
|
||||
).run().then((value) {
|
||||
var messageCount = value.data.map((e) => e.unreadMessages).reduce((a, b) => a + b);
|
||||
var chatCount = value.data.map((e) => e.unreadMessages).length;
|
||||
var people = value.data.where((e) => e.unreadMessages > 0).map((e) => e.displayName.split(" ")[0]);
|
||||
|
||||
final NotificationService service = NotificationService();
|
||||
service.initializeNotifications().then((value) {
|
||||
service.showNotification(
|
||||
title: "Du hast $messageCount ungelesene Nachrichten!",
|
||||
body: "In $chatCount Chats, von ${people.join(", ")}"
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> onForegroundMessageHandler(RemoteMessage message, BuildContext context) async {
|
||||
NotificationService().showToast(message: "Du hast eine neue Talk Nachricht!", context: context);
|
||||
}
|
||||
}
|
75
lib/notification/notificationService.dart
Normal file
75
lib/notification/notificationService.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../model/chatList/chatListProps.dart';
|
||||
import '../model/message/messageProps.dart';
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
|
||||
factory NotificationService() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
NotificationService._internal();
|
||||
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
Future<void> initializeNotifications() async {
|
||||
const AndroidInitializationSettings androidSettings = AndroidInitializationSettings(
|
||||
'@mipmap/ic_launcher'
|
||||
);
|
||||
|
||||
final DarwinInitializationSettings iosSettings = DarwinInitializationSettings(
|
||||
onDidReceiveLocalNotification: (id, title, body, payload) {
|
||||
// TODO Navigate to Talk section (This runs when an Notification is tapped)
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
final InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showNotification({required String title, required String body}) async {
|
||||
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||
AndroidNotificationDetails(
|
||||
'your_channel_id',
|
||||
'Your Channel Name',
|
||||
importance: Importance.defaultImportance,
|
||||
priority: Priority.defaultPriority,
|
||||
ticker: 'ticker',
|
||||
);
|
||||
|
||||
const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
title,
|
||||
body,
|
||||
platformChannelSpecifics,
|
||||
);
|
||||
}
|
||||
|
||||
void showToast({required String message, required BuildContext context, ToastGravity gravity = ToastGravity.BOTTOM}) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
gravity: gravity,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
fontSize: 13.0,
|
||||
);
|
||||
|
||||
Provider.of<ChatListProps>(context, listen: false).run(renew: true);
|
||||
Provider.of<MessageProps>(context, listen: false).run(renew: true);
|
||||
}
|
||||
}
|
22
lib/notification/notifyUpdater.dart
Normal file
22
lib/notification/notifyUpdater.dart
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
import '../api/mhsl/notify/register/notifyRegister.dart';
|
||||
import '../api/mhsl/notify/register/notifyRegisterParams.dart';
|
||||
import '../model/accountData.dart';
|
||||
|
||||
class NotifyUpdater {
|
||||
static void registerToServer() async {
|
||||
String? fcmToken = await FirebaseMessaging.instance.getToken();
|
||||
|
||||
if(fcmToken == null) throw Exception("Failed to register push notification because there is no FBC token!");
|
||||
|
||||
NotifyRegister(
|
||||
NotifyRegisterParams(
|
||||
username: AccountData().getUsername(),
|
||||
password: AccountData().getPassword(),
|
||||
fcmToken: fcmToken,
|
||||
),
|
||||
).run();
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import '../file/fileSettings.dart';
|
||||
import '../fileView/fileViewSettings.dart';
|
||||
import '../gradeAverages/gradeAveragesSettings.dart';
|
||||
import '../holidays/holidaysSettings.dart';
|
||||
import '../notification/notificationSettings.dart';
|
||||
import '../talk/talkSettings.dart';
|
||||
import '../timetable/timetableSettings.dart';
|
||||
|
||||
@ -25,6 +26,7 @@ class Settings {
|
||||
FileSettings fileSettings;
|
||||
HolidaysSettings holidaysSettings;
|
||||
FileViewSettings fileViewSettings;
|
||||
NotificationSettings notificationSettings;
|
||||
|
||||
Settings({
|
||||
required this.appTheme,
|
||||
@ -35,6 +37,7 @@ class Settings {
|
||||
required this.fileSettings,
|
||||
required this.holidaysSettings,
|
||||
required this.fileViewSettings,
|
||||
required this.notificationSettings,
|
||||
});
|
||||
|
||||
static String _themeToJson(ThemeMode m) => m.name;
|
||||
|
@ -21,6 +21,8 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
|
||||
json['holidaysSettings'] as Map<String, dynamic>),
|
||||
fileViewSettings: FileViewSettings.fromJson(
|
||||
json['fileViewSettings'] as Map<String, dynamic>),
|
||||
notificationSettings: NotificationSettings.fromJson(
|
||||
json['notificationSettings'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
@ -32,4 +34,5 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
'fileSettings': instance.fileSettings.toJson(),
|
||||
'holidaysSettings': instance.holidaysSettings.toJson(),
|
||||
'fileViewSettings': instance.fileViewSettings.toJson(),
|
||||
'notificationSettings': instance.notificationSettings.toJson(),
|
||||
};
|
||||
|
14
lib/storage/notification/notificationSettings.dart
Normal file
14
lib/storage/notification/notificationSettings.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'notificationSettings.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class NotificationSettings {
|
||||
bool askUsageDismissed;
|
||||
bool enabled;
|
||||
|
||||
NotificationSettings({required this.askUsageDismissed, required this.enabled});
|
||||
|
||||
factory NotificationSettings.fromJson(Map<String, dynamic> json) => _$NotificationSettingsFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$NotificationSettingsToJson(this);
|
||||
}
|
21
lib/storage/notification/notificationSettings.g.dart
Normal file
21
lib/storage/notification/notificationSettings.g.dart
Normal file
@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'notificationSettings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
NotificationSettings _$NotificationSettingsFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
NotificationSettings(
|
||||
askUsageDismissed: json['askUsageDismissed'] as bool,
|
||||
enabled: json['enabled'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$NotificationSettingsToJson(
|
||||
NotificationSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'askUsageDismissed': instance.askUsageDismissed,
|
||||
'enabled': instance.enabled,
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -26,6 +27,8 @@ class _ChatListState extends State<ChatList> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
FirebaseMessaging.instance.requestPermission();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_query();
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ import '../../storage/file/fileSettings.dart';
|
||||
import '../../storage/fileView/fileViewSettings.dart';
|
||||
import '../../storage/gradeAverages/gradeAveragesSettings.dart';
|
||||
import '../../storage/holidays/holidaysSettings.dart';
|
||||
import '../../storage/notification/notificationSettings.dart';
|
||||
import '../../storage/talk/talkSettings.dart';
|
||||
import '../../storage/timetable/timetableSettings.dart';
|
||||
import '../pages/files/files.dart';
|
||||
@ -39,6 +40,10 @@ class DefaultSettings {
|
||||
fileViewSettings: FileViewSettings(
|
||||
alwaysOpenExternally: Platform.isIOS,
|
||||
),
|
||||
notificationSettings: NotificationSettings(
|
||||
askUsageDismissed: false,
|
||||
enabled: false,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../model/accountData.dart';
|
||||
import '../../notification/notifyUpdater.dart';
|
||||
import '../../storage/base/settingsProvider.dart';
|
||||
import '../../theming/appTheme.dart';
|
||||
import '../../widget/centeredLeading.dart';
|
||||
@ -87,9 +88,7 @@ class _SettingsState extends State<Settings> {
|
||||
),
|
||||
)).toList(),
|
||||
onChanged: (e) {
|
||||
setState(() {
|
||||
settings.val(write: true).appTheme = e!;
|
||||
});
|
||||
settings.val(write: true).appTheme = e!;
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -102,9 +101,7 @@ class _SettingsState extends State<Settings> {
|
||||
trailing: Checkbox(
|
||||
value: settings.val().talkSettings.sortFavoritesToTop,
|
||||
onChanged: (e) {
|
||||
setState(() {
|
||||
settings.val(write: true).talkSettings.sortFavoritesToTop = e!;
|
||||
});
|
||||
settings.val(write: true).talkSettings.sortFavoritesToTop = e!;
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -115,9 +112,7 @@ class _SettingsState extends State<Settings> {
|
||||
trailing: Checkbox(
|
||||
value: settings.val().talkSettings.sortUnreadToTop,
|
||||
onChanged: (e) {
|
||||
setState(() {
|
||||
settings.val(write: true).talkSettings.sortUnreadToTop = e!;
|
||||
});
|
||||
settings.val(write: true).talkSettings.sortUnreadToTop = e!;
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -130,9 +125,7 @@ class _SettingsState extends State<Settings> {
|
||||
trailing: Checkbox(
|
||||
value: settings.val().fileSettings.sortFoldersToTop,
|
||||
onChanged: (e) {
|
||||
setState(() {
|
||||
settings.val(write: true).fileSettings.sortFoldersToTop = e!;
|
||||
});
|
||||
settings.val(write: true).fileSettings.sortFoldersToTop = e!;
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -152,6 +145,49 @@ class _SettingsState extends State<Settings> {
|
||||
|
||||
const Divider(),
|
||||
|
||||
ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
|
||||
title: const Text("Push-Benachrichtigungen aktivieren"),
|
||||
subtitle: const Text("Lange tippen für mehr Informationen"),
|
||||
trailing: Checkbox(
|
||||
value: settings.val().notificationSettings.enabled,
|
||||
onChanged: (e) {
|
||||
if(e!) {
|
||||
ConfirmDialog(
|
||||
title: "Warnung",
|
||||
icon: Icons.warning_amber,
|
||||
content: ""
|
||||
"Die Push-Benachrichtigungen werden durch mhsl.eu versendet.\n\n"
|
||||
"Durch das aktivieren dieser Funktion wird dein Nutzername, dein Password und eine Geräte-ID von mhsl dauerhaft gespeichert und verarbeitet.\n\n"
|
||||
"Für mehr Informationen drücke lange auf die Einstellungsoption!",
|
||||
confirmButton: "Aktivieren",
|
||||
onConfirm: () {
|
||||
settings.val(write: true).notificationSettings.enabled = e;
|
||||
NotifyUpdater.registerToServer();
|
||||
},
|
||||
).asDialog(context);
|
||||
} else {
|
||||
settings.val(write: true).notificationSettings.enabled = e;
|
||||
}
|
||||
},
|
||||
),
|
||||
onLongPress: () => showDialog(context: context, builder: (context) => AlertDialog(
|
||||
title: const Text("Info über Push"),
|
||||
content: const SingleChildScrollView(child: Text(""
|
||||
"Aufgrund technischer Limitationen müssen Push-nachrichten über einen Externen Server - hier 'mhsl.eu' (Author dieser App) - erfolgen.\n\n"
|
||||
"Wenn Push aktiviert wird, werden deine Zugangsdaten und ein Token verschlüsselt an den Betreiber gesendet und von ihm unverschlüsselt gespeichert.\n\n"
|
||||
"Der extene Server verwendet die Zugangsdaten um sich maschinell in Nextcloud Talk anzumelden und via Websockets auf neue Nachrichten zu warten.\n\n"
|
||||
"Wenn eine neue Nachricht eintrifft wird dein Telefon via FBC-Messaging (Google Firebase Push) vom Externen Server benachrichtigt.\n\n"
|
||||
"Behalte im Hinterkopf, dass deine Zugangsdaten auf einem Externen Server gespeichert werden und dies trots bester Absichten ein Sicherheitsrisiko sein kann!"
|
||||
)),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("Zurück"))
|
||||
],
|
||||
)),
|
||||
),
|
||||
|
||||
const Divider(),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.live_help_outlined),
|
||||
title: const Text("Informationen und Lizenzen"),
|
||||
@ -215,9 +251,7 @@ class _SettingsState extends State<Settings> {
|
||||
trailing: Checkbox(
|
||||
value: settings.val().devToolsEnabled,
|
||||
onChanged: (state) {
|
||||
changeView() => setState(() {
|
||||
settings.val(write: true).devToolsEnabled = state ?? false;
|
||||
});
|
||||
changeView() => settings.val(write: true).devToolsEnabled = state ?? false;
|
||||
|
||||
if(!state!) {
|
||||
changeView();
|
||||
@ -284,9 +318,7 @@ class _SettingsState extends State<Settings> {
|
||||
content: "Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.",
|
||||
confirmButton: "Unwiederruflich Löschen",
|
||||
onConfirm: () {
|
||||
setState(() {
|
||||
Provider.of<SettingsProvider>(context, listen: false).reset();
|
||||
});
|
||||
Provider.of<SettingsProvider>(context, listen: false).reset();
|
||||
},
|
||||
).asDialog(context);
|
||||
},
|
||||
|
Reference in New Issue
Block a user