💫 Auto hide or show call controls
This commit is contained in:
parent
c88fcc84da
commit
7e8993fbd2
@ -131,6 +131,8 @@ class ChatEventController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertEvent(LocalEvent entry) {
|
insertEvent(LocalEvent entry) {
|
||||||
|
if (entry.channelId != channel?.id) return;
|
||||||
|
|
||||||
final idx = currentEvents.indexWhere((x) => x.data.uuid == entry.data.uuid);
|
final idx = currentEvents.indexWhere((x) => x.data.uuid == entry.data.uuid);
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
currentEvents[idx] = entry;
|
currentEvents[idx] = entry;
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get/get_rx/get_rx.dart';
|
||||||
import 'package:livekit_client/livekit_client.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/call.dart';
|
import 'package:solian/models/call.dart';
|
||||||
@ -16,6 +17,7 @@ class ChatCallProvider extends GetxController {
|
|||||||
|
|
||||||
RxBool isReady = false.obs;
|
RxBool isReady = false.obs;
|
||||||
RxBool isMounted = false.obs;
|
RxBool isMounted = false.obs;
|
||||||
|
RxBool isInitialized = false.obs;
|
||||||
|
|
||||||
String? token;
|
String? token;
|
||||||
String? endpoint;
|
String? endpoint;
|
||||||
@ -151,6 +153,8 @@ class ChatCallProvider extends GetxController {
|
|||||||
void onRoomDidUpdate() => sortParticipants();
|
void onRoomDidUpdate() => sortParticipants();
|
||||||
|
|
||||||
void setupRoom() {
|
void setupRoom() {
|
||||||
|
if(isInitialized.value) return;
|
||||||
|
|
||||||
sortParticipants();
|
sortParticipants();
|
||||||
room.addListener(onRoomDidUpdate);
|
room.addListener(onRoomDidUpdate);
|
||||||
WidgetsBindingCompatible.instance?.addPostFrameCallback(
|
WidgetsBindingCompatible.instance?.addPostFrameCallback(
|
||||||
@ -160,6 +164,8 @@ class ChatCallProvider extends GetxController {
|
|||||||
if (lkPlatformIsMobile()) {
|
if (lkPlatformIsMobile()) {
|
||||||
Hardware.instance.setSpeakerphoneOn(true);
|
Hardware.instance.setSpeakerphoneOn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInitialized.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupRoomListeners({
|
void setupRoomListeners({
|
||||||
@ -362,6 +368,7 @@ class ChatCallProvider extends GetxController {
|
|||||||
|
|
||||||
void disposeRoom() {
|
void disposeRoom() {
|
||||||
isMounted.value = false;
|
isMounted.value = false;
|
||||||
|
isInitialized.value = false;
|
||||||
current.value = null;
|
current.value = null;
|
||||||
channel.value = null;
|
channel.value = null;
|
||||||
room.removeListener(onRoomDidUpdate);
|
room.removeListener(onRoomDidUpdate);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:async/async.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/providers/call.dart';
|
import 'package:solian/providers/call.dart';
|
||||||
@ -16,11 +17,24 @@ class CallScreen extends StatefulWidget {
|
|||||||
State<CallScreen> createState() => _CallScreenState();
|
State<CallScreen> createState() => _CallScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CallScreenState extends State<CallScreen> {
|
class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
|
||||||
Timer? timer;
|
Timer? _timer;
|
||||||
String currentDuration = '00:00:00';
|
String _currentDuration = '00:00:00';
|
||||||
|
|
||||||
String parseDuration() {
|
bool _showControls = true;
|
||||||
|
CancelableOperation? _hideControlsOperation;
|
||||||
|
|
||||||
|
late final AnimationController _controlsAnimationController =
|
||||||
|
AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
late final Animation<double> _controlsAnimation = CurvedAnimation(
|
||||||
|
parent: _controlsAnimationController,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
String _parseDuration() {
|
||||||
final ChatCallProvider provider = Get.find();
|
final ChatCallProvider provider = Get.find();
|
||||||
if (provider.current.value == null) return '00:00:00';
|
if (provider.current.value == null) return '00:00:00';
|
||||||
Duration duration =
|
Duration duration =
|
||||||
@ -34,18 +48,50 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
return formattedTime;
|
return formattedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateDuration() {
|
void _updateDuration() {
|
||||||
setState(() {
|
setState(() {
|
||||||
currentDuration = parseDuration();
|
_currentDuration = _parseDuration();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleControls() {
|
||||||
|
if (_showControls) {
|
||||||
|
setState(() => _showControls = false);
|
||||||
|
_controlsAnimationController.animateTo(0);
|
||||||
|
_hideControlsOperation?.cancel();
|
||||||
|
} else {
|
||||||
|
setState(() => _showControls = true);
|
||||||
|
_controlsAnimationController.animateTo(1);
|
||||||
|
_planAutoHideControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _planAutoHideControls() {
|
||||||
|
_hideControlsOperation = CancelableOperation.fromFuture(
|
||||||
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_showControls) _toggleControls();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
Get.find<ChatCallProvider>().setupRoom();
|
Get.find<ChatCallProvider>().setupRoom();
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
timer = Timer.periodic(const Duration(seconds: 1), (_) => updateDuration());
|
_updateDuration();
|
||||||
|
_planAutoHideControls();
|
||||||
|
_timer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(_) => _updateDuration(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controlsAnimationController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -68,13 +114,15 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
),
|
),
|
||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: currentDuration,
|
text: _currentDuration,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Stack(
|
() => Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -93,10 +141,17 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (provider.room.localParticipant != null)
|
if (provider.room.localParticipant != null)
|
||||||
ControlsWidget(
|
SizeTransition(
|
||||||
|
sizeFactor: _controlsAnimation,
|
||||||
|
axis: Axis.vertical,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: ControlsWidget(
|
||||||
provider.room,
|
provider.room,
|
||||||
provider.room.localParticipant!,
|
provider.room.localParticipant!,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -107,7 +162,8 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
height: 128,
|
height: 128,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: math.max(0, provider.participantTracks.length),
|
itemCount:
|
||||||
|
math.max(0, provider.participantTracks.length),
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final track = provider.participantTracks[index];
|
final track = provider.participantTracks[index];
|
||||||
if (track.participant.sid ==
|
if (track.participant.sid ==
|
||||||
@ -143,6 +199,10 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
_toggleControls();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -150,16 +210,16 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void deactivate() {
|
void deactivate() {
|
||||||
timer?.cancel();
|
_timer?.cancel();
|
||||||
timer = null;
|
_timer = null;
|
||||||
super.deactivate();
|
super.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void activate() {
|
void activate() {
|
||||||
timer ??= Timer.periodic(
|
_timer ??= Timer.periodic(
|
||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
(_) => updateDuration(),
|
(_) => _updateDuration(),
|
||||||
);
|
);
|
||||||
super.activate();
|
super.activate();
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
break;
|
break;
|
||||||
case 'calls.new':
|
case 'calls.new':
|
||||||
final payload = Call.fromJson(event.payload!);
|
final payload = Call.fromJson(event.payload!);
|
||||||
|
if (payload.channel.id == _channel!.id) {
|
||||||
setState(() => _ongoingCall = payload);
|
setState(() => _ongoingCall = payload);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'calls.end':
|
case 'calls.end':
|
||||||
|
final payload = Call.fromJson(event.payload!);
|
||||||
|
if (payload.channel.id == _channel!.id) {
|
||||||
setState(() => _ongoingCall = null);
|
setState(() => _ongoingCall = null);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ class ControlsWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ControlsWidgetState extends State<ControlsWidget> {
|
class _ControlsWidgetState extends State<ControlsWidget> {
|
||||||
CameraPosition position = CameraPosition.front;
|
CameraPosition _position = CameraPosition.front;
|
||||||
|
|
||||||
List<MediaDevice>? _audioInputs;
|
List<MediaDevice>? _audioInputs;
|
||||||
List<MediaDevice>? _audioOutputs;
|
List<MediaDevice>? _audioOutputs;
|
||||||
@ -36,25 +36,25 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
participant.addListener(onChange);
|
_participant.addListener(onChange);
|
||||||
_subscription = Hardware.instance.onDeviceChange.stream
|
_subscription = Hardware.instance.onDeviceChange.stream
|
||||||
.listen((List<MediaDevice> devices) {
|
.listen((List<MediaDevice> devices) {
|
||||||
revertDevices(devices);
|
_revertDevices(devices);
|
||||||
});
|
});
|
||||||
Hardware.instance.enumerateDevices().then(revertDevices);
|
Hardware.instance.enumerateDevices().then(_revertDevices);
|
||||||
_speakerphoneOn = Hardware.instance.speakerOn ?? false;
|
_speakerphoneOn = Hardware.instance.speakerOn ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subscription?.cancel();
|
_subscription?.cancel();
|
||||||
participant.removeListener(onChange);
|
_participant.removeListener(onChange);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalParticipant get participant => widget.participant;
|
LocalParticipant get _participant => widget.participant;
|
||||||
|
|
||||||
void revertDevices(List<MediaDevice> devices) async {
|
void _revertDevices(List<MediaDevice> devices) async {
|
||||||
_audioInputs = devices.where((d) => d.kind == 'audioinput').toList();
|
_audioInputs = devices.where((d) => d.kind == 'audioinput').toList();
|
||||||
_audioOutputs = devices.where((d) => d.kind == 'audiooutput').toList();
|
_audioOutputs = devices.where((d) => d.kind == 'audiooutput').toList();
|
||||||
_videoInputs = devices.where((d) => d.kind == 'videoinput').toList();
|
_videoInputs = devices.where((d) => d.kind == 'videoinput').toList();
|
||||||
@ -63,7 +63,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
|
|
||||||
void onChange() => setState(() {});
|
void onChange() => setState(() {});
|
||||||
|
|
||||||
bool get isMuted => participant.isMuted;
|
bool get isMuted => _participant.isMuted;
|
||||||
|
|
||||||
Future<bool?> showDisconnectDialog() {
|
Future<bool?> showDisconnectDialog() {
|
||||||
return showDialog<bool>(
|
return showDialog<bool>(
|
||||||
@ -85,7 +85,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void disconnect() async {
|
void _disconnect() async {
|
||||||
if (await showDisconnectDialog() != true) return;
|
if (await showDisconnectDialog() != true) return;
|
||||||
|
|
||||||
final ChatCallProvider provider = Get.find();
|
final ChatCallProvider provider = Get.find();
|
||||||
@ -95,59 +95,59 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void disableAudio() async {
|
void _disableAudio() async {
|
||||||
await participant.setMicrophoneEnabled(false);
|
await _participant.setMicrophoneEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void enableAudio() async {
|
void _enableAudio() async {
|
||||||
await participant.setMicrophoneEnabled(true);
|
await _participant.setMicrophoneEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void disableVideo() async {
|
void _disableVideo() async {
|
||||||
await participant.setCameraEnabled(false);
|
await _participant.setCameraEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void enableVideo() async {
|
void _enableVideo() async {
|
||||||
await participant.setCameraEnabled(true);
|
await _participant.setCameraEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectAudioOutput(MediaDevice device) async {
|
void _selectAudioOutput(MediaDevice device) async {
|
||||||
await widget.room.setAudioOutputDevice(device);
|
await widget.room.setAudioOutputDevice(device);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectAudioInput(MediaDevice device) async {
|
void _selectAudioInput(MediaDevice device) async {
|
||||||
await widget.room.setAudioInputDevice(device);
|
await widget.room.setAudioInputDevice(device);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectVideoInput(MediaDevice device) async {
|
void _selectVideoInput(MediaDevice device) async {
|
||||||
await widget.room.setVideoInputDevice(device);
|
await widget.room.setVideoInputDevice(device);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSpeakerphoneOn() {
|
void _setSpeakerphoneOn() {
|
||||||
_speakerphoneOn = !_speakerphoneOn;
|
_speakerphoneOn = !_speakerphoneOn;
|
||||||
Hardware.instance.setSpeakerphoneOn(_speakerphoneOn);
|
Hardware.instance.setSpeakerphoneOn(_speakerphoneOn);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final newPosition = position.switched();
|
final newPosition = _position.switched();
|
||||||
await track.setCameraPosition(newPosition);
|
await track.setCameraPosition(newPosition);
|
||||||
setState(() {
|
setState(() {
|
||||||
position = newPosition;
|
_position = newPosition;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void enableScreenShare() async {
|
void _enableScreenShare() async {
|
||||||
if (lkPlatformIsDesktop()) {
|
if (lkPlatformIsDesktop()) {
|
||||||
try {
|
try {
|
||||||
final source = await showDialog<DesktopCapturerSource>(
|
final source = await showDialog<DesktopCapturerSource>(
|
||||||
@ -163,7 +163,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
maxFrameRate: 15.0,
|
maxFrameRate: 15.0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await participant.publishVideoTrack(track);
|
await _participant.publishVideoTrack(track);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final message = e.toString();
|
final message = e.toString();
|
||||||
context.showErrorDialog(message);
|
context.showErrorDialog(message);
|
||||||
@ -177,7 +177,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
maxFrameRate: 30.0,
|
maxFrameRate: 30.0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await participant.publishVideoTrack(track);
|
await _participant.publishVideoTrack(track);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,11 +188,11 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await participant.setScreenShareEnabled(true, captureScreenAudio: true);
|
await _participant.setScreenShareEnabled(true, captureScreenAudio: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void disableScreenShare() async {
|
void _disableScreenShare() async {
|
||||||
await participant.setScreenShareEnabled(false);
|
await _participant.setScreenShareEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -210,12 +210,12 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
icon: Transform.flip(
|
icon: Transform.flip(
|
||||||
flipX: true, child: const Icon(Icons.exit_to_app)),
|
flipX: true, child: const Icon(Icons.exit_to_app)),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
onPressed: disconnect,
|
onPressed: _disconnect,
|
||||||
),
|
),
|
||||||
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),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
tooltip: 'callMicrophoneOff'.tr,
|
tooltip: 'callMicrophoneOff'.tr,
|
||||||
@ -227,7 +227,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
return [
|
return [
|
||||||
PopupMenuItem<MediaDevice>(
|
PopupMenuItem<MediaDevice>(
|
||||||
value: null,
|
value: null,
|
||||||
onTap: isMuted ? enableAudio : disableAudio,
|
onTap: isMuted ? _enableAudio : _disableAudio,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.mic_off),
|
leading: const Icon(Icons.mic_off),
|
||||||
title: Text(isMuted
|
title: Text(isMuted
|
||||||
@ -246,7 +246,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
: const Icon(Icons.check_box_outline_blank),
|
: const Icon(Icons.check_box_outline_blank),
|
||||||
title: Text(device.label),
|
title: Text(device.label),
|
||||||
),
|
),
|
||||||
onTap: () => selectAudioInput(device),
|
onTap: () => _selectAudioInput(device),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@ -254,19 +254,19 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: enableAudio,
|
onPressed: _enableAudio,
|
||||||
icon: const Icon(Icons.mic_off),
|
icon: const Icon(Icons.mic_off),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
tooltip: 'callMicrophoneOn'.tr,
|
tooltip: 'callMicrophoneOn'.tr,
|
||||||
),
|
),
|
||||||
if (participant.isCameraEnabled())
|
if (_participant.isCameraEnabled())
|
||||||
PopupMenuButton<MediaDevice>(
|
PopupMenuButton<MediaDevice>(
|
||||||
icon: const Icon(Icons.videocam_sharp),
|
icon: const Icon(Icons.videocam_sharp),
|
||||||
itemBuilder: (BuildContext context) {
|
itemBuilder: (BuildContext context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem<MediaDevice>(
|
PopupMenuItem<MediaDevice>(
|
||||||
value: null,
|
value: null,
|
||||||
onTap: disableVideo,
|
onTap: _disableVideo,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.videocam_off),
|
leading: const Icon(Icons.videocam_off),
|
||||||
title: Text('callCameraOff'.tr),
|
title: Text('callCameraOff'.tr),
|
||||||
@ -283,7 +283,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
: const Icon(Icons.check_box_outline_blank),
|
: const Icon(Icons.check_box_outline_blank),
|
||||||
title: Text(device.label),
|
title: Text(device.label),
|
||||||
),
|
),
|
||||||
onTap: () => selectVideoInput(device),
|
onTap: () => _selectVideoInput(device),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@ -291,17 +291,17 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: enableVideo,
|
onPressed: _enableVideo,
|
||||||
icon: const Icon(Icons.videocam_off),
|
icon: const Icon(Icons.videocam_off),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
tooltip: 'callCameraOn'.tr,
|
tooltip: 'callCameraOn'.tr,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(position == CameraPosition.back
|
icon: Icon(_position == CameraPosition.back
|
||||||
? Icons.video_camera_back
|
? Icons.video_camera_back
|
||||||
: Icons.video_camera_front),
|
: Icons.video_camera_front),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
onPressed: () => toggleCamera(),
|
onPressed: () => _toggleCamera(),
|
||||||
tooltip: 'callVideoFlip'.tr,
|
tooltip: 'callVideoFlip'.tr,
|
||||||
),
|
),
|
||||||
if (!lkPlatformIs(PlatformType.iOS))
|
if (!lkPlatformIs(PlatformType.iOS))
|
||||||
@ -327,7 +327,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
: const Icon(Icons.check_box_outline_blank),
|
: const Icon(Icons.check_box_outline_blank),
|
||||||
title: Text(device.label),
|
title: Text(device.label),
|
||||||
),
|
),
|
||||||
onTap: () => selectAudioOutput(device),
|
onTap: () => _selectAudioOutput(device),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@ -336,7 +336,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
if (!kIsWeb && lkPlatformIs(PlatformType.iOS))
|
if (!kIsWeb && lkPlatformIs(PlatformType.iOS))
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: Hardware.instance.canSwitchSpeakerphone
|
onPressed: Hardware.instance.canSwitchSpeakerphone
|
||||||
? setSpeakerphoneOn
|
? _setSpeakerphoneOn
|
||||||
: null,
|
: null,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@ -344,18 +344,18 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
|||||||
),
|
),
|
||||||
tooltip: 'callSpeakerphoneToggle'.tr,
|
tooltip: 'callSpeakerphoneToggle'.tr,
|
||||||
),
|
),
|
||||||
if (participant.isScreenShareEnabled())
|
if (_participant.isScreenShareEnabled())
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.monitor_outlined),
|
icon: const Icon(Icons.monitor_outlined),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
onPressed: () => disableScreenShare(),
|
onPressed: () => _disableScreenShare(),
|
||||||
tooltip: 'callScreenOff'.tr,
|
tooltip: 'callScreenOff'.tr,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.monitor),
|
icon: const Icon(Icons.monitor),
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
onPressed: () => enableScreenShare(),
|
onPressed: () => _enableScreenShare(),
|
||||||
tooltip: 'callScreenOn'.tr,
|
tooltip: 'callScreenOn'.tr,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -219,7 +219,7 @@ class InteractiveParticipantWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
@ -50,7 +50,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
|
@ -68,6 +68,7 @@ dependencies:
|
|||||||
markdown_toolbar: ^0.5.0
|
markdown_toolbar: ^0.5.0
|
||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
avatar_stack: ^1.2.0
|
avatar_stack: ^1.2.0
|
||||||
|
async: ^2.11.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user