diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 7e41bf0..30cb705 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -78,6 +78,14 @@ "chatCall": "Call", "chatCallOngoing": "A call is ongoing", "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...", "chatMessageEditNotify": "You are about editing a message.", "chatMessageReplyNotify": "You are about replying a message.", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 46a63f1..586bbc1 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -76,6 +76,14 @@ "chatChannelLeaveConfirm": "你确定你要离开这个频道吗?你在这个频道里的消息将被存储下来,但是当你重新加入本频道后你将会失去对你之前消息的权限。", "chatChannelDeleteConfirm": "你确定你要删除这个频道吗?这个频道里的所有消息都将消失,并且不可被反转!", "chatCall": "通话", + "chatCallMute": "静音", + "chatCallUnMute": "取消静音", + "chatCallVideoOff": "关闭摄像头", + "chatCallVideoOn": "启动摄像头", + "chatCallVideoFlip": "翻转视频输出", + "chatCallScreenOn": "开启屏幕分享", + "chatCallScreenOff": "停止屏幕分享", + "chatCallChangeSpeaker": "切换扬声器", "chatCallOngoing": "一则通话正在进行中", "chatCallJoin": "加入", "chatMessagePlaceholder": "发条消息……", diff --git a/lib/main.dart b/lib/main.dart index 5df3c30..9ff2c74 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,8 @@ import 'package:solian/utils/video_player.dart'; import 'package:solian/widgets/notification_notifier.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + initVideo(); initTimeAgo(); diff --git a/lib/screens/chat/call.dart b/lib/screens/chat/call.dart index a28c5c5..8b7fabc 100644 --- a/lib/screens/chat/call.dart +++ b/lib/screens/chat/call.dart @@ -13,6 +13,7 @@ import 'package:solian/widgets/chat/call/participant.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; import 'dart:math' as math; import '../../widgets/chat/call/controls.dart'; @@ -50,9 +51,8 @@ class _ChatCallState extends State { List _participantTracks = []; - bool get _fastConnection => _callRoom.engine.fastConnectOptions != null; - Future checkPermissions() async { + if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return; await Permission.camera.request(); await Permission.microphone.request(); await Permission.bluetooth.request(); @@ -87,7 +87,7 @@ class _ChatCallState extends State { } void joinRoom(String url, String token) async { - if(_isMounted) { + if (_isMounted) { return; } else { _isMounted = true; @@ -145,16 +145,14 @@ class _ChatCallState extends State { } } - void askPublish() async { - final result = await context.showPublishDialog(); - if (result != true) return; + void autoPublish() async { try { - await _callRoom.localParticipant?.setCameraEnabled(true); + if (_enableVideo) await _callRoom.localParticipant?.setCameraEnabled(true); } catch (error) { await context.showErrorDialog(error); } try { - await _callRoom.localParticipant?.setMicrophoneEnabled(true); + if (_enableAudio) await _callRoom.localParticipant?.setMicrophoneEnabled(true); } catch (error) { await context.showErrorDialog(error); } @@ -164,11 +162,7 @@ class _ChatCallState extends State { _callRoom.addListener(onRoomDidUpdate); setupRoomListeners(); sortParticipants(); - WidgetsBindingCompatible.instance?.addPostFrameCallback((_) { - if (!_fastConnection) { - askPublish(); - } - }); + WidgetsBindingCompatible.instance?.addPostFrameCallback((_) => autoPublish()); if (lkPlatformIsMobile()) { Hardware.instance.setSpeakerphoneOn(true); @@ -362,6 +356,7 @@ class _ChatCallState extends State { _callRoom = Room(); _callListener = _callRoom.createListener(); Hardware.instance.enumerateDevices().then(revertDevices); + WakelockPlus.enable(); } @override @@ -425,6 +420,7 @@ class _ChatCallState extends State { @override void dispose() { + WakelockPlus.disable(); (() async { _callRoom.removeListener(onRoomDidUpdate); await _callRoom.disconnect(); diff --git a/lib/widgets/chat/call/controls.dart b/lib/widgets/chat/call/controls.dart index 15a581b..63073fc 100644 --- a/lib/widgets/chat/call/controls.dart +++ b/lib/widgets/chat/call/controls.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_background/flutter_background.dart'; import 'package:flutter_webrtc/flutter_webrtc.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 { final Room room; @@ -61,11 +61,6 @@ class _ControlsWidgetState extends State { void onChange() => setState(() {}); - void unpublishAll() async { - final result = await context.showUnPublishDialog(); - if (result == true) await participant.unpublishAllTracks(); - } - bool get isMuted => participant.isMuted; void disableAudio() async { @@ -106,7 +101,6 @@ class _ControlsWidgetState extends State { } void toggleCamera() async { - // final track = participant.videoTrackPublications.firstOrNull?.track; if (track == null) return; @@ -202,22 +196,6 @@ class _ControlsWidgetState extends State { } } - 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 Widget build(BuildContext context) { return Padding( @@ -230,17 +208,12 @@ class _ControlsWidgetState extends State { spacing: 5, runSpacing: 5, children: [ - IconButton( - onPressed: unpublishAll, - icon: const Icon(Icons.cancel), - tooltip: 'Unpublish all', - ), if (participant.isMicrophoneEnabled()) if (lkPlatformIs(PlatformType.android)) IconButton( onPressed: disableAudio, icon: const Icon(Icons.mic), - tooltip: 'mute audio', + tooltip: AppLocalizations.of(context)!.chatCallMute, ) else PopupMenuButton( @@ -250,9 +223,9 @@ class _ControlsWidgetState extends State { PopupMenuItem( value: null, onTap: isMuted ? enableAudio : disableAudio, - child: const ListTile( - leading: Icon(Icons.mic_off), - title: Text('Mute Microphone'), + child: ListTile( + leading: const Icon(Icons.mic_off), + title: Text(AppLocalizations.of(context)!.chatCallMute), ), ), if (_audioInputs != null) @@ -275,7 +248,7 @@ class _ControlsWidgetState extends State { IconButton( onPressed: enableAudio, icon: const Icon(Icons.mic_off), - tooltip: 'un-mute audio', + tooltip: AppLocalizations.of(context)!.chatCallUnMute, ), if (!lkPlatformIs(PlatformType.iOS)) PopupMenuButton( @@ -310,7 +283,7 @@ class _ControlsWidgetState extends State { disabledColor: Colors.grey, onPressed: Hardware.instance.canSwitchSpeakerphone ? setSpeakerphoneOn : null, icon: Icon(_speakerphoneOn ? Icons.speaker_phone : Icons.phone_android), - tooltip: 'Switch SpeakerPhone', + tooltip: AppLocalizations.of(context)!.chatCallChangeSpeaker, ), if (participant.isCameraEnabled()) PopupMenuButton( @@ -320,12 +293,9 @@ class _ControlsWidgetState extends State { PopupMenuItem( value: null, onTap: disableVideo, - child: const ListTile( - leading: Icon( - Icons.videocam_off, - color: Colors.white, - ), - title: Text('Disable Camera'), + child: ListTile( + leading: const Icon(Icons.videocam_off, color: Colors.white), + title: Text(AppLocalizations.of(context)!.chatCallVideoOff), ), ), if (_videoInputs != null) @@ -348,30 +318,25 @@ class _ControlsWidgetState extends State { IconButton( onPressed: enableVideo, icon: const Icon(Icons.videocam_off), - tooltip: 'un-mute video', + tooltip: AppLocalizations.of(context)!.chatCallVideoOn, ), IconButton( icon: Icon(position == CameraPosition.back ? Icons.video_camera_back : Icons.video_camera_front), onPressed: () => toggleCamera(), - tooltip: 'toggle camera', + tooltip: AppLocalizations.of(context)!.chatCallVideoFlip, ), if (participant.isScreenShareEnabled()) IconButton( icon: const Icon(Icons.monitor_outlined), onPressed: () => disableScreenShare(), - tooltip: 'unshare screen (experimental)', + tooltip: AppLocalizations.of(context)!.chatCallScreenOff, ) else IconButton( icon: const Icon(Icons.monitor), onPressed: () => enableScreenShare(), - tooltip: 'share screen (experimental)', + tooltip: AppLocalizations.of(context)!.chatCallScreenOn, ), - IconButton( - onPressed: onTapUpdateSubscribePermission, - icon: const Icon(Icons.settings), - tooltip: 'Subscribe permission', - ), ], ), ); diff --git a/lib/widgets/chat/call/participant.dart b/lib/widgets/chat/call/participant.dart index bc57304..053bbee 100644 --- a/lib/widgets/chat/call/participant.dart +++ b/lib/widgets/chat/call/participant.dart @@ -156,7 +156,6 @@ abstract class _ParticipantWidgetState extends Stat _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true, connectionQuality: widget.participant.connectionQuality, isScreenShare: widget.isScreenShare, - enabledE2EE: widget.participant.isEncrypted, ), ], ), diff --git a/lib/widgets/chat/call/participant_info.dart b/lib/widgets/chat/call/participant_info.dart index 9a62fd2..fc918c4 100644 --- a/lib/widgets/chat/call/participant_info.dart +++ b/lib/widgets/chat/call/participant_info.dart @@ -6,7 +6,6 @@ class ParticipantInfoWidget extends StatelessWidget { final bool audioAvailable; final ConnectionQuality connectionQuality; final bool isScreenShare; - final bool enabledE2EE; const ParticipantInfoWidget({ super.key, @@ -14,7 +13,6 @@ class ParticipantInfoWidget extends StatelessWidget { this.audioAvailable = true, this.connectionQuality = ConnectionQuality.unknown, this.isScreenShare = false, - this.enabledE2EE = false, }); @override @@ -65,14 +63,6 @@ class ParticipantInfoWidget extends StatelessWidget { 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, - ), - ), ], ), ); diff --git a/lib/widgets/chat/message_editor.dart b/lib/widgets/chat/message_editor.dart index ceee790..af45046 100644 --- a/lib/widgets/chat/message_editor.dart +++ b/lib/widgets/chat/message_editor.dart @@ -27,6 +27,7 @@ class _ChatMessageEditorState extends State { final _textController = TextEditingController(); bool _isSubmitting = false; + int? _prevEditingId; List _attachments = List.empty(growable: true); @@ -80,8 +81,9 @@ class _ChatMessageEditorState extends State { } void syncWidget() { - if (widget.editing != null) { + if (widget.editing != null && _prevEditingId != widget.editing!.id) { setState(() { + _prevEditingId = widget.editing!.id; _textController.text = widget.editing!.content; _attachments = widget.editing!.attachments ?? List.empty(growable: true); }); diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 9f689d1..0593444 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -6,11 +6,19 @@ com.apple.security.cs.allow-jit - com.apple.security.network.server + com.apple.security.device.audio-input + + com.apple.security.device.bluetooth + + com.apple.security.device.camera + + com.apple.security.files.user-selected.read-only com.apple.security.network.client - - com.apple.security.files.user-selected.read-only - + + com.apple.security.network.server + + keychain-access-groups + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 30ed24a..27481de 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,9 +4,17 @@ com.apple.security.app-sandbox + com.apple.security.device.audio-input + + com.apple.security.device.bluetooth + + com.apple.security.device.camera + + com.apple.security.files.user-selected.read-only + com.apple.security.network.client - - com.apple.security.files.user-selected.read-only - + + keychain-access-groups + diff --git a/pubspec.lock b/pubspec.lock index 0d7eb6d..1f94fcf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1163,7 +1163,7 @@ packages: source: hosted version: "2.0.7" wakelock_plus: - dependency: transitive + dependency: "direct main" description: name: wakelock_plus sha256: c8b7cc80f045533b40a0e6c9109905494e3cf32c0fbd5c62616998e0de44003f diff --git a/pubspec.yaml b/pubspec.yaml index 0d90f15..25c39e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: permission_handler: ^11.3.1 flutter_webrtc: ^0.10.3 flutter_background: ^1.2.0 + wakelock_plus: ^1.2.4 dev_dependencies: flutter_test: