🐛 Bug fixes

This commit is contained in:
LittleSheep 2024-04-29 20:22:06 +08:00
parent e665b44507
commit 5922d325e5
29 changed files with 249 additions and 380 deletions

View File

@ -5,6 +5,8 @@ PODS:
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0): - flutter_secure_storage (6.0.0):
- Flutter - Flutter
- flutter_webrtc (0.9.36): - flutter_webrtc (0.9.36):
@ -44,6 +46,7 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
@ -71,6 +74,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_secure_storage: flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_webrtc: flutter_webrtc:
@ -106,6 +111,7 @@ SPEC CHECKSUMS:
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
flutter_webrtc: 9bc044b0b5bcaabd0fb7d52c90421fb540f8c35e flutter_webrtc: 9bc044b0b5bcaabd0fb7d52c90421fb540f8c35e
image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18 image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18

View File

@ -1,5 +1,5 @@
{ {
"solian": "Solian", "appName": "Solar Network",
"explore": "Explore", "explore": "Explore",
"chat": "Chat", "chat": "Chat",
"account": "Account", "account": "Account",
@ -28,6 +28,7 @@
"report": "Report", "report": "Report",
"reply": "Reply", "reply": "Reply",
"settings": "Settings", "settings": "Settings",
"errorHappened": "An Error Occurred",
"notification": "Notification", "notification": "Notification",
"notifyDone": "You're done!", "notifyDone": "You're done!",
"notifyDoneCaption": "There are no notifications unread for you.", "notifyDoneCaption": "There are no notifications unread for you.",

View File

@ -1,5 +1,5 @@
{ {
"solian": "索链", "appName": "Solar",
"explore": "探索", "explore": "探索",
"chat": "聊天", "chat": "聊天",
"account": "账号", "account": "账号",
@ -28,6 +28,7 @@
"report": "举报", "report": "举报",
"reply": "回复", "reply": "回复",
"settings": "设置", "settings": "设置",
"errorHappened": "发生了错误",
"notification": "通知", "notification": "通知",
"notifyDone": "所有通知已读!", "notifyDone": "所有通知已读!",
"notifyDoneCaption": "这里没有什么东西可以给你看的了~", "notifyDoneCaption": "这里没有什么东西可以给你看的了~",

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.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:permission_handler/permission_handler.dart';
import 'package:solian/models/pagination.dart'; import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
@ -24,17 +25,25 @@ class NotifyProvider extends ChangeNotifier {
} }
void initNotify() { void initNotify() {
const androidSettings = AndroidInitializationSettings('app_icon');
const darwinSettings = DarwinInitializationSettings(
notificationCategories: [
DarwinNotificationCategory("general"),
],
);
const linuxSettings = LinuxInitializationSettings(defaultActionName: 'Open notification');
const InitializationSettings initializationSettings = InitializationSettings( const InitializationSettings initializationSettings = InitializationSettings(
android: AndroidInitializationSettings('app_icon'), android: androidSettings,
iOS: DarwinInitializationSettings(), iOS: darwinSettings,
macOS: DarwinInitializationSettings(), macOS: darwinSettings,
linux: LinuxInitializationSettings(defaultActionName: 'Open notification'), linux: linuxSettings,
); );
localNotify.initialize(initializationSettings); localNotify.initialize(initializationSettings);
} }
Future<void> requestPermissions() async { Future<void> requestPermissions() async {
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return;
await Permission.notification.request(); await Permission.notification.request();
} }
@ -78,20 +87,30 @@ class NotifyProvider extends ChangeNotifier {
} }
void notifyMessage(String title, String body) { 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( localNotify.show(
math.max(1, math.Random().nextInt(100000000)), math.max(1, math.Random().nextInt(100000000)),
title, title,
body, body,
const NotificationDetails( const NotificationDetails(
android: AndroidNotificationDetails( android: androidSettings,
'general', iOS: darwinSettings,
'General', macOS: darwinSettings,
importance: Importance.high, linux: linuxSettings,
priority: Priority.high,
),
iOS: DarwinNotificationDetails(),
macOS: DarwinNotificationDetails(),
linux: LinuxNotificationDetails(),
), ),
); );
} }

View File

@ -7,6 +7,7 @@ import 'package:solian/models/friendship.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/account/avatar.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/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -40,9 +41,7 @@ class _FriendScreenState extends State<FriendScreen> {
}); });
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
} }
@ -65,9 +64,7 @@ class _FriendScreenState extends State<FriendScreen> {
await fetchFriendships(); await fetchFriendships();
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
@ -97,9 +94,7 @@ class _FriendScreenState extends State<FriendScreen> {
await fetchFriendships(); await fetchFriendships();
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);

View File

@ -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/exts.dart';
import 'package:solian/widgets/chat/call/participant.dart'; import 'package:solian/widgets/chat/call/participant.dart';
import 'package:solian/widgets/chat/call/participant_menu.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:solian/widgets/indent_wrapper.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -81,9 +82,7 @@ class _ChatCallState extends State<ChatCall> {
return (_token!, _endpoint!); return (_token!, _endpoint!);
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
throw Exception(message); throw Exception(message);
} }
} }
@ -138,10 +137,7 @@ class _ChatCallState extends State<ChatCall> {
setupRoom(); setupRoom();
} catch (e) { } catch (e) {
final message = e.toString(); context.showErrorDialog(e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Something went wrong... $message")),
);
} finally { } finally {
notify.close(); notify.close();
} }
@ -182,9 +178,6 @@ class _ChatCallState extends State<ChatCall> {
if (router.canPop()) router.pop(); if (router.canPop()) router.pop();
}) })
..on<ParticipantEvent>((event) => sortParticipants()) ..on<ParticipantEvent>((event) => sortParticipants())
..on<RoomRecordingStatusChanged>((event) {
context.showRecordingStatusChangedDialog(event.activeRecording);
})
..on<LocalTrackPublishedEvent>((_) => sortParticipants()) ..on<LocalTrackPublishedEvent>((_) => sortParticipants())
..on<LocalTrackUnpublishedEvent>((_) => sortParticipants()) ..on<LocalTrackUnpublishedEvent>((_) => sortParticipants())
..on<TrackSubscribedEvent>((_) => sortParticipants()) ..on<TrackSubscribedEvent>((_) => sortParticipants())
@ -203,30 +196,22 @@ class _ChatCallState extends State<ChatCall> {
} }
void sortParticipants() { void sortParticipants() {
List<ParticipantTrack> screenTracks = []; Map<String, ParticipantTrack> mediaTracks = {};
Map<String, ParticipantTrack> userMediaTracks = {};
for (var participant in _callRoom.remoteParticipants.values) { for (var participant in _callRoom.remoteParticipants.values) {
userMediaTracks[participant.sid] = ParticipantTrack( mediaTracks[participant.sid] = ParticipantTrack(
participant: participant, participant: participant,
videoTrack: null, videoTrack: null,
isScreenShare: false, isScreenShare: false,
); );
for (var t in participant.videoTrackPublications) { for (var t in participant.videoTrackPublications) {
if (t.isScreenShare) { mediaTracks[participant.sid]?.videoTrack = t.track;
screenTracks.add(ParticipantTrack( mediaTracks[participant.sid]?.isScreenShare = t.isScreenShare;
participant: participant,
videoTrack: t.track as VideoTrack,
isScreenShare: true,
));
} else {
userMediaTracks[participant.sid]?.videoTrack = t.track;
}
} }
} }
final userMediaTrackList = userMediaTracks.values.toList(); final mediaTrackList = mediaTracks.values.toList();
userMediaTrackList.sort((a, b) { mediaTrackList.sort((a, b) {
// Loudest people first // Loudest people first
if (a.participant.isSpeaking && b.participant.isSpeaking) { if (a.participant.isSpeaking && b.participant.isSpeaking) {
if (a.participant.audioLevel > b.participant.audioLevel) { if (a.participant.audioLevel > b.participant.audioLevel) {
@ -262,22 +247,20 @@ class _ChatCallState extends State<ChatCall> {
final localParticipantTracks = _callRoom.localParticipant?.videoTrackPublications; final localParticipantTracks = _callRoom.localParticipant?.videoTrackPublications;
if (localParticipantTracks != null) { if (localParticipantTracks != null) {
for (var t in localParticipantTracks) { for (var t in localParticipantTracks) {
if (t.isScreenShare) { localTrack.videoTrack = t.track;
screenTracks.add(ParticipantTrack( localTrack.isScreenShare = t.isScreenShare;
participant: _callRoom.localParticipant!,
videoTrack: t.track as VideoTrack,
isScreenShare: true,
));
} else {
localTrack.videoTrack = t.track;
}
} }
} }
} }
setState(() { setState(() {
_participantTracks = [...screenTracks, localTrack, ...userMediaTrackList]; _participantTracks = [localTrack, ...mediaTrackList];
_focusParticipant ??= _participantTracks.first; 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? width;
final double? height; final double? height;
final Color? color; final Color? color;
final bool? isFixed;
final ParticipantTrack participant; final ParticipantTrack participant;
final Function() onTap; final Function() onTap;
@ -474,6 +458,7 @@ class InteractiveParticipantWidget extends StatelessWidget {
this.width, this.width,
this.height, this.height,
this.color, this.color,
this.isFixed = false,
required this.participant, required this.participant,
required this.onTap, required this.onTap,
}); });
@ -485,7 +470,7 @@ class InteractiveParticipantWidget extends StatelessWidget {
width: width, width: width,
height: height, height: height,
color: color, color: color,
child: ParticipantWidget.widgetFor(participant), child: ParticipantWidget.widgetFor(participant, isFixed: true),
), ),
onTap: () => onTap(), onTap: () => onTap(),
onLongPress: () { onLongPress: () {

View File

@ -8,6 +8,7 @@ import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.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/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -52,9 +53,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
var res = await Response.fromStream(await auth.client!.send(req)); var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else { } else {
if (router.canPop()) { if (router.canPop()) {
router.pop(true); router.pop(true);

View File

@ -9,6 +9,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/account/avatar.dart'; import 'package:solian/widgets/account/avatar.dart';
import 'package:solian/widgets/account/friend_picker.dart'; import 'package:solian/widgets/account/friend_picker.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -45,9 +46,7 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
}); });
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
} }
@ -75,9 +74,7 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
await fetchMemberships(); await fetchMemberships();
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
@ -107,9 +104,7 @@ class _ChatMemberScreenState extends State<ChatMemberScreen> {
await fetchMemberships(); await fetchMemberships();
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);

View File

@ -16,6 +16,7 @@ import 'package:solian/widgets/chat/maintainer.dart';
import 'package:solian/widgets/chat/message.dart'; import 'package:solian/widgets/chat/message.dart';
import 'package:solian/widgets/chat/message_action.dart'; import 'package:solian/widgets/chat/message_action.dart';
import 'package:solian/widgets/chat/message_editor.dart'; import 'package:solian/widgets/chat/message_editor.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -46,9 +47,7 @@ class _ChatScreenState extends State<ChatScreen> {
return _channelMeta!; return _channelMeta!;
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
throw Exception(message); throw Exception(message);
} }
} }
@ -62,9 +61,7 @@ class _ChatScreenState extends State<ChatScreen> {
return _ongoingCall; return _ongoingCall;
} else if (res.statusCode != 404) { } else if (res.statusCode != 404) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
throw Exception(message); throw Exception(message);
} else { } else {
return null; return null;

View File

@ -7,6 +7,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/chat/chat_new.dart'; import 'package:solian/widgets/chat/chat_new.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/indent_wrapper.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/notification_notifier.dart'; import 'package:solian/widgets/notification_notifier.dart';
@ -36,9 +37,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
}); });
} else { } else {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
} }

View File

@ -21,6 +21,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
final nty = context.watch<NotifyProvider>(); final nty = context.watch<NotifyProvider>();
WidgetsBinding.instance.addPostFrameCallback((_) {
nty.allRead();
});
return IndentWrapper( return IndentWrapper(
noSafeArea: true, noSafeArea: true,
hideDrawer: true, hideDrawer: true,
@ -48,9 +52,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
return NotificationItem( return NotificationItem(
index: index, index: index,
item: element, item: element,
onDismiss: () => setState(() { onDismiss: () => nty.clearAt(index),
nty.clearAt(index);
}),
); );
}, },
), ),
@ -69,14 +71,6 @@ class _NotificationScreenState extends State<NotificationScreen> {
), ),
); );
} }
@override
void dispose() {
final nty = context.read<NotifyProvider>();
nty.allRead();
nty.clearRealtime();
super.dispose();
}
} }
class NotificationItem extends StatelessWidget { class NotificationItem extends StatelessWidget {
@ -141,7 +135,7 @@ class NotificationItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dismissible( return Dismissible(
key: Key('n$index'), key: Key((DateTime.now().millisecondsSinceEpoch << 10).toString()),
onDismissed: (direction) { onDismissed: (direction) {
markAsRead(item, context).then((value) { markAsRead(item, context).then((value) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(

View File

@ -10,6 +10,7 @@ import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/account/avatar.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/indent_wrapper.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
@ -75,9 +76,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
var res = await Response.fromStream(await auth.client!.send(req)); var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else { } else {
if (router.canPop()) { if (router.canPop()) {
router.pop(true); router.pop(true);

View File

@ -10,6 +10,7 @@ import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/account/avatar.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/indent_wrapper.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
@ -65,9 +66,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
var res = await Response.fromStream(await auth.client!.send(req)); var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else { } else {
if (router.canPop()) { if (router.canPop()) {
router.pop(true); router.pop(true);

View File

@ -5,6 +5,7 @@ import 'package:http/http.dart' as http;
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/utils/service_url.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/indent_wrapper.dart';
import 'package:solian/widgets/posts/comment_list.dart'; import 'package:solian/widgets/posts/comment_list.dart';
import 'package:solian/widgets/posts/item.dart'; import 'package:solian/widgets/posts/item.dart';
@ -30,9 +31,7 @@ class _PostScreenState extends State<PostScreen> {
final res = await _client.get(uri); final res = await _client.get(uri);
if (res.statusCode != 200) { if (res.statusCode != 200) {
final err = utf8.decode(res.bodyBytes); final err = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(err);
SnackBar(content: Text("Something went wrong... $err")),
);
return null; return null;
} else { } else {
return Post.fromJson(jsonDecode(utf8.decode(res.bodyBytes))); return Post.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));

View File

@ -6,6 +6,7 @@ import 'package:flutter_background/flutter_background.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_client/livekit_client.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/exts.dart';
class ControlsWidget extends StatefulWidget { class ControlsWidget extends StatefulWidget {
final Room room; final Room room;
@ -134,9 +135,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
await participant.publishVideoTrack(track); await participant.publishVideoTrack(track);
} catch (e) { } catch (e) {
final message = e.toString(); final message = e.toString();
ScaffoldMessenger.of(context).showSnackBar(SnackBar( context.showErrorDialog(message);
content: Text('Something went wrong... $message'),
));
} }
return; return;
} }

View File

@ -1,206 +1,73 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
extension SolianCallExt on BuildContext { extension SolianCallExt on BuildContext {
Future<bool?> showPublishDialog() => showDialog<bool>(
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<bool?> showPlayAudioManuallyDialog() => showDialog<bool>( Future<bool?> showPlayAudioManuallyDialog() => showDialog<bool>(
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<bool?> showUnPublishDialog() => showDialog<bool>(
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<void> showErrorDialog(dynamic exception) => showDialog<void>(
context: this,
builder: (ctx) => AlertDialog(
title: const Text('Error'),
content: Text(exception.toString()),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('OK'),
)
],
),
);
Future<bool?> showDisconnectDialog() => showDialog<bool>(
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<bool?> showReconnectDialog() => showDialog<bool>(
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<void> showReconnectSuccessDialog() => showDialog<void>(
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<bool?> showSendDataDialog() => showDialog<bool>(
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<bool?> showDataReceivedDialog(String data) => showDialog<bool>(
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<bool?> showRecordingStatusChangedDialog(bool isActiveRecording) =>
showDialog<bool>(
context: this, context: this,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('Room recording reminder'), title: const Text('Play Audio'),
content: Text(isActiveRecording content: const Text(
? 'Room recording is active.' 'You need to manually activate audio PlayBack for iOS Safari!',
: 'Room recording is stoped.'), ),
actions: [ actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Ignore'),
),
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx, true), onPressed: () => Navigator.pop(ctx, true),
child: const Text('OK'), child: const Text('Play Audio'),
), ),
], ],
), ),
); );
Future<bool?> showSubscribePermissionDialog() => showDialog<bool>( Future<bool?> showDisconnectDialog() => showDialog<bool>(
context: this, context: this,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: const Text('Allow subscription'), title: const Text('Disconnect'),
content: const Text( content: const Text('Are you sure to disconnect?'),
'Allow all participants to subscribe tracks published by local participant?'), actions: [
actions: [ TextButton(
TextButton( onPressed: () => Navigator.pop(ctx, false),
onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel'),
child: const Text('NO'), ),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Disconnect'),
),
],
), ),
TextButton( );
onPressed: () => Navigator.pop(ctx, true),
child: const Text('YES'),
),
],
),
);
}
enum SimulateScenarioResult { Future<bool?> showReconnectDialog() => showDialog<bool>(
signalReconnect, context: this,
fullReconnect, builder: (ctx) => AlertDialog(
speakerUpdate, title: const Text('Reconnect'),
nodeFailure, content: const Text('This will force a reconnection'),
migration, actions: [
serverLeave, TextButton(
switchCandidate, onPressed: () => Navigator.pop(ctx, false),
e2eeKeyRatchet, child: const Text('Cancel'),
participantName, ),
participantMetadata, TextButton(
clear, onPressed: () => Navigator.pop(ctx, true),
child: const Text('Reconnect'),
),
],
),
);
Future<void> showReconnectSuccessDialog() => showDialog<void>(
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'),
),
],
),
);
} }

View File

@ -7,8 +7,14 @@ import 'dart:math' as math;
class NoContentWidget extends StatefulWidget { class NoContentWidget extends StatefulWidget {
final Account? userinfo; final Account? userinfo;
final bool isSpeaking; 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 @override
State<NoContentWidget> createState() => _NoContentWidgetState(); State<NoContentWidget> createState() => _NoContentWidgetState();
@ -35,35 +41,41 @@ class _NoContentWidgetState extends State<NoContentWidget> with SingleTickerProv
@override @override
Widget build(BuildContext context) { 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( return Container(
alignment: Alignment.center, alignment: Alignment.center,
child: Center( child: Center(
child: Animate( child: Animate(
autoPlay: false, autoPlay: false,
controller: _animationController, controller: _animationController,
effects: [ effects: [
CustomEffect( CustomEffect(
begin: widget.isSpeaking ? 2 : 0, begin: widget.isSpeaking ? 2 : 0,
end: 8, end: 8,
curve: Curves.easeInOut, curve: Curves.easeInOut,
duration: 1250.ms, duration: 1250.ms,
builder: (context, value, child) => Container( builder: (context, value, child) => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(radius + 8)), borderRadius: BorderRadius.all(Radius.circular(radius + 8)),
border: value > 0 ? Border.all(color: Colors.green, width: value) : null, border: value > 0 ? Border.all(color: Colors.green, width: value) : null,
),
child: child,
), ),
) child: child,
], ),
child: AccountAvatar( )
source: widget.userinfo!.avatar, ],
backgroundColor: Colors.transparent, child: AccountAvatar(
radius: radius, source: widget.userinfo!.avatar,
direct: true, backgroundColor: Colors.transparent,
)), radius: radius,
direct: true,
),
),
), ),
); );
} }

View File

@ -10,13 +10,24 @@ import 'package:solian/widgets/chat/call/participant_info.dart';
import 'package:solian/widgets/chat/call/participant_stats.dart'; import 'package:solian/widgets/chat/call/participant_stats.dart';
abstract class ParticipantWidget extends StatefulWidget { 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) { if (participantTrack.participant is LocalParticipant) {
return LocalParticipantWidget(participantTrack.participant as LocalParticipant, participantTrack.videoTrack, return LocalParticipantWidget(
participantTrack.isScreenShare, showStatsLayer); participantTrack.participant as LocalParticipant,
participantTrack.videoTrack,
isFixed,
participantTrack.isScreenShare,
showStatsLayer,
);
} else if (participantTrack.participant is RemoteParticipant) { } else if (participantTrack.participant is RemoteParticipant) {
return RemoteParticipantWidget(participantTrack.participant as RemoteParticipant, participantTrack.videoTrack, return RemoteParticipantWidget(
participantTrack.isScreenShare, showStatsLayer); participantTrack.participant as RemoteParticipant,
participantTrack.videoTrack,
isFixed,
participantTrack.isScreenShare,
showStatsLayer,
);
} }
throw UnimplementedError('Unknown participant type'); throw UnimplementedError('Unknown participant type');
} }
@ -24,6 +35,7 @@ abstract class ParticipantWidget extends StatefulWidget {
abstract final Participant participant; abstract final Participant participant;
abstract final VideoTrack? videoTrack; abstract final VideoTrack? videoTrack;
abstract final bool isScreenShare; abstract final bool isScreenShare;
abstract final bool isFixed;
abstract final bool showStatsLayer; abstract final bool showStatsLayer;
final VideoQuality quality; final VideoQuality quality;
@ -39,6 +51,8 @@ class LocalParticipantWidget extends ParticipantWidget {
@override @override
final VideoTrack? videoTrack; final VideoTrack? videoTrack;
@override @override
final bool isFixed;
@override
final bool isScreenShare; final bool isScreenShare;
@override @override
final bool showStatsLayer; final bool showStatsLayer;
@ -46,6 +60,7 @@ class LocalParticipantWidget extends ParticipantWidget {
const LocalParticipantWidget( const LocalParticipantWidget(
this.participant, this.participant,
this.videoTrack, this.videoTrack,
this.isFixed,
this.isScreenShare, this.isScreenShare,
this.showStatsLayer, { this.showStatsLayer, {
super.key, super.key,
@ -61,6 +76,8 @@ class RemoteParticipantWidget extends ParticipantWidget {
@override @override
final VideoTrack? videoTrack; final VideoTrack? videoTrack;
@override @override
final bool isFixed;
@override
final bool isScreenShare; final bool isScreenShare;
@override @override
final bool showStatsLayer; final bool showStatsLayer;
@ -68,6 +85,7 @@ class RemoteParticipantWidget extends ParticipantWidget {
const RemoteParticipantWidget( const RemoteParticipantWidget(
this.participant, this.participant,
this.videoTrack, this.videoTrack,
this.isFixed,
this.isScreenShare, this.isScreenShare,
this.showStatsLayer, { this.showStatsLayer, {
super.key, super.key,
@ -80,8 +98,6 @@ class RemoteParticipantWidget extends ParticipantWidget {
abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends State<T> { abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends State<T> {
VideoTrack? get _activeVideoTrack; VideoTrack? get _activeVideoTrack;
TrackPublication? get _videoPublication;
TrackPublication? get _firstAudioPublication; TrackPublication? get _firstAudioPublication;
Account? _userinfoMetadata; Account? _userinfoMetadata;
@ -126,15 +142,14 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
) )
: NoContentWidget( : NoContentWidget(
userinfo: _userinfoMetadata, userinfo: _userinfoMetadata,
isFixed: widget.isFixed,
isSpeaking: widget.participant.isSpeaking, isSpeaking: widget.participant.isSpeaking,
), ),
if (widget.showStatsLayer) if (widget.showStatsLayer)
Positioned( Positioned(
top: 30, top: 30,
right: 30, right: 30,
child: ParticipantStatsWidget( child: ParticipantStatsWidget(participant: widget.participant),
participant: widget.participant,
),
), ),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
@ -143,9 +158,7 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ParticipantInfoWidget( ParticipantInfoWidget(
title: widget.participant.name.isNotEmpty title: widget.participant.name.isNotEmpty ? widget.participant.name : widget.participant.identity,
? widget.participant.name
: widget.participant.identity,
audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true, audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true,
connectionQuality: widget.participant.connectionQuality, connectionQuality: widget.participant.connectionQuality,
isScreenShare: widget.isScreenShare, isScreenShare: widget.isScreenShare,
@ -159,10 +172,6 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
} }
class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticipantWidget> { class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticipantWidget> {
@override
LocalTrackPublication<LocalVideoTrack>? get _videoPublication =>
widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull;
@override @override
LocalTrackPublication<LocalAudioTrack>? get _firstAudioPublication => LocalTrackPublication<LocalAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull; widget.participant.audioTrackPublications.firstOrNull;
@ -172,10 +181,6 @@ class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticip
} }
class _RemoteParticipantWidgetState extends _ParticipantWidgetState<RemoteParticipantWidget> { class _RemoteParticipantWidgetState extends _ParticipantWidgetState<RemoteParticipantWidget> {
@override
RemoteTrackPublication<RemoteVideoTrack>? get _videoPublication =>
widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull;
@override @override
RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication => RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull; widget.participant.audioTrackPublications.firstOrNull;

View File

@ -21,17 +21,12 @@ class ParticipantMenu extends StatefulWidget {
} }
class _ParticipantMenuState extends State<ParticipantMenu> { class _ParticipantMenuState extends State<ParticipantMenu> {
@override
RemoteTrackPublication<RemoteVideoTrack>? get _videoPublication => RemoteTrackPublication<RemoteVideoTrack>? get _videoPublication =>
widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull; widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull;
@override
RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication => RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull; widget.participant.audioTrackPublications.firstOrNull;
@override
VideoTrack? get _activeVideoTrack => widget.videoTrack;
void tookAction() { void tookAction() {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.pop(context); Navigator.pop(context);

View File

@ -7,6 +7,7 @@ import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.dart';
class ChannelCallAction extends StatefulWidget { class ChannelCallAction extends StatefulWidget {
final Call? call; final Call? call;
@ -36,9 +37,7 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
var res = await auth.client!.post(uri); var res = await auth.client!.post(uri);
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
@ -58,9 +57,7 @@ class _ChannelCallActionState extends State<ChannelCallAction> {
var res = await auth.client!.delete(uri); var res = await auth.client!.delete(uri);
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);

View File

@ -6,6 +6,7 @@ import 'package:solian/models/channel.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.dart';
class ChannelDeletion extends StatefulWidget { class ChannelDeletion extends StatefulWidget {
final Channel channel; final Channel channel;
@ -34,9 +35,7 @@ class _ChannelDeletionState extends State<ChannelDeletion> {
); );
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else if (Navigator.canPop(context)) { } else if (Navigator.canPop(context)) {
Navigator.pop(context, true); Navigator.pop(context, true);
} }
@ -58,9 +57,7 @@ class _ChannelDeletionState extends State<ChannelDeletion> {
); );
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else if (Navigator.canPop(context)) { } else if (Navigator.canPop(context)) {
Navigator.pop(context, true); Navigator.pop(context, true);
} }

View File

@ -6,6 +6,7 @@ import 'package:solian/models/message.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.dart';
class ChatMessageDeletionDialog extends StatefulWidget { class ChatMessageDeletionDialog extends StatefulWidget {
final String channel; final String channel;
@ -34,9 +35,7 @@ class _ChatMessageDeletionDialogState extends State<ChatMessageDeletionDialog> {
final res = await auth.client!.delete(uri); final res = await auth.client!.delete(uri);
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} else { } else {
Navigator.pop(context, true); Navigator.pop(context, true);

View File

@ -8,6 +8,7 @@ import 'package:solian/models/message.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
import 'package:badges/badges.dart' as badge; import 'package:badges/badges.dart' as badge;
@ -64,9 +65,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
var res = await Response.fromStream(await auth.client!.send(req)); var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} else { } else {
reset(); reset();
} }

18
lib/widgets/exts.dart Normal file
View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
extension SolianCommonExtensions on BuildContext {
Future<void> showErrorDialog(dynamic exception) => showDialog<void>(
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),
)
],
),
);
}

View File

@ -72,7 +72,7 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
Image.asset("assets/logo.png", width: 26, height: 26), Image.asset("assets/logo.png", width: 26, height: 26),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
AppLocalizations.of(context)!.solian, AppLocalizations.of(context)!.appName,
style: const TextStyle(fontWeight: FontWeight.w900), style: const TextStyle(fontWeight: FontWeight.w900),
), ),
], ],

View File

@ -12,6 +12,7 @@ import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/exts.dart';
class AttachmentEditor extends StatefulWidget { class AttachmentEditor extends StatefulWidget {
final String provider; final String provider;
@ -67,9 +68,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
try { try {
await uploadAttachment(file, hashcode); await uploadAttachment(file, hashcode);
} catch (err) { } catch (err) {
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(err);
SnackBar(content: Text("Something went wrong... $err")),
);
} finally { } finally {
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} }
@ -94,9 +93,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
try { try {
await uploadAttachment(file, hashcode); await uploadAttachment(file, hashcode);
} catch (err) { } catch (err) {
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(err);
SnackBar(content: Text("Something went wrong... $err")),
);
} finally { } finally {
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} }
@ -133,9 +130,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
widget.onUpdate(_attachments); widget.onUpdate(_attachments);
} else { } else {
final err = utf8.decode(await res.stream.toBytes()); final err = utf8.decode(await res.stream.toBytes());
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(err);
SnackBar(content: Text("Something went wrong... $err")),
);
} }
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} }

View File

@ -6,6 +6,7 @@ import 'package:solian/models/post.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:solian/widgets/exts.dart';
class ItemDeletionDialog extends StatefulWidget { class ItemDeletionDialog extends StatefulWidget {
final Post item; final Post item;
@ -35,9 +36,7 @@ class _ItemDeletionDialogState extends State<ItemDeletionDialog> {
final res = await auth.client!.delete(uri); final res = await auth.client!.delete(uri);
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
setState(() => _isSubmitting = false); setState(() => _isSubmitting = false);
} else { } else {
Navigator.pop(context, true); Navigator.pop(context, true);

View File

@ -7,6 +7,7 @@ import 'package:solian/models/reaction.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/utils/service_url.dart'; import 'package:solian/utils/service_url.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:solian/widgets/exts.dart';
Future<void> doReact( Future<void> doReact(
String dataset, String dataset,
@ -51,9 +52,7 @@ Future<void> doReact(
); );
} else { } else {
final message = utf8.decode(res.bodyBytes); final message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( context.showErrorDialog(message);
SnackBar(content: Text("Something went wrong... $message")),
);
} }
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {

View File

@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<true/> <false/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>