🐛 Bug fixes on macos

This commit is contained in:
LittleSheep 2024-04-27 20:10:15 +08:00
parent 657b497370
commit 34dee3773d
12 changed files with 69 additions and 82 deletions

View File

@ -78,6 +78,14 @@
"chatCall": "Call", "chatCall": "Call",
"chatCallOngoing": "A call is ongoing", "chatCallOngoing": "A call is ongoing",
"chatCallJoin": "Join", "chatCallJoin": "Join",
"chatCallMute": "Mute",
"chatCallUnMute": "Un-mute",
"chatCallVideoOff": "Turn Off Video",
"chatCallVideoOn": "Turn On Video",
"chatCallVideoFlip": "Flip Camera",
"chatCallScreenOn": "Start Screen Share",
"chatCallScreenOff": "Stop Screen Share",
"chatCallChangeSpeaker": "Change Speaker",
"chatMessagePlaceholder": "Write a message...", "chatMessagePlaceholder": "Write a message...",
"chatMessageEditNotify": "You are about editing a message.", "chatMessageEditNotify": "You are about editing a message.",
"chatMessageReplyNotify": "You are about replying a message.", "chatMessageReplyNotify": "You are about replying a message.",

View File

@ -76,6 +76,14 @@
"chatChannelLeaveConfirm": "你确定你要离开这个频道吗?你在这个频道里的消息将被存储下来,但是当你重新加入本频道后你将会失去对你之前消息的权限。", "chatChannelLeaveConfirm": "你确定你要离开这个频道吗?你在这个频道里的消息将被存储下来,但是当你重新加入本频道后你将会失去对你之前消息的权限。",
"chatChannelDeleteConfirm": "你确定你要删除这个频道吗?这个频道里的所有消息都将消失,并且不可被反转!", "chatChannelDeleteConfirm": "你确定你要删除这个频道吗?这个频道里的所有消息都将消失,并且不可被反转!",
"chatCall": "通话", "chatCall": "通话",
"chatCallMute": "静音",
"chatCallUnMute": "取消静音",
"chatCallVideoOff": "关闭摄像头",
"chatCallVideoOn": "启动摄像头",
"chatCallVideoFlip": "翻转视频输出",
"chatCallScreenOn": "开启屏幕分享",
"chatCallScreenOff": "停止屏幕分享",
"chatCallChangeSpeaker": "切换扬声器",
"chatCallOngoing": "一则通话正在进行中", "chatCallOngoing": "一则通话正在进行中",
"chatCallJoin": "加入", "chatCallJoin": "加入",
"chatMessagePlaceholder": "发条消息……", "chatMessagePlaceholder": "发条消息……",

View File

@ -12,6 +12,8 @@ import 'package:solian/utils/video_player.dart';
import 'package:solian/widgets/notification_notifier.dart'; import 'package:solian/widgets/notification_notifier.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized();
initVideo(); initVideo();
initTimeAgo(); initTimeAgo();

View File

@ -13,6 +13,7 @@ import 'package:solian/widgets/chat/call/participant.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';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../widgets/chat/call/controls.dart'; import '../../widgets/chat/call/controls.dart';
@ -50,9 +51,8 @@ class _ChatCallState extends State<ChatCall> {
List<ParticipantTrack> _participantTracks = []; List<ParticipantTrack> _participantTracks = [];
bool get _fastConnection => _callRoom.engine.fastConnectOptions != null;
Future<void> checkPermissions() async { Future<void> checkPermissions() async {
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return;
await Permission.camera.request(); await Permission.camera.request();
await Permission.microphone.request(); await Permission.microphone.request();
await Permission.bluetooth.request(); await Permission.bluetooth.request();
@ -145,16 +145,14 @@ class _ChatCallState extends State<ChatCall> {
} }
} }
void askPublish() async { void autoPublish() async {
final result = await context.showPublishDialog();
if (result != true) return;
try { try {
await _callRoom.localParticipant?.setCameraEnabled(true); if (_enableVideo) await _callRoom.localParticipant?.setCameraEnabled(true);
} catch (error) { } catch (error) {
await context.showErrorDialog(error); await context.showErrorDialog(error);
} }
try { try {
await _callRoom.localParticipant?.setMicrophoneEnabled(true); if (_enableAudio) await _callRoom.localParticipant?.setMicrophoneEnabled(true);
} catch (error) { } catch (error) {
await context.showErrorDialog(error); await context.showErrorDialog(error);
} }
@ -164,11 +162,7 @@ class _ChatCallState extends State<ChatCall> {
_callRoom.addListener(onRoomDidUpdate); _callRoom.addListener(onRoomDidUpdate);
setupRoomListeners(); setupRoomListeners();
sortParticipants(); sortParticipants();
WidgetsBindingCompatible.instance?.addPostFrameCallback((_) { WidgetsBindingCompatible.instance?.addPostFrameCallback((_) => autoPublish());
if (!_fastConnection) {
askPublish();
}
});
if (lkPlatformIsMobile()) { if (lkPlatformIsMobile()) {
Hardware.instance.setSpeakerphoneOn(true); Hardware.instance.setSpeakerphoneOn(true);
@ -362,6 +356,7 @@ class _ChatCallState extends State<ChatCall> {
_callRoom = Room(); _callRoom = Room();
_callListener = _callRoom.createListener(); _callListener = _callRoom.createListener();
Hardware.instance.enumerateDevices().then(revertDevices); Hardware.instance.enumerateDevices().then(revertDevices);
WakelockPlus.enable();
} }
@override @override
@ -425,6 +420,7 @@ class _ChatCallState extends State<ChatCall> {
@override @override
void dispose() { void dispose() {
WakelockPlus.disable();
(() async { (() async {
_callRoom.removeListener(onRoomDidUpdate); _callRoom.removeListener(onRoomDidUpdate);
await _callRoom.disconnect(); await _callRoom.disconnect();

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_background/flutter_background.dart'; 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:solian/widgets/chat/call/exts.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ControlsWidget extends StatefulWidget { class ControlsWidget extends StatefulWidget {
final Room room; final Room room;
@ -61,11 +61,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
void onChange() => setState(() {}); void onChange() => setState(() {});
void unpublishAll() async {
final result = await context.showUnPublishDialog();
if (result == true) await participant.unpublishAllTracks();
}
bool get isMuted => participant.isMuted; bool get isMuted => participant.isMuted;
void disableAudio() async { void disableAudio() async {
@ -106,7 +101,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
} }
void toggleCamera() async { void toggleCamera() async {
//
final track = participant.videoTrackPublications.firstOrNull?.track; final track = participant.videoTrackPublications.firstOrNull?.track;
if (track == null) return; if (track == null) return;
@ -202,22 +196,6 @@ class _ControlsWidgetState extends State<ControlsWidget> {
} }
} }
void onTapUpdateSubscribePermission() async {
final result = await context.showSubscribePermissionDialog();
if (result != null) {
try {
widget.room.localParticipant?.setTrackSubscriptionPermissions(
allParticipantsAllowed: result,
);
} catch (e) {
final message = e.toString();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Something went wrong... $message'),
));
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
@ -230,17 +208,12 @@ class _ControlsWidgetState extends State<ControlsWidget> {
spacing: 5, spacing: 5,
runSpacing: 5, runSpacing: 5,
children: [ children: [
IconButton(
onPressed: unpublishAll,
icon: const Icon(Icons.cancel),
tooltip: 'Unpublish all',
),
if (participant.isMicrophoneEnabled()) if (participant.isMicrophoneEnabled())
if (lkPlatformIs(PlatformType.android)) if (lkPlatformIs(PlatformType.android))
IconButton( IconButton(
onPressed: disableAudio, onPressed: disableAudio,
icon: const Icon(Icons.mic), icon: const Icon(Icons.mic),
tooltip: 'mute audio', tooltip: AppLocalizations.of(context)!.chatCallMute,
) )
else else
PopupMenuButton<MediaDevice>( PopupMenuButton<MediaDevice>(
@ -250,9 +223,9 @@ class _ControlsWidgetState extends State<ControlsWidget> {
PopupMenuItem<MediaDevice>( PopupMenuItem<MediaDevice>(
value: null, value: null,
onTap: isMuted ? enableAudio : disableAudio, onTap: isMuted ? enableAudio : disableAudio,
child: const ListTile( child: ListTile(
leading: Icon(Icons.mic_off), leading: const Icon(Icons.mic_off),
title: Text('Mute Microphone'), title: Text(AppLocalizations.of(context)!.chatCallMute),
), ),
), ),
if (_audioInputs != null) if (_audioInputs != null)
@ -275,7 +248,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
IconButton( IconButton(
onPressed: enableAudio, onPressed: enableAudio,
icon: const Icon(Icons.mic_off), icon: const Icon(Icons.mic_off),
tooltip: 'un-mute audio', tooltip: AppLocalizations.of(context)!.chatCallUnMute,
), ),
if (!lkPlatformIs(PlatformType.iOS)) if (!lkPlatformIs(PlatformType.iOS))
PopupMenuButton<MediaDevice>( PopupMenuButton<MediaDevice>(
@ -310,7 +283,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
disabledColor: Colors.grey, disabledColor: Colors.grey,
onPressed: Hardware.instance.canSwitchSpeakerphone ? setSpeakerphoneOn : null, onPressed: Hardware.instance.canSwitchSpeakerphone ? setSpeakerphoneOn : null,
icon: Icon(_speakerphoneOn ? Icons.speaker_phone : Icons.phone_android), icon: Icon(_speakerphoneOn ? Icons.speaker_phone : Icons.phone_android),
tooltip: 'Switch SpeakerPhone', tooltip: AppLocalizations.of(context)!.chatCallChangeSpeaker,
), ),
if (participant.isCameraEnabled()) if (participant.isCameraEnabled())
PopupMenuButton<MediaDevice>( PopupMenuButton<MediaDevice>(
@ -320,12 +293,9 @@ class _ControlsWidgetState extends State<ControlsWidget> {
PopupMenuItem<MediaDevice>( PopupMenuItem<MediaDevice>(
value: null, value: null,
onTap: disableVideo, onTap: disableVideo,
child: const ListTile( child: ListTile(
leading: Icon( leading: const Icon(Icons.videocam_off, color: Colors.white),
Icons.videocam_off, title: Text(AppLocalizations.of(context)!.chatCallVideoOff),
color: Colors.white,
),
title: Text('Disable Camera'),
), ),
), ),
if (_videoInputs != null) if (_videoInputs != null)
@ -348,29 +318,24 @@ class _ControlsWidgetState extends State<ControlsWidget> {
IconButton( IconButton(
onPressed: enableVideo, onPressed: enableVideo,
icon: const Icon(Icons.videocam_off), icon: const Icon(Icons.videocam_off),
tooltip: 'un-mute video', tooltip: AppLocalizations.of(context)!.chatCallVideoOn,
), ),
IconButton( IconButton(
icon: Icon(position == CameraPosition.back ? Icons.video_camera_back : Icons.video_camera_front), icon: Icon(position == CameraPosition.back ? Icons.video_camera_back : Icons.video_camera_front),
onPressed: () => toggleCamera(), onPressed: () => toggleCamera(),
tooltip: 'toggle camera', tooltip: AppLocalizations.of(context)!.chatCallVideoFlip,
), ),
if (participant.isScreenShareEnabled()) if (participant.isScreenShareEnabled())
IconButton( IconButton(
icon: const Icon(Icons.monitor_outlined), icon: const Icon(Icons.monitor_outlined),
onPressed: () => disableScreenShare(), onPressed: () => disableScreenShare(),
tooltip: 'unshare screen (experimental)', tooltip: AppLocalizations.of(context)!.chatCallScreenOff,
) )
else else
IconButton( IconButton(
icon: const Icon(Icons.monitor), icon: const Icon(Icons.monitor),
onPressed: () => enableScreenShare(), onPressed: () => enableScreenShare(),
tooltip: 'share screen (experimental)', tooltip: AppLocalizations.of(context)!.chatCallScreenOn,
),
IconButton(
onPressed: onTapUpdateSubscribePermission,
icon: const Icon(Icons.settings),
tooltip: 'Subscribe permission',
), ),
], ],
), ),

View File

@ -156,7 +156,6 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
_firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true, _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true,
connectionQuality: widget.participant.connectionQuality, connectionQuality: widget.participant.connectionQuality,
isScreenShare: widget.isScreenShare, isScreenShare: widget.isScreenShare,
enabledE2EE: widget.participant.isEncrypted,
), ),
], ],
), ),

View File

@ -6,7 +6,6 @@ class ParticipantInfoWidget extends StatelessWidget {
final bool audioAvailable; final bool audioAvailable;
final ConnectionQuality connectionQuality; final ConnectionQuality connectionQuality;
final bool isScreenShare; final bool isScreenShare;
final bool enabledE2EE;
const ParticipantInfoWidget({ const ParticipantInfoWidget({
super.key, super.key,
@ -14,7 +13,6 @@ class ParticipantInfoWidget extends StatelessWidget {
this.audioAvailable = true, this.audioAvailable = true,
this.connectionQuality = ConnectionQuality.unknown, this.connectionQuality = ConnectionQuality.unknown,
this.isScreenShare = false, this.isScreenShare = false,
this.enabledE2EE = false,
}); });
@override @override
@ -65,14 +63,6 @@ class ParticipantInfoWidget extends StatelessWidget {
size: 16, size: 16,
), ),
), ),
Padding(
padding: const EdgeInsets.only(left: 5),
child: Icon(
enabledE2EE ? Icons.lock : Icons.lock_open,
color: enabledE2EE ? Colors.green : Colors.red,
size: 16,
),
),
], ],
), ),
); );

View File

@ -27,6 +27,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
final _textController = TextEditingController(); final _textController = TextEditingController();
bool _isSubmitting = false; bool _isSubmitting = false;
int? _prevEditingId;
List<Attachment> _attachments = List.empty(growable: true); List<Attachment> _attachments = List.empty(growable: true);
@ -80,8 +81,9 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
} }
void syncWidget() { void syncWidget() {
if (widget.editing != null) { if (widget.editing != null && _prevEditingId != widget.editing!.id) {
setState(() { setState(() {
_prevEditingId = widget.editing!.id;
_textController.text = widget.editing!.content; _textController.text = widget.editing!.content;
_attachments = widget.editing!.attachments ?? List.empty(growable: true); _attachments = widget.editing!.attachments ?? List.empty(growable: true);
}); });

View File

@ -6,11 +6,19 @@
<true/> <true/>
<key>com.apple.security.cs.allow-jit</key> <key>com.apple.security.cs.allow-jit</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.device.audio-input</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/> <true/>
<key>com.apple.security.files.user-selected.read-only</key> <key>com.apple.security.files.user-selected.read-only</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array/>
</dict> </dict>
</plist> </plist>

View File

@ -4,9 +4,17 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/> <true/>
<key>com.apple.security.files.user-selected.read-only</key> <key>com.apple.security.files.user-selected.read-only</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array/>
</dict> </dict>
</plist> </plist>

View File

@ -1163,7 +1163,7 @@ packages:
source: hosted source: hosted
version: "2.0.7" version: "2.0.7"
wakelock_plus: wakelock_plus:
dependency: transitive dependency: "direct main"
description: description:
name: wakelock_plus name: wakelock_plus
sha256: c8b7cc80f045533b40a0e6c9109905494e3cf32c0fbd5c62616998e0de44003f sha256: c8b7cc80f045533b40a0e6c9109905494e3cf32c0fbd5c62616998e0de44003f

View File

@ -64,6 +64,7 @@ dependencies:
permission_handler: ^11.3.1 permission_handler: ^11.3.1
flutter_webrtc: ^0.10.3 flutter_webrtc: ^0.10.3
flutter_background: ^1.2.0 flutter_background: ^1.2.0
wakelock_plus: ^1.2.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: