🐛 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):
- 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

View File

@ -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.",

View File

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

View File

@ -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<void> 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,
),
);
}

View File

@ -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<FriendScreen> {
});
} 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<FriendScreen> {
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<FriendScreen> {
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);

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/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<ChatCall> {
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<ChatCall> {
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<ChatCall> {
if (router.canPop()) router.pop();
})
..on<ParticipantEvent>((event) => sortParticipants())
..on<RoomRecordingStatusChanged>((event) {
context.showRecordingStatusChangedDialog(event.activeRecording);
})
..on<LocalTrackPublishedEvent>((_) => sortParticipants())
..on<LocalTrackUnpublishedEvent>((_) => sortParticipants())
..on<TrackSubscribedEvent>((_) => sortParticipants())
@ -203,30 +196,22 @@ class _ChatCallState extends State<ChatCall> {
}
void sortParticipants() {
List<ParticipantTrack> screenTracks = [];
Map<String, ParticipantTrack> userMediaTracks = {};
Map<String, ParticipantTrack> 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<ChatCall> {
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: () {

View File

@ -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<ChannelEditorScreen> {
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);

View File

@ -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<ChatMemberScreen> {
});
} 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<ChatMemberScreen> {
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<ChatMemberScreen> {
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);

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_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<ChatScreen> {
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<ChatScreen> {
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;

View File

@ -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<ChatIndexScreen> {
});
} else {
var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Something went wrong... $message")),
);
context.showErrorDialog(message);
}
}

View File

@ -21,6 +21,10 @@ class _NotificationScreenState extends State<NotificationScreen> {
final auth = context.read<AuthProvider>();
final nty = context.watch<NotifyProvider>();
WidgetsBinding.instance.addPostFrameCallback((_) {
nty.allRead();
});
return IndentWrapper(
noSafeArea: true,
hideDrawer: true,
@ -48,9 +52,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
return NotificationItem(
index: index,
item: element,
onDismiss: () => setState(() {
nty.clearAt(index);
}),
onDismiss: () => 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 {
@ -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(

View File

@ -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<CommentEditorScreen> {
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);

View File

@ -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<MomentEditorScreen> {
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);

View File

@ -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<PostScreen> {
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)));

View File

@ -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<ControlsWidget> {
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;
}

View File

@ -1,206 +1,73 @@
import 'package:flutter/material.dart';
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>(
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,
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<bool?> showSubscribePermissionDialog() => showDialog<bool>(
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<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'),
),
],
),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('YES'),
),
],
),
);
}
);
enum SimulateScenarioResult {
signalReconnect,
fullReconnect,
speakerUpdate,
nodeFailure,
migration,
serverLeave,
switchCandidate,
e2eeKeyRatchet,
participantName,
participantMetadata,
clear,
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'),
),
],
),
);
}

View File

@ -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<NoContentWidget> createState() => _NoContentWidgetState();
@ -35,35 +41,41 @@ class _NoContentWidgetState extends State<NoContentWidget> 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,
),
),
),
);
}

View File

@ -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<T extends ParticipantWidget> extends State<T> {
VideoTrack? get _activeVideoTrack;
TrackPublication? get _videoPublication;
TrackPublication? get _firstAudioPublication;
Account? _userinfoMetadata;
@ -126,15 +142,14 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> 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<T extends ParticipantWidget> 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<T extends ParticipantWidget> extends Stat
}
class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticipantWidget> {
@override
LocalTrackPublication<LocalVideoTrack>? get _videoPublication =>
widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull;
@override
LocalTrackPublication<LocalAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull;
@ -172,10 +181,6 @@ class _LocalParticipantWidgetState extends _ParticipantWidgetState<LocalParticip
}
class _RemoteParticipantWidgetState extends _ParticipantWidgetState<RemoteParticipantWidget> {
@override
RemoteTrackPublication<RemoteVideoTrack>? get _videoPublication =>
widget.participant.videoTrackPublications.where((element) => element.sid == widget.videoTrack?.sid).firstOrNull;
@override
RemoteTrackPublication<RemoteAudioTrack>? get _firstAudioPublication =>
widget.participant.audioTrackPublications.firstOrNull;

View File

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

View File

@ -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<ChannelDeletion> {
);
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<ChannelDeletion> {
);
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);
}

View File

@ -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<ChatMessageDeletionDialog> {
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);

View File

@ -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<ChatMessageEditor> {
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();
}

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),
const SizedBox(width: 10),
Text(
AppLocalizations.of(context)!.solian,
AppLocalizations.of(context)!.appName,
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/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<AttachmentEditor> {
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<AttachmentEditor> {
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<AttachmentEditor> {
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);
}

View File

@ -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<ItemDeletionDialog> {
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);

View File

@ -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<void> doReact(
String dataset,
@ -51,9 +52,7 @@ Future<void> 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)) {

View File

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