🐛 Bug fixes and optimization

This commit is contained in:
2024-06-08 21:35:50 +08:00
parent e88a0ddb22
commit 6acbd1ee9e
34 changed files with 481 additions and 392 deletions

View File

@ -9,22 +9,12 @@ extension SolianExtenions on BuildContext {
}
Future<void> showErrorDialog(dynamic exception) {
String formatMessage(dynamic exception) {
final message = exception.toString();
if (message.trim().isEmpty) return '';
return message
.split(' ')
.map((element) =>
'${element[0].toUpperCase()}${element.substring(1).toLowerCase()}')
.join(' ');
}
return showDialog<void>(
useRootNavigator: true,
context: this,
builder: (ctx) => AlertDialog(
title: Text('errorHappened'.tr),
content: Text(formatMessage(exception)),
content: Text(exception.toString().capitalize!),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),

View File

@ -6,8 +6,6 @@ class Notification {
String subject;
String content;
List<Link>? links;
bool isImportant;
bool isRealtime;
DateTime? readAt;
int? senderId;
int recipientId;
@ -20,8 +18,6 @@ class Notification {
required this.subject,
required this.content,
required this.links,
required this.isImportant,
required this.isRealtime,
required this.readAt,
required this.senderId,
required this.recipientId,
@ -41,8 +37,6 @@ class Notification {
links: json['links'] != null
? List<Link>.from(json['links'].map((x) => Link.fromJson(x)))
: List.empty(),
isImportant: json['is_important'],
isRealtime: json['is_realtime'],
readAt: json['read_at'],
senderId: json['sender_id'],
recipientId: json['recipient_id'],
@ -58,8 +52,6 @@ class Notification {
'links': links != null
? List<dynamic>.from(links!.map((x) => x.toJson()))
: List.empty(),
'is_important': isImportant,
'is_realtime': isRealtime,
'read_at': readAt,
'sender_id': senderId,
'recipient_id': recipientId,

View File

@ -1,11 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:math' as math;
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:platform_device_id/platform_device_id.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/packet.dart';
import 'package:solian/models/pagination.dart';
@ -30,7 +31,14 @@ class AccountProvider extends GetxController {
@override
onInit() {
Permission.notification.request().then((status) {
FirebaseMessaging.instance
.requestPermission(
alert: true,
announcement: true,
carPlay: true,
badge: true,
sound: true)
.then((status) {
notifyInitialization();
notifyPrefetch();
});
@ -94,7 +102,7 @@ class AccountProvider extends GetxController {
},
onDone: () {
isConnected.value = false;
Future.delayed(const Duration(seconds: 3), () => connect());
Future.delayed(const Duration(seconds: 1), () => connect());
},
onError: (err) {
isConnected.value = false;
@ -175,7 +183,11 @@ class AccountProvider extends GetxController {
late final String? token;
late final String provider;
final deviceUuid = await PlatformDeviceId.getDeviceId;
final deviceUuid = await _getDeviceUuid();
if (deviceUuid == null) {
log("Unable to active push notifications, couldn't get device uuid");
}
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
provider = "apple";
@ -196,4 +208,31 @@ class AccountProvider extends GetxController {
throw Exception(resp.bodyString);
}
}
Future<String?> _getDeviceUuid() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (PlatformInfo.isWeb) {
final WebBrowserInfo webInfo = await deviceInfo.webBrowserInfo;
return webInfo.vendor! +
webInfo.userAgent! +
webInfo.hardwareConcurrency.toString();
}
if (PlatformInfo.isAndroid) {
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.id;
}
if (PlatformInfo.isIOS) {
final IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
return iosInfo.identifierForVendor!;
}
if (PlatformInfo.isLinux) {
final LinuxDeviceInfo linuxInfo = await deviceInfo.linuxInfo;
return linuxInfo.machineId!;
}
if (PlatformInfo.isWindows) {
final WindowsDeviceInfo windowsInfo = await deviceInfo.windowsInfo;
return windowsInfo.deviceId;
}
return null;
}
}

View File

@ -63,7 +63,7 @@ class ChatProvider extends GetxController {
},
onDone: () {
isConnected.value = false;
Future.delayed(const Duration(seconds: 3), () => connect());
Future.delayed(const Duration(seconds: 1), () => connect());
},
onError: (err) {
isConnected.value = false;

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart';
import 'package:get/get.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:permission_handler/permission_handler.dart';
@ -44,9 +43,6 @@ class ChatCallProvider extends GetxController {
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) {
return;
}
if (lkPlatformIs(PlatformType.android)) {
FlutterBackground.enableBackgroundExecution();
}
await Permission.camera.request();
await Permission.microphone.request();

View File

@ -78,9 +78,9 @@ class _AccountScreenState extends State<AccountScreen> {
);
}
return Column(
return ListView(
children: [
const AccountHeading().paddingOnly(bottom: 8),
const AccountHeading().paddingOnly(bottom: 8, top: 16),
...(actionItems.map(
(x) => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34),

View File

@ -27,7 +27,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
List<int> markList = List.empty(growable: true);
for (final element in provider.notifications) {
if (element.isRealtime) continue;
if (element.id <= 0) continue;
markList.add(element.id);
}
@ -48,7 +48,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
final AccountProvider provider = Get.find();
if (element.isRealtime) {
if (element.id <= 0) {
provider.notifications.removeAt(index);
return;
}

View File

@ -1,5 +1,5 @@
import 'dart:async';
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@ -337,37 +337,45 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
children: [
Expanded(
child: PagedListView<int, Message>(
clipBehavior: Clip.none,
reverse: true,
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Message>(
itemBuilder: buildHistory,
noItemsFoundIndicatorBuilder: (_) => Container(),
),
).paddingOnly(bottom: 64),
).paddingOnly(bottom: 56),
),
],
),
Positioned(
bottom: math.max(MediaQuery.of(context).padding.bottom, 16),
bottom: 0,
left: 0,
right: 0,
child: ChatMessageInput(
edit: _messageToEditing,
reply: _messageToReplying,
realm: widget.realm,
placeholder: placeholder,
channel: _channel!,
onSent: (Message item) {
setState(() {
_pagingController.itemList?.insert(0, item);
});
},
onReset: () {
setState(() {
_messageToReplying = null;
_messageToEditing = null;
});
},
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: SafeArea(
child: ChatMessageInput(
edit: _messageToEditing,
reply: _messageToReplying,
realm: widget.realm,
placeholder: placeholder,
channel: _channel!,
onSent: (Message item) {
setState(() {
_pagingController.itemList?.insert(0, item);
});
},
onReset: () {
setState(() {
_messageToReplying = null;
_messageToEditing = null;
});
},
),
),
),
),
),
if (_ongoingCall != null)

View File

@ -72,7 +72,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
ListTile(
leading: const Icon(Icons.edit),
trailing: const Icon(Icons.chevron_right),
title: Text('channelAdjust'.tr),
title: Text('channelAdjust'.tr.capitalize!),
onTap: () async {
AppRouter.instance
.pushNamed(
@ -129,12 +129,12 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
ListTile(
leading: const Icon(Icons.settings),
trailing: const Icon(Icons.chevron_right),
title: Text('channelSettings'.tr),
title: Text('channelSettings'.tr.capitalize!),
),
ListTile(
leading: const Icon(Icons.supervisor_account),
trailing: const Icon(Icons.chevron_right),
title: Text('channelMembers'.tr),
title: Text('channelMembers'.tr.capitalize!),
onTap: () => showMemberList(),
),
...(_isOwned ? ownerActions : List.empty()),

View File

@ -69,7 +69,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
ListTile(
leading: const Icon(Icons.edit),
trailing: const Icon(Icons.chevron_right),
title: Text('realmAdjust'.tr),
title: Text('realmAdjust'.tr.capitalize!),
onTap: () async {
AppRouter.instance
.pushNamed(
@ -122,12 +122,12 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
ListTile(
leading: const Icon(Icons.settings),
trailing: const Icon(Icons.chevron_right),
title: Text('realmSettings'.tr),
title: Text('realmSettings'.tr.capitalize!),
),
ListTile(
leading: const Icon(Icons.supervisor_account),
trailing: const Icon(Icons.chevron_right),
title: Text('realmMembers'.tr),
title: Text('realmMembers'.tr.capitalize!),
onTap: () => showMemberList(),
),
...(_isOwned ? ownerActions : List.empty()),

View File

@ -14,7 +14,10 @@ abstract class ServiceFinder {
static GetConnect configureClient(String service,
{timeout = const Duration(seconds: 5)}) {
final client = GetConnect(timeout: timeout, allowAutoSignedCert: true);
final client = GetConnect(
timeout: timeout,
allowAutoSignedCert: true,
);
client.httpClient.baseUrl = ServiceFinder.services[service];
return client;

View File

@ -116,7 +116,7 @@ class SolianMessages extends Translations {
'realmMembers': 'Realm members',
'realmMembersAdd': 'Add realm members',
'realmMembersAddHint': 'Into @realm',
'realmAdjust': 'Realm Adjustment',
'realmAdjust': 'Realm adjustment',
'realmSettings': 'Realm settings',
'realmEditingNotify': 'You\'re editing realm @realm',
'realmDeletionConfirm': 'Confirm realm deletion',
@ -142,9 +142,9 @@ class SolianMessages extends Translations {
'channelType': 'Channel type',
'channelTypeCommon': 'Regular',
'channelTypeDirect': 'DM',
'channelAdjust': 'Channel Adjustment',
'channelDetail': 'Channel Detail',
'channelSettings': 'Channel Settings',
'channelAdjust': 'Channel adjustment',
'channelDetail': 'Channel detail',
'channelSettings': 'Channel settings',
'channelDeletionConfirm': 'Confirm channel deletion',
'channelDeletionConfirmCaption':
'Are you sure to delete channel @channel? This action cannot be undone!',

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:get/get.dart';
import 'package:livekit_client/livekit_client.dart';
@ -171,35 +170,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
}
return;
}
if (lkPlatformIs(PlatformType.android)) {
requestBackgroundPermission([bool isRetry = false]) async {
try {
bool hasPermissions = await FlutterBackground.hasPermissions;
if (!isRetry) {
const androidConfig = FlutterBackgroundAndroidConfig(
notificationTitle: 'Screen Sharing',
notificationText: 'Solar Messager is sharing your screen',
notificationImportance: AndroidNotificationImportance.Default,
notificationIcon:
AndroidResource(name: 'launcher_icon', defType: 'mipmap'),
);
hasPermissions = await FlutterBackground.initialize(
androidConfig: androidConfig);
}
if (hasPermissions &&
!FlutterBackground.isBackgroundExecutionEnabled) {
await FlutterBackground.enableBackgroundExecution();
}
} catch (e) {
if (!isRetry) {
return await Future<void>.delayed(const Duration(seconds: 1),
() => requestBackgroundPermission(true));
}
}
}
await requestBackgroundPermission();
}
if (lkPlatformIs(PlatformType.iOS)) {
var track = await LocalVideoTrack.createScreenShareTrack(
const ScreenShareCaptureOptions(
@ -223,12 +193,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
void disableScreenShare() async {
await participant.setScreenShareEnabled(false);
if (lkPlatformIs(PlatformType.android)) {
// Android specific
try {
await FlutterBackground.disableBackgroundExecution();
} catch (_) {}
}
}
@override

View File

@ -56,7 +56,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
Map<String, dynamic> encodeMessage(String content) {
return {
'value': content,
'value': content.trim(),
'keypair_id': null,
'algorithm': 'plain',
};
@ -103,9 +103,11 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
senderId: sender.id,
);
message.isSending = true;
if (_editTo == null) {
message.isSending = true;
widget.onSent(message);
}
if (widget.edit == null) widget.onSent(message);
resetInput();
Response resp;
@ -131,6 +133,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
_editTo = null;
_replyTo = null;
_textController.clear();
_attachments.clear();
setState(() {});
}
@ -161,70 +164,66 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
)
];
return Material(
color: Theme.of(context).colorScheme.surface,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Divider(thickness: 0.3, height: 1),
if (_replyTo != null)
MaterialBanner(
leading: const FaIcon(FontAwesomeIcons.reply, size: 18),
dividerColor: Colors.transparent,
content: ChatMessage(
item: _replyTo!,
isContentPreviewing: true,
),
actions: notifyBannerActions,
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_replyTo != null)
MaterialBanner(
leading: const FaIcon(FontAwesomeIcons.reply, size: 18),
dividerColor: Colors.transparent,
content: ChatMessage(
item: _replyTo!,
isContentPreviewing: true,
),
if (_editTo != null)
MaterialBanner(
leading: const Icon(Icons.edit),
dividerColor: Colors.transparent,
content: ChatMessage(
item: _editTo!,
isContentPreviewing: true,
),
actions: notifyBannerActions,
),
SizedBox(
height: 56,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: TextField(
controller: _textController,
focusNode: _focusNode,
maxLines: null,
autocorrect: true,
keyboardType: TextInputType.text,
decoration: InputDecoration.collapsed(
hintText: widget.placeholder ??
'messageInputPlaceholder'.trParams(
{'channel': '#${widget.channel.alias}'},
),
),
onSubmitted: (_) => sendMessage(),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
IconButton(
icon: const Icon(Icons.attach_file),
color: Colors.teal,
onPressed: () => showAttachments(),
),
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.primary,
onPressed: () => sendMessage(),
)
],
).paddingOnly(left: 20, right: 16),
actions: notifyBannerActions,
),
],
),
if (_editTo != null)
MaterialBanner(
leading: const Icon(Icons.edit),
dividerColor: Colors.transparent,
content: ChatMessage(
item: _editTo!,
isContentPreviewing: true,
),
actions: notifyBannerActions,
),
SizedBox(
height: 56,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: TextField(
controller: _textController,
focusNode: _focusNode,
maxLines: null,
autocorrect: true,
keyboardType: TextInputType.text,
decoration: InputDecoration.collapsed(
hintText: widget.placeholder ??
'messageInputPlaceholder'.trParams(
{'channel': '#${widget.channel.alias}'},
),
),
onSubmitted: (_) => sendMessage(),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
),
IconButton(
icon: const Icon(Icons.attach_file),
color: Colors.teal,
onPressed: () => showAttachments(),
),
IconButton(
icon: const Icon(Icons.send),
color: Theme.of(context).colorScheme.primary,
onPressed: () => sendMessage(),
)
],
).paddingOnly(left: 20, right: 16),
),
],
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:solian/providers/account.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/chat.dart';
class BackgroundStateWidget extends StatelessWidget {
@ -9,6 +10,7 @@ class BackgroundStateWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
final AccountProvider account = Get.find();
final ChatProvider chat = Get.find();
@ -20,35 +22,51 @@ class BackgroundStateWidget extends StatelessWidget {
return Row(children: [
if (disconnected && !connecting)
IconButton(
tooltip: [
if (account.isConnected.isFalse)
'Lost Connection with Passport Server...',
if (chat.isConnected.isFalse)
'Lost Connection with Messaging Server...',
].join('\n'),
icon: const Icon(Icons.wifi_off)
.animate(onPlay: (c) => c.repeat())
.fadeIn(duration: 800.ms)
.then()
.fadeOut(duration: 800.ms),
onPressed: () {
if (account.isConnected.isFalse) account.connect();
if (chat.isConnected.isFalse) chat.connect();
FutureBuilder(
future: auth.isAuthorized,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == false) {
return const SizedBox();
}
return IconButton(
tooltip: [
if (account.isConnected.isFalse)
'Lost Connection with Passport Server...',
if (chat.isConnected.isFalse)
'Lost Connection with Messaging Server...',
].join('\n'),
icon: const Icon(Icons.wifi_off)
.animate(onPlay: (c) => c.repeat())
.fadeIn(duration: 800.ms)
.then()
.fadeOut(duration: 800.ms),
onPressed: () {
if (account.isConnected.isFalse) account.connect();
if (chat.isConnected.isFalse) chat.connect();
},
);
},
),
if (connecting)
IconButton(
tooltip: [
if (account.isConnecting.isTrue)
'Waiting Passport Server Response...',
if (chat.isConnecting.isTrue)
'Waiting Messaging Server Response...',
].join('\n'),
icon: const Icon(Icons.sync)
.animate(onPlay: (c) => c.repeat())
.rotate(duration: 1850.ms, begin: 1, end: 0),
onPressed: () {},
FutureBuilder(
future: auth.isAuthorized,
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == false) {
return const SizedBox();
}
return IconButton(
tooltip: [
if (account.isConnecting.isTrue)
'Waiting Passport Server Response...',
if (chat.isConnecting.isTrue)
'Waiting Messaging Server Response...',
].join('\n'),
icon: const Icon(Icons.sync)
.animate(onPlay: (c) => c.repeat())
.rotate(duration: 1850.ms, begin: 1, end: 0),
onPressed: () {},
);
},
),
]);
});