Switch to better notification library

This commit is contained in:
2024-05-31 23:21:24 +02:00
parent dfcee5dfc7
commit d5d89ee93a
15 changed files with 320 additions and 65 deletions

View File

@@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:simplecloudnotifier/models/api_error.dart';
@@ -14,6 +13,7 @@ import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/models/message.dart';
import 'package:simplecloudnotifier/utils/toaster.dart';
enum ChannelSelector {
owned(apiKey: 'owned'), // Return all channels of the user
@@ -71,7 +71,7 @@ class APIClient {
responseHeaders = response.headers;
} catch (exc, trace) {
RequestLog.addRequestException(name, t0, method, uri, req.body, req.headers, exc, trace);
showPlatformToast(child: Text('Request "${name}" failed'), context: ToastProvider.context);
Toaster.error("Error", 'Request "${name}" failed');
ApplicationLog.error('Request "${name}" failed: ' + exc.toString(), trace: trace);
rethrow;
}
@@ -81,14 +81,14 @@ class APIClient {
final apierr = APIError.fromJson(jsonDecode(responseBody) as Map<String, dynamic>);
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
Toaster.error("Error", 'Request "${name}" failed');
throw Exception(apierr.message);
} catch (exc, trace) {
ApplicationLog.warn('Failed to decode api response as error-object', additional: exc.toString() + "\nBody:\n" + responseBody, trace: trace);
}
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
Toaster.error("Error", 'Request "${name}" failed');
throw Exception('API request failed with status code ${responseStatusCode}');
}
@@ -105,7 +105,7 @@ class APIClient {
}
} catch (exc, trace) {
RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
Toaster.error("Error", 'Request "${name}" failed');
ApplicationLog.error('Failed to decode response: ' + exc.toString(), additional: "\nBody:\n" + responseBody, trace: trace);
rethrow;
}

View File

@@ -1,5 +1,4 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:hive_flutter/hive_flutter.dart';
@@ -11,6 +10,7 @@ import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
import 'package:simplecloudnotifier/state/user_account.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:toastification/toastification.dart';
import 'firebase_options.dart';
void main() async {
@@ -87,15 +87,22 @@ class SCNApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<AppTheme>(
builder: (context, appTheme, child) => MaterialApp(
title: 'SimpleCloudNotifier',
theme: ThemeData(
//TODO color settings
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
useMaterial3: true,
return ToastificationWrapper(
config: ToastificationConfig(
itemWidth: 440,
marginBuilder: (alignment) => EdgeInsets.symmetric(vertical: 64),
animationDuration: Duration(milliseconds: 200),
),
child: Consumer<AppTheme>(
builder: (context, appTheme, child) => MaterialApp(
title: 'SimpleCloudNotifier',
theme: ThemeData(
//TODO color settings
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
useMaterial3: true,
),
home: SCNNavLayout(),
),
home: const ToastProvider(child: SCNNavLayout()),
),
);
}

View File

@@ -1,4 +1,3 @@
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_lazy_indexed_stack/flutter_lazy_indexed_stack.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -11,6 +10,7 @@ import 'package:simplecloudnotifier/pages/account/account.dart';
import 'package:simplecloudnotifier/pages/message_list/message_list.dart';
import 'package:simplecloudnotifier/pages/settings/root.dart';
import 'package:simplecloudnotifier/state/user_account.dart';
import 'package:simplecloudnotifier/utils/toaster.dart';
class SCNNavLayout extends StatefulWidget {
const SCNNavLayout({super.key});
@@ -33,7 +33,7 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
void _onItemTapped(int index) {
final userAcc = Provider.of<UserAccount>(context, listen: false);
if (userAcc.auth == null) {
showPlatformToast(child: Text('Please login first or create a new account'), context: ToastProvider.context);
Toaster.info("Not logged in", "Please login or create a new account first");
return;
}
@@ -45,7 +45,7 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
void _onFABTapped() {
final userAcc = Provider.of<UserAccount>(context, listen: false);
if (userAcc.auth == null) {
showPlatformToast(child: Text('Please login first or create a new account'), context: ToastProvider.context);
Toaster.info("Not logged in", "Please login or create a new account first");
return;
}

View File

@@ -44,35 +44,37 @@ class _AccountRootPageState extends State<AccountRootPage> {
futureChannelAllCount = null;
futureChannelSubscribedCount = null;
futureChannelAllCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.all);
return channels.length;
}();
if (userAcc.auth != null) {
futureChannelAllCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.all);
return channels.length;
}();
futureChannelSubscribedCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.subscribed);
return channels.length;
}();
futureChannelSubscribedCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.subscribed);
return channels.length;
}();
futureSubscriptionCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final subs = await APIClient.getSubscriptionList(userAcc.auth!);
return subs.length;
}();
futureSubscriptionCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final subs = await APIClient.getSubscriptionList(userAcc.auth!);
return subs.length;
}();
futureClientCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final clients = await APIClient.getClientList(userAcc.auth!);
return clients.length;
}();
futureClientCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final clients = await APIClient.getClientList(userAcc.auth!);
return clients.length;
}();
futureKeyCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final keys = await APIClient.getKeyTokenList(userAcc.auth!);
return keys.length;
}();
futureKeyCount = () async {
if (userAcc.auth == null) throw new Exception('not logged in');
final keys = await APIClient.getKeyTokenList(userAcc.auth!);
return keys.length;
}();
}
}
@override

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:simplecloudnotifier/utils/toaster.dart';
import 'package:toastification/toastification.dart';
class DebugActionsPage extends StatefulWidget {
@override
_DebugActionsPageState createState() => _DebugActionsPageState();
}
class _DebugActionsPageState extends State<DebugActionsPage> {
@override
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: () => Toaster.success("Hello World", "This was a triumph!"),
child: const Text('Show Success Notification'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: () => Toaster.info("Hello World", "This was a triumph!"),
child: const Text('Show Info Notification'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: () => Toaster.warn("Hello World", "This was a triumph!"),
child: const Text('Show Warn Notification'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: () => Toaster.error("Hello World", "This was a triumph!"),
child: const Text('Show Info Notification'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: () => Toaster.simple("Hello World"),
child: const Text('Show Simple Notification'),
),
SizedBox(height: 20),
],
),
),
),
);
}
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
import 'package:simplecloudnotifier/pages/debug/debug_actions.dart';
import 'package:simplecloudnotifier/pages/debug/debug_colors.dart';
import 'package:simplecloudnotifier/pages/debug/debug_logs.dart';
import 'package:simplecloudnotifier/pages/debug/debug_persistence.dart';
@@ -10,7 +12,7 @@ class DebugMainPage extends StatefulWidget {
_DebugMainPageState createState() => _DebugMainPageState();
}
enum DebugMainPageSubPage { colors, requests, persistence, logs }
enum DebugMainPageSubPage { colors, requests, persistence, logs, actions }
class _DebugMainPageState extends State<DebugMainPage> {
final Map<DebugMainPageSubPage, Widget> _subpages = {
@@ -18,6 +20,7 @@ class _DebugMainPageState extends State<DebugMainPage> {
DebugMainPageSubPage.requests: DebugRequestsPage(),
DebugMainPageSubPage.persistence: DebugPersistencePage(),
DebugMainPageSubPage.logs: DebugLogsPage(),
DebugMainPageSubPage.actions: DebugActionsPage(),
};
DebugMainPageSubPage _subPage = DebugMainPageSubPage.colors;
@@ -51,11 +54,16 @@ class _DebugMainPageState extends State<DebugMainPage> {
return SegmentedButton<DebugMainPageSubPage>(
showSelectedIcon: false,
segments: const <ButtonSegment<DebugMainPageSubPage>>[
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.colors, label: Text('Theme')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.requests, label: Text('Requests')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.persistence, label: Text('Persistence')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.logs, label: Text('Logs')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.colors, icon: Icon(FontAwesomeIcons.solidPaintRoller, size: 14)),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.actions, icon: Icon(FontAwesomeIcons.solidHammer, size: 14)),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.requests, icon: Icon(FontAwesomeIcons.solidNetworkWired, size: 14)),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.persistence, icon: Icon(FontAwesomeIcons.solidFloppyDisk, size: 14)),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.logs, icon: Icon(FontAwesomeIcons.solidFileLines, size: 14)),
],
style: ButtonStyle(
padding: MaterialStateProperty.all<EdgeInsets>(EdgeInsets.fromLTRB(0, 0, 0, 0)),
visualDensity: VisualDensity(horizontal: -3, vertical: -3),
),
selected: <DebugMainPageSubPage>{_subPage},
onSelectionChanged: (Set<DebugMainPageSubPage> v) {
setState(() {

View File

@@ -1,9 +1,9 @@
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
import 'package:simplecloudnotifier/utils/toaster.dart';
class DebugRequestViewPage extends StatelessWidget {
final SCNRequest request;
@@ -64,7 +64,7 @@ class DebugRequestViewPage extends StatelessWidget {
constraints: BoxConstraints(),
onPressed: () {
Clipboard.setData(new ClipboardData(text: value));
showPlatformToast(child: Text('Copied to clipboard'), context: ToastProvider.context);
Toaster.info("Clipboard", 'Copied text to Clipboard');
},
),
],

View File

@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:toastification/toastification.dart';
class Toaster {
// https://payamzahedi.com/toastification/
static const autoCloseDuration = Duration(seconds: 4);
static const alignment = Alignment.topCenter;
static const animationDuration = Duration(milliseconds: 200);
static final borderRadius = BorderRadius.circular(4.0);
static void simple(String title) {
toastification.show(
type: ToastificationType.success,
style: ToastificationStyle.simple,
title: Text(title),
description: Text(title),
autoCloseDuration: autoCloseDuration,
borderRadius: borderRadius,
closeButtonShowType: CloseButtonShowType.none,
alignment: alignment,
animationDuration: animationDuration,
pauseOnHover: false,
applyBlurEffect: true,
closeOnClick: true,
showProgressBar: false,
);
}
static void success(String title, String message) {
toastification.show(
type: ToastificationType.success,
style: ToastificationStyle.flatColored,
title: Text(title),
description: Text(message),
autoCloseDuration: autoCloseDuration,
borderRadius: borderRadius,
closeButtonShowType: CloseButtonShowType.none,
alignment: alignment,
animationDuration: animationDuration,
pauseOnHover: false,
applyBlurEffect: true,
closeOnClick: true,
showProgressBar: false,
);
}
static void info(String title, String message) {
toastification.show(
type: ToastificationType.info,
style: ToastificationStyle.flatColored,
title: Text(title),
description: Text(message),
autoCloseDuration: autoCloseDuration,
borderRadius: borderRadius,
closeButtonShowType: CloseButtonShowType.none,
alignment: alignment,
animationDuration: animationDuration,
pauseOnHover: false,
applyBlurEffect: true,
closeOnClick: true,
showProgressBar: false,
);
}
static void warn(String title, String message) {
toastification.show(
type: ToastificationType.warning,
style: ToastificationStyle.flatColored,
title: Text(title),
description: Text(message),
autoCloseDuration: autoCloseDuration,
borderRadius: borderRadius,
closeButtonShowType: CloseButtonShowType.none,
alignment: alignment,
animationDuration: animationDuration,
pauseOnHover: false,
applyBlurEffect: true,
closeOnClick: true,
showProgressBar: false,
);
}
static void error(String title, String message) {
toastification.show(
type: ToastificationType.error,
style: ToastificationStyle.flatColored,
title: Text(title),
description: Text(message),
autoCloseDuration: autoCloseDuration,
borderRadius: borderRadius,
closeButtonShowType: CloseButtonShowType.none,
alignment: alignment,
animationDuration: animationDuration,
pauseOnHover: false,
applyBlurEffect: true,
closeOnClick: true,
showProgressBar: false,
);
}
}