From 5922d325e523d49bc2b0d7e5319d1e14427412bb Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 29 Apr 2024 20:22:06 +0800 Subject: [PATCH] :bug: Bug fixes --- ios/Podfile.lock | 6 + lib/i18n/app_en.arb | 3 +- lib/i18n/app_zh.arb | 3 +- lib/providers/notify.dart | 45 ++-- lib/screens/account/friend.dart | 13 +- lib/screens/chat/call.dart | 57 ++--- lib/screens/chat/channel/editor.dart | 5 +- lib/screens/chat/channel/member.dart | 13 +- lib/screens/chat/chat.dart | 9 +- lib/screens/chat/index.dart | 5 +- lib/screens/notification.dart | 18 +- lib/screens/posts/comment_editor.dart | 5 +- lib/screens/posts/moment_editor.dart | 5 +- lib/screens/posts/screen.dart | 5 +- lib/widgets/chat/call/controls.dart | 5 +- lib/widgets/chat/call/exts.dart | 247 +++++--------------- lib/widgets/chat/call/no_content.dart | 60 +++-- lib/widgets/chat/call/participant.dart | 47 ++-- lib/widgets/chat/call/participant_menu.dart | 5 - lib/widgets/chat/channel_action.dart | 9 +- lib/widgets/chat/channel_deletion.dart | 9 +- lib/widgets/chat/message_deletion.dart | 5 +- lib/widgets/chat/message_editor.dart | 5 +- lib/widgets/exts.dart | 18 ++ lib/widgets/navigation_drawer.dart | 2 +- lib/widgets/posts/attachment_editor.dart | 13 +- lib/widgets/posts/item_deletion.dart | 5 +- lib/widgets/posts/reaction_action.dart | 5 +- macos/Runner/Info.plist | 2 +- 29 files changed, 249 insertions(+), 380 deletions(-) create mode 100644 lib/widgets/exts.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2b9fc29..6989be7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,6 +5,8 @@ PODS: - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) + - flutter_local_notifications (0.0.1): + - Flutter - flutter_secure_storage (6.0.0): - Flutter - flutter_webrtc (0.9.36): @@ -44,6 +46,7 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -71,6 +74,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" flutter_webrtc: @@ -106,6 +111,7 @@ SPEC CHECKSUMS: connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be flutter_webrtc: 9bc044b0b5bcaabd0fb7d52c90421fb540f8c35e image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18 diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 30cb705..a7aab36 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -1,5 +1,5 @@ { - "solian": "Solian", + "appName": "Solar Network", "explore": "Explore", "chat": "Chat", "account": "Account", @@ -28,6 +28,7 @@ "report": "Report", "reply": "Reply", "settings": "Settings", + "errorHappened": "An Error Occurred", "notification": "Notification", "notifyDone": "You're done!", "notifyDoneCaption": "There are no notifications unread for you.", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 586bbc1..0024315 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -1,5 +1,5 @@ { - "solian": "索链", + "appName": "Solar", "explore": "探索", "chat": "聊天", "account": "账号", @@ -28,6 +28,7 @@ "report": "举报", "reply": "回复", "settings": "设置", + "errorHappened": "发生了错误", "notification": "通知", "notifyDone": "所有通知已读!", "notifyDoneCaption": "这里没有什么东西可以给你看的了~", diff --git a/lib/providers/notify.dart b/lib/providers/notify.dart index d18c593..5a3065b 100644 --- a/lib/providers/notify.dart +++ b/lib/providers/notify.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:livekit_client/livekit_client.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/providers/auth.dart'; @@ -24,17 +25,25 @@ class NotifyProvider extends ChangeNotifier { } void initNotify() { + const androidSettings = AndroidInitializationSettings('app_icon'); + const darwinSettings = DarwinInitializationSettings( + notificationCategories: [ + DarwinNotificationCategory("general"), + ], + ); + const linuxSettings = LinuxInitializationSettings(defaultActionName: 'Open notification'); const InitializationSettings initializationSettings = InitializationSettings( - android: AndroidInitializationSettings('app_icon'), - iOS: DarwinInitializationSettings(), - macOS: DarwinInitializationSettings(), - linux: LinuxInitializationSettings(defaultActionName: 'Open notification'), + android: androidSettings, + iOS: darwinSettings, + macOS: darwinSettings, + linux: linuxSettings, ); localNotify.initialize(initializationSettings); } Future requestPermissions() async { + if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return; await Permission.notification.request(); } @@ -78,20 +87,30 @@ class NotifyProvider extends ChangeNotifier { } void notifyMessage(String title, String body) { + const androidSettings = AndroidNotificationDetails( + 'general', + 'General', + importance: Importance.high, + priority: Priority.high, + silent: true, + ); + const darwinSettings = DarwinNotificationDetails( + presentAlert: true, + presentBanner: true, + presentBadge: true, + presentSound: false, + ); + const linuxSettings = LinuxNotificationDetails(); + localNotify.show( math.max(1, math.Random().nextInt(100000000)), title, body, const NotificationDetails( - android: AndroidNotificationDetails( - 'general', - 'General', - importance: Importance.high, - priority: Priority.high, - ), - iOS: DarwinNotificationDetails(), - macOS: DarwinNotificationDetails(), - linux: LinuxNotificationDetails(), + android: androidSettings, + iOS: darwinSettings, + macOS: darwinSettings, + linux: linuxSettings, ), ); } diff --git a/lib/screens/account/friend.dart b/lib/screens/account/friend.dart index aee9523..23b1f7d 100644 --- a/lib/screens/account/friend.dart +++ b/lib/screens/account/friend.dart @@ -7,6 +7,7 @@ import 'package:solian/models/friendship.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/account/avatar.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -40,9 +41,7 @@ class _FriendScreenState extends State { }); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } } @@ -65,9 +64,7 @@ class _FriendScreenState extends State { await fetchFriendships(); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); @@ -97,9 +94,7 @@ class _FriendScreenState extends State { await fetchFriendships(); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); diff --git a/lib/screens/chat/call.dart b/lib/screens/chat/call.dart index e87914f..9357324 100644 --- a/lib/screens/chat/call.dart +++ b/lib/screens/chat/call.dart @@ -12,6 +12,7 @@ import 'package:solian/widgets/chat/call/controls.dart'; import 'package:solian/widgets/chat/call/exts.dart'; import 'package:solian/widgets/chat/call/participant.dart'; import 'package:solian/widgets/chat/call/participant_menu.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -81,9 +82,7 @@ class _ChatCallState extends State { return (_token!, _endpoint!); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); throw Exception(message); } } @@ -138,10 +137,7 @@ class _ChatCallState extends State { setupRoom(); } catch (e) { - final message = e.toString(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(e); } finally { notify.close(); } @@ -182,9 +178,6 @@ class _ChatCallState extends State { if (router.canPop()) router.pop(); }) ..on((event) => sortParticipants()) - ..on((event) { - context.showRecordingStatusChangedDialog(event.activeRecording); - }) ..on((_) => sortParticipants()) ..on((_) => sortParticipants()) ..on((_) => sortParticipants()) @@ -203,30 +196,22 @@ class _ChatCallState extends State { } void sortParticipants() { - List screenTracks = []; - Map userMediaTracks = {}; + Map mediaTracks = {}; for (var participant in _callRoom.remoteParticipants.values) { - userMediaTracks[participant.sid] = ParticipantTrack( + mediaTracks[participant.sid] = ParticipantTrack( participant: participant, videoTrack: null, isScreenShare: false, ); for (var t in participant.videoTrackPublications) { - if (t.isScreenShare) { - screenTracks.add(ParticipantTrack( - participant: participant, - videoTrack: t.track as VideoTrack, - isScreenShare: true, - )); - } else { - userMediaTracks[participant.sid]?.videoTrack = t.track; - } + mediaTracks[participant.sid]?.videoTrack = t.track; + mediaTracks[participant.sid]?.isScreenShare = t.isScreenShare; } } - final userMediaTrackList = userMediaTracks.values.toList(); - userMediaTrackList.sort((a, b) { + final mediaTrackList = mediaTracks.values.toList(); + mediaTrackList.sort((a, b) { // Loudest people first if (a.participant.isSpeaking && b.participant.isSpeaking) { if (a.participant.audioLevel > b.participant.audioLevel) { @@ -262,22 +247,20 @@ class _ChatCallState extends State { final localParticipantTracks = _callRoom.localParticipant?.videoTrackPublications; if (localParticipantTracks != null) { for (var t in localParticipantTracks) { - if (t.isScreenShare) { - screenTracks.add(ParticipantTrack( - participant: _callRoom.localParticipant!, - videoTrack: t.track as VideoTrack, - isScreenShare: true, - )); - } else { - localTrack.videoTrack = t.track; - } + localTrack.videoTrack = t.track; + localTrack.isScreenShare = t.isScreenShare; } } } setState(() { - _participantTracks = [...screenTracks, localTrack, ...userMediaTrackList]; - _focusParticipant ??= _participantTracks.first; + _participantTracks = [localTrack, ...mediaTrackList]; + if (_focusParticipant == null) { + _focusParticipant = _participantTracks.first; + } else { + final idx = _participantTracks.indexWhere((x) => _focusParticipant!.participant.sid == x.participant.sid); + _focusParticipant = _participantTracks[idx]; + } }); } @@ -466,6 +449,7 @@ class InteractiveParticipantWidget extends StatelessWidget { final double? width; final double? height; final Color? color; + final bool? isFixed; final ParticipantTrack participant; final Function() onTap; @@ -474,6 +458,7 @@ class InteractiveParticipantWidget extends StatelessWidget { this.width, this.height, this.color, + this.isFixed = false, required this.participant, required this.onTap, }); @@ -485,7 +470,7 @@ class InteractiveParticipantWidget extends StatelessWidget { width: width, height: height, color: color, - child: ParticipantWidget.widgetFor(participant), + child: ParticipantWidget.widgetFor(participant, isFixed: true), ), onTap: () => onTap(), onLongPress: () { diff --git a/lib/screens/chat/channel/editor.dart b/lib/screens/chat/channel/editor.dart index fea7f69..307b114 100644 --- a/lib/screens/chat/channel/editor.dart +++ b/lib/screens/chat/channel/editor.dart @@ -8,6 +8,7 @@ import 'package:solian/models/channel.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:uuid/uuid.dart'; @@ -52,9 +53,7 @@ class _ChannelEditorScreenState extends State { var res = await Response.fromStream(await auth.client!.send(req)); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else { if (router.canPop()) { router.pop(true); diff --git a/lib/screens/chat/channel/member.dart b/lib/screens/chat/channel/member.dart index fc6c07a..f3d5d2d 100644 --- a/lib/screens/chat/channel/member.dart +++ b/lib/screens/chat/channel/member.dart @@ -9,6 +9,7 @@ import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/account/avatar.dart'; import 'package:solian/widgets/account/friend_picker.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -45,9 +46,7 @@ class _ChatMemberScreenState extends State { }); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } } @@ -75,9 +74,7 @@ class _ChatMemberScreenState extends State { await fetchMemberships(); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); @@ -107,9 +104,7 @@ class _ChatMemberScreenState extends State { await fetchMemberships(); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); diff --git a/lib/screens/chat/chat.dart b/lib/screens/chat/chat.dart index a6d7b92..6747d07 100644 --- a/lib/screens/chat/chat.dart +++ b/lib/screens/chat/chat.dart @@ -16,6 +16,7 @@ import 'package:solian/widgets/chat/maintainer.dart'; import 'package:solian/widgets/chat/message.dart'; import 'package:solian/widgets/chat/message_action.dart'; import 'package:solian/widgets/chat/message_editor.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; @@ -46,9 +47,7 @@ class _ChatScreenState extends State { return _channelMeta!; } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); throw Exception(message); } } @@ -62,9 +61,7 @@ class _ChatScreenState extends State { return _ongoingCall; } else if (res.statusCode != 404) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); throw Exception(message); } else { return null; diff --git a/lib/screens/chat/index.dart b/lib/screens/chat/index.dart index ab6f5a7..8c56a37 100644 --- a/lib/screens/chat/index.dart +++ b/lib/screens/chat/index.dart @@ -7,6 +7,7 @@ import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/chat/chat_new.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/widgets/notification_notifier.dart'; @@ -36,9 +37,7 @@ class _ChatIndexScreenState extends State { }); } else { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } } diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index 36b2563..6e6269c 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -21,6 +21,10 @@ class _NotificationScreenState extends State { final auth = context.read(); final nty = context.watch(); + WidgetsBinding.instance.addPostFrameCallback((_) { + nty.allRead(); + }); + return IndentWrapper( noSafeArea: true, hideDrawer: true, @@ -48,9 +52,7 @@ class _NotificationScreenState extends State { return NotificationItem( index: index, item: element, - onDismiss: () => setState(() { - nty.clearAt(index); - }), + onDismiss: () => nty.clearAt(index), ); }, ), @@ -69,14 +71,6 @@ class _NotificationScreenState extends State { ), ); } - - @override - void dispose() { - final nty = context.read(); - nty.allRead(); - nty.clearRealtime(); - super.dispose(); - } } class NotificationItem extends StatelessWidget { @@ -141,7 +135,7 @@ class NotificationItem extends StatelessWidget { @override Widget build(BuildContext context) { return Dismissible( - key: Key('n$index'), + key: Key((DateTime.now().millisecondsSinceEpoch << 10).toString()), onDismissed: (direction) { markAsRead(item, context).then((value) { ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/screens/posts/comment_editor.dart b/lib/screens/posts/comment_editor.dart index 899dae2..00468b0 100644 --- a/lib/screens/posts/comment_editor.dart +++ b/lib/screens/posts/comment_editor.dart @@ -10,6 +10,7 @@ import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/widgets/account/avatar.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/posts/attachment_editor.dart'; @@ -75,9 +76,7 @@ class _CommentEditorScreenState extends State { var res = await Response.fromStream(await auth.client!.send(req)); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else { if (router.canPop()) { router.pop(true); diff --git a/lib/screens/posts/moment_editor.dart b/lib/screens/posts/moment_editor.dart index ae1b838..0c603fd 100644 --- a/lib/screens/posts/moment_editor.dart +++ b/lib/screens/posts/moment_editor.dart @@ -10,6 +10,7 @@ import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/widgets/account/avatar.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/posts/attachment_editor.dart'; @@ -65,9 +66,7 @@ class _MomentEditorScreenState extends State { var res = await Response.fromStream(await auth.client!.send(req)); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else { if (router.canPop()) { router.pop(true); diff --git a/lib/screens/posts/screen.dart b/lib/screens/posts/screen.dart index 0aafb9c..58b256f 100644 --- a/lib/screens/posts/screen.dart +++ b/lib/screens/posts/screen.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart' as http; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:solian/models/post.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/posts/comment_list.dart'; import 'package:solian/widgets/posts/item.dart'; @@ -30,9 +31,7 @@ class _PostScreenState extends State { final res = await _client.get(uri); if (res.statusCode != 200) { final err = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $err")), - ); + context.showErrorDialog(err); return null; } else { return Post.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); diff --git a/lib/widgets/chat/call/controls.dart b/lib/widgets/chat/call/controls.dart index a64be26..1a0c817 100644 --- a/lib/widgets/chat/call/controls.dart +++ b/lib/widgets/chat/call/controls.dart @@ -6,6 +6,7 @@ import 'package:flutter_background/flutter_background.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/exts.dart'; class ControlsWidget extends StatefulWidget { final Room room; @@ -134,9 +135,7 @@ class _ControlsWidgetState extends State { await participant.publishVideoTrack(track); } catch (e) { final message = e.toString(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text('Something went wrong... $message'), - )); + context.showErrorDialog(message); } return; } diff --git a/lib/widgets/chat/call/exts.dart b/lib/widgets/chat/call/exts.dart index 41130d7..e4f540e 100644 --- a/lib/widgets/chat/call/exts.dart +++ b/lib/widgets/chat/call/exts.dart @@ -1,206 +1,73 @@ import 'package:flutter/material.dart'; extension SolianCallExt on BuildContext { - Future showPublishDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Publish'), - content: const Text('Would you like to publish your Camera & Mic ?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('NO'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('YES'), - ), - ], - ), - ); - Future showPlayAudioManuallyDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Play Audio'), - content: const Text( - 'You need to manually activate audio PlayBack for iOS Safari !'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Ignore'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('Play Audio'), - ), - ], - ), - ); - - Future showUnPublishDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('UnPublish'), - content: - const Text('Would you like to un-publish your Camera & Mic ?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('NO'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('YES'), - ), - ], - ), - ); - - Future showErrorDialog(dynamic exception) => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Error'), - content: Text(exception.toString()), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('OK'), - ) - ], - ), - ); - - Future showDisconnectDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Disconnect'), - content: const Text('Are you sure to disconnect?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('Disconnect'), - ), - ], - ), - ); - - Future showReconnectDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Reconnect'), - content: const Text('This will force a reconnection'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('Reconnect'), - ), - ], - ), - ); - - Future showReconnectSuccessDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Reconnect'), - content: const Text('Reconnection was successful.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('OK'), - ), - ], - ), - ); - - Future showSendDataDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Send data'), - content: const Text( - 'This will send a sample data to all participants in the room'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('Send'), - ), - ], - ), - ); - - Future showDataReceivedDialog(String data) => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Received data'), - content: Text(data), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('OK'), - ), - ], - ), - ); - - Future showRecordingStatusChangedDialog(bool isActiveRecording) => - showDialog( context: this, builder: (ctx) => AlertDialog( - title: const Text('Room recording reminder'), - content: Text(isActiveRecording - ? 'Room recording is active.' - : 'Room recording is stoped.'), + title: const Text('Play Audio'), + content: const Text( + 'You need to manually activate audio PlayBack for iOS Safari!', + ), actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Ignore'), + ), TextButton( onPressed: () => Navigator.pop(ctx, true), - child: const Text('OK'), + child: const Text('Play Audio'), ), ], ), ); - Future showSubscribePermissionDialog() => showDialog( - context: this, - builder: (ctx) => AlertDialog( - title: const Text('Allow subscription'), - content: const Text( - 'Allow all participants to subscribe tracks published by local participant?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('NO'), + Future showDisconnectDialog() => showDialog( + context: this, + builder: (ctx) => AlertDialog( + title: const Text('Disconnect'), + content: const Text('Are you sure to disconnect?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('Disconnect'), + ), + ], ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('YES'), - ), - ], - ), - ); -} + ); -enum SimulateScenarioResult { - signalReconnect, - fullReconnect, - speakerUpdate, - nodeFailure, - migration, - serverLeave, - switchCandidate, - e2eeKeyRatchet, - participantName, - participantMetadata, - clear, -} \ No newline at end of file + Future showReconnectDialog() => showDialog( + context: this, + builder: (ctx) => AlertDialog( + title: const Text('Reconnect'), + content: const Text('This will force a reconnection'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('Reconnect'), + ), + ], + ), + ); + + Future showReconnectSuccessDialog() => showDialog( + context: this, + builder: (ctx) => AlertDialog( + title: const Text('Reconnect'), + content: const Text('Reconnection was successful.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('OK'), + ), + ], + ), + ); +} diff --git a/lib/widgets/chat/call/no_content.dart b/lib/widgets/chat/call/no_content.dart index 3fd5882..4f1fee5 100644 --- a/lib/widgets/chat/call/no_content.dart +++ b/lib/widgets/chat/call/no_content.dart @@ -7,8 +7,14 @@ import 'dart:math' as math; class NoContentWidget extends StatefulWidget { final Account? userinfo; final bool isSpeaking; + final bool isFixed; - const NoContentWidget({super.key, this.userinfo, required this.isSpeaking}); + const NoContentWidget({ + super.key, + this.userinfo, + this.isFixed = false, + required this.isSpeaking, + }); @override State createState() => _NoContentWidgetState(); @@ -35,35 +41,41 @@ class _NoContentWidgetState extends State with SingleTickerProv @override Widget build(BuildContext context) { - final radius = math.min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height) * 0.1; + final double radius = widget.isFixed + ? 16 + : math.min( + MediaQuery.of(context).size.width * 0.1, + MediaQuery.of(context).size.height * 0.1, + ); return Container( alignment: Alignment.center, child: Center( child: Animate( - autoPlay: false, - controller: _animationController, - effects: [ - CustomEffect( - begin: widget.isSpeaking ? 2 : 0, - end: 8, - curve: Curves.easeInOut, - duration: 1250.ms, - builder: (context, value, child) => Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(radius + 8)), - border: value > 0 ? Border.all(color: Colors.green, width: value) : null, - ), - child: child, + autoPlay: false, + controller: _animationController, + effects: [ + CustomEffect( + begin: widget.isSpeaking ? 2 : 0, + end: 8, + curve: Curves.easeInOut, + duration: 1250.ms, + builder: (context, value, child) => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(radius + 8)), + border: value > 0 ? Border.all(color: Colors.green, width: value) : null, ), - ) - ], - child: AccountAvatar( - source: widget.userinfo!.avatar, - backgroundColor: Colors.transparent, - radius: radius, - direct: true, - )), + child: child, + ), + ) + ], + child: AccountAvatar( + source: widget.userinfo!.avatar, + backgroundColor: Colors.transparent, + radius: radius, + direct: true, + ), + ), ), ); } diff --git a/lib/widgets/chat/call/participant.dart b/lib/widgets/chat/call/participant.dart index 6b635c7..bc4e63a 100644 --- a/lib/widgets/chat/call/participant.dart +++ b/lib/widgets/chat/call/participant.dart @@ -10,13 +10,24 @@ import 'package:solian/widgets/chat/call/participant_info.dart'; import 'package:solian/widgets/chat/call/participant_stats.dart'; abstract class ParticipantWidget extends StatefulWidget { - static ParticipantWidget widgetFor(ParticipantTrack participantTrack, {bool showStatsLayer = false}) { + static ParticipantWidget widgetFor(ParticipantTrack participantTrack, + {bool isFixed = false, bool showStatsLayer = false}) { if (participantTrack.participant is LocalParticipant) { - return LocalParticipantWidget(participantTrack.participant as LocalParticipant, participantTrack.videoTrack, - participantTrack.isScreenShare, showStatsLayer); + return LocalParticipantWidget( + participantTrack.participant as LocalParticipant, + participantTrack.videoTrack, + isFixed, + participantTrack.isScreenShare, + showStatsLayer, + ); } else if (participantTrack.participant is RemoteParticipant) { - return RemoteParticipantWidget(participantTrack.participant as RemoteParticipant, participantTrack.videoTrack, - participantTrack.isScreenShare, showStatsLayer); + return RemoteParticipantWidget( + participantTrack.participant as RemoteParticipant, + participantTrack.videoTrack, + isFixed, + participantTrack.isScreenShare, + showStatsLayer, + ); } throw UnimplementedError('Unknown participant type'); } @@ -24,6 +35,7 @@ abstract class ParticipantWidget extends StatefulWidget { abstract final Participant participant; abstract final VideoTrack? videoTrack; abstract final bool isScreenShare; + abstract final bool isFixed; abstract final bool showStatsLayer; final VideoQuality quality; @@ -39,6 +51,8 @@ class LocalParticipantWidget extends ParticipantWidget { @override final VideoTrack? videoTrack; @override + final bool isFixed; + @override final bool isScreenShare; @override final bool showStatsLayer; @@ -46,6 +60,7 @@ class LocalParticipantWidget extends ParticipantWidget { const LocalParticipantWidget( this.participant, this.videoTrack, + this.isFixed, this.isScreenShare, this.showStatsLayer, { super.key, @@ -61,6 +76,8 @@ class RemoteParticipantWidget extends ParticipantWidget { @override final VideoTrack? videoTrack; @override + final bool isFixed; + @override final bool isScreenShare; @override final bool showStatsLayer; @@ -68,6 +85,7 @@ class RemoteParticipantWidget extends ParticipantWidget { const RemoteParticipantWidget( this.participant, this.videoTrack, + this.isFixed, this.isScreenShare, this.showStatsLayer, { super.key, @@ -80,8 +98,6 @@ class RemoteParticipantWidget extends ParticipantWidget { abstract class _ParticipantWidgetState extends State { VideoTrack? get _activeVideoTrack; - TrackPublication? get _videoPublication; - TrackPublication? get _firstAudioPublication; Account? _userinfoMetadata; @@ -126,15 +142,14 @@ abstract class _ParticipantWidgetState extends Stat ) : NoContentWidget( userinfo: _userinfoMetadata, + isFixed: widget.isFixed, isSpeaking: widget.participant.isSpeaking, ), if (widget.showStatsLayer) Positioned( top: 30, right: 30, - child: ParticipantStatsWidget( - participant: widget.participant, - ), + child: ParticipantStatsWidget(participant: widget.participant), ), Align( alignment: Alignment.bottomCenter, @@ -143,9 +158,7 @@ abstract class _ParticipantWidgetState extends Stat mainAxisSize: MainAxisSize.min, children: [ ParticipantInfoWidget( - title: widget.participant.name.isNotEmpty - ? widget.participant.name - : widget.participant.identity, + title: widget.participant.name.isNotEmpty ? widget.participant.name : widget.participant.identity, audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true, connectionQuality: widget.participant.connectionQuality, isScreenShare: widget.isScreenShare, @@ -159,10 +172,6 @@ abstract class _ParticipantWidgetState extends Stat } class _LocalParticipantWidgetState extends _ParticipantWidgetState { - @override - LocalTrackPublication? get _videoPublication => - widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull; - @override LocalTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; @@ -172,10 +181,6 @@ class _LocalParticipantWidgetState extends _ParticipantWidgetState { - @override - RemoteTrackPublication? get _videoPublication => - widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull; - @override RemoteTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; diff --git a/lib/widgets/chat/call/participant_menu.dart b/lib/widgets/chat/call/participant_menu.dart index 8805191..107d401 100644 --- a/lib/widgets/chat/call/participant_menu.dart +++ b/lib/widgets/chat/call/participant_menu.dart @@ -21,17 +21,12 @@ class ParticipantMenu extends StatefulWidget { } class _ParticipantMenuState extends State { - @override RemoteTrackPublication? get _videoPublication => widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull; - @override RemoteTrackPublication? get _firstAudioPublication => widget.participant.audioTrackPublications.firstOrNull; - @override - VideoTrack? get _activeVideoTrack => widget.videoTrack; - void tookAction() { if (Navigator.canPop(context)) { Navigator.pop(context); diff --git a/lib/widgets/chat/channel_action.dart b/lib/widgets/chat/channel_action.dart index 5482f57..1f1bc54 100644 --- a/lib/widgets/chat/channel_action.dart +++ b/lib/widgets/chat/channel_action.dart @@ -7,6 +7,7 @@ import 'package:solian/models/channel.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; class ChannelCallAction extends StatefulWidget { final Call? call; @@ -36,9 +37,7 @@ class _ChannelCallActionState extends State { var res = await auth.client!.post(uri); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); @@ -58,9 +57,7 @@ class _ChannelCallActionState extends State { var res = await auth.client!.delete(uri); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } setState(() => _isSubmitting = false); diff --git a/lib/widgets/chat/channel_deletion.dart b/lib/widgets/chat/channel_deletion.dart index 92136ba..05b441b 100644 --- a/lib/widgets/chat/channel_deletion.dart +++ b/lib/widgets/chat/channel_deletion.dart @@ -6,6 +6,7 @@ import 'package:solian/models/channel.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; class ChannelDeletion extends StatefulWidget { final Channel channel; @@ -34,9 +35,7 @@ class _ChannelDeletionState extends State { ); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else if (Navigator.canPop(context)) { Navigator.pop(context, true); } @@ -58,9 +57,7 @@ class _ChannelDeletionState extends State { ); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else if (Navigator.canPop(context)) { Navigator.pop(context, true); } diff --git a/lib/widgets/chat/message_deletion.dart b/lib/widgets/chat/message_deletion.dart index cfcb1b7..8df92ff 100644 --- a/lib/widgets/chat/message_deletion.dart +++ b/lib/widgets/chat/message_deletion.dart @@ -6,6 +6,7 @@ import 'package:solian/models/message.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; class ChatMessageDeletionDialog extends StatefulWidget { final String channel; @@ -34,9 +35,7 @@ class _ChatMessageDeletionDialogState extends State { final res = await auth.client!.delete(uri); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); setState(() => _isSubmitting = false); } else { Navigator.pop(context, true); diff --git a/lib/widgets/chat/message_editor.dart b/lib/widgets/chat/message_editor.dart index af45046..2ed523a 100644 --- a/lib/widgets/chat/message_editor.dart +++ b/lib/widgets/chat/message_editor.dart @@ -8,6 +8,7 @@ import 'package:solian/models/message.dart'; import 'package:solian/models/post.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:badges/badges.dart' as badge; @@ -64,9 +65,7 @@ class _ChatMessageEditorState extends State { var res = await Response.fromStream(await auth.client!.send(req)); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } else { reset(); } diff --git a/lib/widgets/exts.dart b/lib/widgets/exts.dart new file mode 100644 index 0000000..7d60d0d --- /dev/null +++ b/lib/widgets/exts.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +extension SolianCommonExtensions on BuildContext { + Future showErrorDialog(dynamic exception) => showDialog( + context: this, + builder: (ctx) => AlertDialog( + title: Text(AppLocalizations.of(this)!.errorHappened), + content: Text(exception.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text(AppLocalizations.of(this)!.confirmOkay), + ) + ], + ), + ); +} \ No newline at end of file diff --git a/lib/widgets/navigation_drawer.dart b/lib/widgets/navigation_drawer.dart index 88139e8..5d174d6 100644 --- a/lib/widgets/navigation_drawer.dart +++ b/lib/widgets/navigation_drawer.dart @@ -72,7 +72,7 @@ class _SolianNavigationDrawerState extends State { Image.asset("assets/logo.png", width: 26, height: 26), const SizedBox(width: 10), Text( - AppLocalizations.of(context)!.solian, + AppLocalizations.of(context)!.appName, style: const TextStyle(fontWeight: FontWeight.w900), ), ], diff --git a/lib/widgets/posts/attachment_editor.dart b/lib/widgets/posts/attachment_editor.dart index c3b565a..32f4ec5 100755 --- a/lib/widgets/posts/attachment_editor.dart +++ b/lib/widgets/posts/attachment_editor.dart @@ -12,6 +12,7 @@ import 'package:solian/models/post.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/exts.dart'; class AttachmentEditor extends StatefulWidget { final String provider; @@ -67,9 +68,7 @@ class _AttachmentEditorState extends State { try { await uploadAttachment(file, hashcode); } catch (err) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $err")), - ); + context.showErrorDialog(err); } finally { setState(() => _isSubmitting = false); } @@ -94,9 +93,7 @@ class _AttachmentEditorState extends State { try { await uploadAttachment(file, hashcode); } catch (err) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $err")), - ); + context.showErrorDialog(err); } finally { setState(() => _isSubmitting = false); } @@ -133,9 +130,7 @@ class _AttachmentEditorState extends State { widget.onUpdate(_attachments); } else { final err = utf8.decode(await res.stream.toBytes()); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $err")), - ); + context.showErrorDialog(err); } setState(() => _isSubmitting = false); } diff --git a/lib/widgets/posts/item_deletion.dart b/lib/widgets/posts/item_deletion.dart index 085fc76..b24f22f 100644 --- a/lib/widgets/posts/item_deletion.dart +++ b/lib/widgets/posts/item_deletion.dart @@ -6,6 +6,7 @@ import 'package:solian/models/post.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; +import 'package:solian/widgets/exts.dart'; class ItemDeletionDialog extends StatefulWidget { final Post item; @@ -35,9 +36,7 @@ class _ItemDeletionDialogState extends State { final res = await auth.client!.delete(uri); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); setState(() => _isSubmitting = false); } else { Navigator.pop(context, true); diff --git a/lib/widgets/posts/reaction_action.dart b/lib/widgets/posts/reaction_action.dart index a79e8b9..65015ae 100644 --- a/lib/widgets/posts/reaction_action.dart +++ b/lib/widgets/posts/reaction_action.dart @@ -7,6 +7,7 @@ import 'package:solian/models/reaction.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/exts.dart'; Future doReact( String dataset, @@ -51,9 +52,7 @@ Future doReact( ); } else { final message = utf8.decode(res.bodyBytes); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Something went wrong... $message")), - ); + context.showErrorDialog(message); } if (Navigator.canPop(context)) { diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 8ba8012..44a49c9 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -3,7 +3,7 @@ ITSAppUsesNonExemptEncryption - + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable