diff --git a/analysis_options.yaml b/analysis_options.yaml index 61b6c4de..b787b0ca 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + experimental_member_use: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/data_layer/repositories/direct_message_repository_impl.dart b/lib/data_layer/repositories/direct_message_repository_impl.dart index 254a44c6..868791bb 100644 --- a/lib/data_layer/repositories/direct_message_repository_impl.dart +++ b/lib/data_layer/repositories/direct_message_repository_impl.dart @@ -786,10 +786,12 @@ class DirectMessageRepositoryImpl implements DirectMessageRepository { _notifyMessagesChanged(recipientPubkey); _notifyConversationsChanged(); - log( - 'DM: Message ${relayConfirmed ? 'sent successfully' : 'failed - no relay confirmation'}', - ); + if (!relayConfirmed) { + log('DM: Message failed - no relay confirmation'); + throw Exception('No relay confirmation'); + } + log('DM: Message sent successfully'); return finalMessage; } catch (e) { log('DM: Error sending message: $e'); diff --git a/lib/main.dart b/lib/main.dart index 51946430..d6cb4e91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:window_manager/window_manager.dart'; import 'package:flutter_mentions/flutter_mentions.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ndk/ndk.dart'; +import 'package:toastification/toastification.dart'; import 'domain_layer/entities/stored_account.dart'; import 'l10n/app_localizations.dart'; //import 'package:device_preview/device_preview.dart'; @@ -183,57 +184,59 @@ class MyApp extends ConsumerWidget { themeColor: themeState.color, ); - return Portal( - child: MaterialApp.router( - routerConfig: router, - scrollBehavior: const MaterialScrollBehavior().copyWith( - scrollbars: false, - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - PointerDeviceKind.trackpad, - }, - ), - debugShowCheckedModeBanner: false, - title: 'camelus', - locale: currentLocale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - theme: themeVariants.lightTheme, - darkTheme: themeVariants.darkTheme, - themeMode: themeState.mode, - builder: (context, child) { - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - return DragToResizeArea( - child: Stack( - children: [ - child!, - Positioned( - top: 0, - left: 0, - right: 0, - child: SizedBox( - height: 32, - child: Row( - children: [ - Expanded(child: DragToMoveArea(child: Container())), - SizedBox( - width: 154, - child: WindowCaption( - brightness: Theme.of(context).brightness, - backgroundColor: Colors.transparent, + return ToastificationWrapper( + child: Portal( + child: MaterialApp.router( + routerConfig: router, + scrollBehavior: const MaterialScrollBehavior().copyWith( + scrollbars: false, + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + PointerDeviceKind.trackpad, + }, + ), + debugShowCheckedModeBanner: false, + title: 'camelus', + locale: currentLocale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + theme: themeVariants.lightTheme, + darkTheme: themeVariants.darkTheme, + themeMode: themeState.mode, + builder: (context, child) { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + return DragToResizeArea( + child: Stack( + children: [ + child!, + Positioned( + top: 0, + left: 0, + right: 0, + child: SizedBox( + height: 32, + child: Row( + children: [ + Expanded(child: DragToMoveArea(child: Container())), + SizedBox( + width: 154, + child: WindowCaption( + brightness: Theme.of(context).brightness, + backgroundColor: Colors.transparent, + ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - ); - } - return child!; - }, + ], + ), + ); + } + return child!; + }, + ), ), ); } diff --git a/lib/presentation_layer/routes/messages/dm_thread_page.dart b/lib/presentation_layer/routes/messages/dm_thread_page.dart index 72fafcf6..7e165390 100644 --- a/lib/presentation_layer/routes/messages/dm_thread_page.dart +++ b/lib/presentation_layer/routes/messages/dm_thread_page.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:ndk/shared/nips/nip19/nip19.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:toastification/toastification.dart'; import '../../../domain_layer/entities/direct_message.dart'; import '../../atoms/my_profile_picture.dart'; @@ -220,9 +221,13 @@ class _DmThreadPageState extends ConsumerState { onDelete: (messageId) => ref .read(dmThreadProvider(_peerPubkey).notifier) .deleteMessage(messageId), - onRetry: (messageId) => ref - .read(dmThreadProvider(_peerPubkey).notifier) - .retrySendMessage(messageId), + onRetry: (messageId) async { + final success = await ref + .read(dmThreadProvider(_peerPubkey).notifier) + .retrySendMessage(messageId); + if (!success && mounted) _showSendErrorToast(); + return success; + }, onRemoveFailedMessage: (messageId) => ref .read(dmThreadProvider(_peerPubkey).notifier) .removeFailedMessage(messageId), @@ -467,14 +472,26 @@ class _DmThreadPageState extends ConsumerState { ); } + void _showSendErrorToast() { + toastification.show( + context: context, + type: ToastificationType.error, + title: Text(AppLocalizations.of(context)!.failedToSendMessage), + autoCloseDuration: const Duration(seconds: 10), + showProgressBar: true, + ); + } + Future _sendMessage() async { final content = _messageController.text.trim(); if (content.isEmpty) return; _messageController.clear(); - // Message is added optimistically, no need to wait for result - // or show error snackbar - error will be shown in the message bubble - ref.read(dmThreadProvider(_peerPubkey).notifier).sendMessage(content); + final success = await ref + .read(dmThreadProvider(_peerPubkey).notifier) + .sendMessage(content); + + if (!success && mounted) _showSendErrorToast(); } } diff --git a/pubspec.lock b/pubspec.lock index a28210e1..df18bc37 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1856,7 +1856,7 @@ packages: source: hosted version: "0.10.1" toastification: - dependency: transitive + dependency: "direct main" description: name: toastification sha256: "69db2bff425b484007409650d8bcd5ed1ce2e9666293ece74dcd917dacf23112" diff --git a/pubspec.yaml b/pubspec.yaml index 6d3bc6e2..d5ea79c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,7 +78,7 @@ dependencies: phosphor_flutter: ^2.1.0 # ndk imports - ndk: ^0.7.1-dev.14 + ndk: ^0.7.1-dev.17 ndk_rust_verifier: ^0.5.0 ndk_amber: ^0.4.0-dev.15 ndk_objectbox: ^0.2.8-dev.14 @@ -106,6 +106,7 @@ dependencies: sdk: flutter system_theme: ^3.1.2 ndk_flutter: ^0.0.2-dev.8 + toastification: ^3.0.3