✨ Optimized UI of call actions
This commit is contained in:
parent
1e4fda7daa
commit
e665b44507
@ -101,8 +101,9 @@ class NotifyProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearNonRealtime() {
|
void clearRealtime() {
|
||||||
notifications = notifications.where((x) => !x.isRealtime).toList();
|
notifications = notifications.where((x) => !x.isRealtime).toList();
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void allRead() {
|
void allRead() {
|
||||||
|
@ -11,6 +11,7 @@ import 'package:solian/utils/service_url.dart';
|
|||||||
import 'package:solian/widgets/chat/call/controls.dart';
|
import 'package:solian/widgets/chat/call/controls.dart';
|
||||||
import 'package:solian/widgets/chat/call/exts.dart';
|
import 'package:solian/widgets/chat/call/exts.dart';
|
||||||
import 'package:solian/widgets/chat/call/participant.dart';
|
import 'package:solian/widgets/chat/call/participant.dart';
|
||||||
|
import 'package:solian/widgets/chat/call/participant_menu.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';
|
||||||
@ -49,9 +50,10 @@ class _ChatCallState extends State<ChatCall> {
|
|||||||
late EventsListener<RoomEvent> _callListener;
|
late EventsListener<RoomEvent> _callListener;
|
||||||
|
|
||||||
List<ParticipantTrack> _participantTracks = [];
|
List<ParticipantTrack> _participantTracks = [];
|
||||||
|
ParticipantTrack? _focusParticipant;
|
||||||
|
|
||||||
Future<void> checkPermissions() async {
|
Future<void> checkPermissions() async {
|
||||||
if(lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return;
|
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) return;
|
||||||
|
|
||||||
await Permission.camera.request();
|
await Permission.camera.request();
|
||||||
await Permission.microphone.request();
|
await Permission.microphone.request();
|
||||||
@ -275,6 +277,7 @@ class _ChatCallState extends State<ChatCall> {
|
|||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_participantTracks = [...screenTracks, localTrack, ...userMediaTrackList];
|
_participantTracks = [...screenTracks, localTrack, ...userMediaTrackList];
|
||||||
|
_focusParticipant ??= _participantTracks.first;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,8 +389,11 @@ class _ChatCallState extends State<ChatCall> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
child: _participantTracks.isNotEmpty
|
child: _focusParticipant != null
|
||||||
? ParticipantWidget.widgetFor(_participantTracks.first)
|
? InteractiveParticipantWidget(
|
||||||
|
participant: _focusParticipant!,
|
||||||
|
onTap: () {},
|
||||||
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -399,22 +405,34 @@ class _ChatCallState extends State<ChatCall> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 120 + 16,
|
height: 128,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: math.max(0, _participantTracks.length - 1),
|
itemCount: math.max(0, _participantTracks.length),
|
||||||
itemBuilder: (BuildContext context, int index) => Padding(
|
itemBuilder: (BuildContext context, int index) {
|
||||||
padding: const EdgeInsets.all(8.0),
|
final track = _participantTracks[index];
|
||||||
child: ClipRRect(
|
if (track.participant.sid == _focusParticipant?.participant.sid) {
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
return Container();
|
||||||
child: Container(
|
}
|
||||||
width: 120,
|
|
||||||
height: 120,
|
return Padding(
|
||||||
color: Theme.of(context).cardColor,
|
padding: const EdgeInsets.only(top: 8, left: 8),
|
||||||
child: ParticipantWidget.widgetFor(_participantTracks[index + 1]),
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: InteractiveParticipantWidget(
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
|
participant: track,
|
||||||
|
onTap: () {
|
||||||
|
if (track.participant.sid != _focusParticipant?.participant.sid) {
|
||||||
|
setState(() => _focusParticipant = track);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -443,3 +461,44 @@ class _ChatCallState extends State<ChatCall> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InteractiveParticipantWidget extends StatelessWidget {
|
||||||
|
final double? width;
|
||||||
|
final double? height;
|
||||||
|
final Color? color;
|
||||||
|
final ParticipantTrack participant;
|
||||||
|
final Function() onTap;
|
||||||
|
|
||||||
|
const InteractiveParticipantWidget({
|
||||||
|
super.key,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.color,
|
||||||
|
required this.participant,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
color: color,
|
||||||
|
child: ParticipantWidget.widgetFor(participant),
|
||||||
|
),
|
||||||
|
onTap: () => onTap(),
|
||||||
|
onLongPress: () {
|
||||||
|
if (participant.participant is LocalParticipant) return;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ParticipantMenu(
|
||||||
|
participant: participant.participant as RemoteParticipant,
|
||||||
|
videoTrack: participant.videoTrack,
|
||||||
|
isScreenShare: participant.isScreenShare,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,8 +21,6 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
final nty = context.watch<NotifyProvider>();
|
final nty = context.watch<NotifyProvider>();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => nty.allRead());
|
|
||||||
|
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
noSafeArea: true,
|
noSafeArea: true,
|
||||||
hideDrawer: true,
|
hideDrawer: true,
|
||||||
@ -71,6 +69,14 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
final nty = context.read<NotifyProvider>();
|
||||||
|
nty.allRead();
|
||||||
|
nty.clearRealtime();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationItem extends StatelessWidget {
|
class NotificationItem extends StatelessWidget {
|
||||||
|
@ -60,6 +60,7 @@ class _NoContentWidgetState extends State<NoContentWidget> with SingleTickerProv
|
|||||||
],
|
],
|
||||||
child: AccountAvatar(
|
child: AccountAvatar(
|
||||||
source: widget.userinfo!.avatar,
|
source: widget.userinfo!.avatar,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
direct: true,
|
direct: true,
|
||||||
)),
|
)),
|
||||||
|
@ -78,8 +78,6 @@ class RemoteParticipantWidget extends ParticipantWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends State<T> {
|
abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends State<T> {
|
||||||
bool _visible = true;
|
|
||||||
|
|
||||||
VideoTrack? get _activeVideoTrack;
|
VideoTrack? get _activeVideoTrack;
|
||||||
|
|
||||||
TrackPublication? get _videoPublication;
|
TrackPublication? get _videoPublication;
|
||||||
@ -117,24 +115,19 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> extraWidgets(bool isScreenShare) => [];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext ctx) {
|
Widget build(BuildContext ctx) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
_activeVideoTrack != null && !_activeVideoTrack!.muted
|
||||||
onTap: () => setState(() => _visible = !_visible),
|
? VideoTrackRenderer(
|
||||||
child: _activeVideoTrack != null && !_activeVideoTrack!.muted
|
_activeVideoTrack!,
|
||||||
? VideoTrackRenderer(
|
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
||||||
_activeVideoTrack!,
|
)
|
||||||
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
: NoContentWidget(
|
||||||
)
|
userinfo: _userinfoMetadata,
|
||||||
: NoContentWidget(
|
isSpeaking: widget.participant.isSpeaking,
|
||||||
userinfo: _userinfoMetadata,
|
),
|
||||||
isSpeaking: widget.participant.isSpeaking,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.showStatsLayer)
|
if (widget.showStatsLayer)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 30,
|
top: 30,
|
||||||
@ -149,10 +142,9 @@ abstract class _ParticipantWidgetState<T extends ParticipantWidget> extends Stat
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
...extraWidgets(widget.isScreenShare),
|
|
||||||
ParticipantInfoWidget(
|
ParticipantInfoWidget(
|
||||||
title: widget.participant.name.isNotEmpty
|
title: widget.participant.name.isNotEmpty
|
||||||
? '${widget.participant.name} (${widget.participant.identity})'
|
? widget.participant.name
|
||||||
: widget.participant.identity,
|
: widget.participant.identity,
|
||||||
audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true,
|
audioAvailable: _firstAudioPublication?.muted == false && _firstAudioPublication?.subscribed == true,
|
||||||
connectionQuality: widget.participant.connectionQuality,
|
connectionQuality: widget.participant.connectionQuality,
|
||||||
@ -190,143 +182,4 @@ class _RemoteParticipantWidgetState extends _ParticipantWidgetState<RemotePartic
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
VideoTrack? get _activeVideoTrack => widget.videoTrack;
|
VideoTrack? get _activeVideoTrack => widget.videoTrack;
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> extraWidgets(bool isScreenShare) => [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
// Menu for RemoteTrackPublication<RemoteAudioTrack>
|
|
||||||
if (_firstAudioPublication != null && !isScreenShare)
|
|
||||||
RemoteTrackPublicationMenuWidget(
|
|
||||||
pub: _firstAudioPublication!,
|
|
||||||
icon: Icons.volume_up,
|
|
||||||
),
|
|
||||||
if (_videoPublication != null)
|
|
||||||
RemoteTrackPublicationMenuWidget(
|
|
||||||
pub: _videoPublication!,
|
|
||||||
icon: isScreenShare ? Icons.monitor : Icons.videocam,
|
|
||||||
),
|
|
||||||
if (_videoPublication != null)
|
|
||||||
RemoteTrackFPSMenuWidget(
|
|
||||||
pub: _videoPublication!,
|
|
||||||
icon: Icons.menu,
|
|
||||||
),
|
|
||||||
if (_videoPublication != null)
|
|
||||||
RemoteTrackQualityMenuWidget(
|
|
||||||
pub: _videoPublication!,
|
|
||||||
icon: Icons.monitor_outlined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
class RemoteTrackPublicationMenuWidget extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final RemoteTrackPublication pub;
|
|
||||||
|
|
||||||
const RemoteTrackPublicationMenuWidget({
|
|
||||||
required this.pub,
|
|
||||||
required this.icon,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Material(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
|
|
||||||
child: PopupMenuButton<Function>(
|
|
||||||
tooltip: 'Subscribe menu',
|
|
||||||
icon: Icon(icon,
|
|
||||||
color: {
|
|
||||||
TrackSubscriptionState.notAllowed: Colors.red,
|
|
||||||
TrackSubscriptionState.unsubscribed: Colors.grey,
|
|
||||||
TrackSubscriptionState.subscribed: Colors.green,
|
|
||||||
}[pub.subscriptionState]),
|
|
||||||
onSelected: (value) => value(),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<Function>>[
|
|
||||||
if (pub.subscribed == false)
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('Subscribe'),
|
|
||||||
value: () => pub.subscribe(),
|
|
||||||
)
|
|
||||||
else if (pub.subscribed == true)
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('Un-subscribe'),
|
|
||||||
value: () => pub.unsubscribe(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RemoteTrackFPSMenuWidget extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final RemoteTrackPublication pub;
|
|
||||||
|
|
||||||
const RemoteTrackFPSMenuWidget({
|
|
||||||
required this.pub,
|
|
||||||
required this.icon,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Material(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
child: PopupMenuButton<Function>(
|
|
||||||
tooltip: 'Preferred FPS',
|
|
||||||
icon: Icon(icon, color: Colors.white),
|
|
||||||
onSelected: (value) => value(),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<Function>>[
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('30'),
|
|
||||||
value: () => pub.setVideoFPS(30),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('15'),
|
|
||||||
value: () => pub.setVideoFPS(15),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('8'),
|
|
||||||
value: () => pub.setVideoFPS(8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RemoteTrackQualityMenuWidget extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final RemoteTrackPublication pub;
|
|
||||||
|
|
||||||
const RemoteTrackQualityMenuWidget({
|
|
||||||
required this.pub,
|
|
||||||
required this.icon,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Material(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
child: PopupMenuButton<Function>(
|
|
||||||
tooltip: 'Preferred Quality',
|
|
||||||
icon: Icon(icon, color: Colors.white),
|
|
||||||
onSelected: (value) => value(),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<Function>>[
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('HIGH'),
|
|
||||||
value: () => pub.setVideoQuality(VideoQuality.HIGH),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('MEDIUM'),
|
|
||||||
value: () => pub.setVideoQuality(VideoQuality.MEDIUM),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: const Text('LOW'),
|
|
||||||
value: () => pub.setVideoQuality(VideoQuality.LOW),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
148
lib/widgets/chat/call/participant_menu.dart
Normal file
148
lib/widgets/chat/call/participant_menu.dart
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class ParticipantMenu extends StatefulWidget {
|
||||||
|
final RemoteParticipant participant;
|
||||||
|
final VideoTrack? videoTrack;
|
||||||
|
final bool isScreenShare;
|
||||||
|
final bool showStatsLayer;
|
||||||
|
|
||||||
|
const ParticipantMenu({
|
||||||
|
super.key,
|
||||||
|
required this.participant,
|
||||||
|
this.videoTrack,
|
||||||
|
this.isScreenShare = false,
|
||||||
|
this.showStatsLayer = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ParticipantMenu> createState() => _ParticipantMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8, top: 20, bottom: 12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.action,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
if (_firstAudioPublication != null && !widget.isScreenShare)
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.volume_up,
|
||||||
|
color: {
|
||||||
|
TrackSubscriptionState.notAllowed: Theme.of(context).colorScheme.error,
|
||||||
|
TrackSubscriptionState.unsubscribed: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
|
TrackSubscriptionState.subscribed: Theme.of(context).colorScheme.primary,
|
||||||
|
}[_firstAudioPublication!.subscriptionState],
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
_firstAudioPublication!.subscribed
|
||||||
|
? AppLocalizations.of(context)!.chatCallMute
|
||||||
|
: AppLocalizations.of(context)!.chatCallUnMute,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (_firstAudioPublication!.subscribed) {
|
||||||
|
_firstAudioPublication!.unsubscribe();
|
||||||
|
} else {
|
||||||
|
_firstAudioPublication!.subscribe();
|
||||||
|
}
|
||||||
|
tookAction();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_videoPublication != null)
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
widget.isScreenShare ? Icons.monitor : Icons.videocam,
|
||||||
|
color: {
|
||||||
|
TrackSubscriptionState.notAllowed: Theme.of(context).colorScheme.error,
|
||||||
|
TrackSubscriptionState.unsubscribed: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
|
TrackSubscriptionState.subscribed: Theme.of(context).colorScheme.primary,
|
||||||
|
}[_videoPublication!.subscriptionState],
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
_videoPublication!.subscribed
|
||||||
|
? AppLocalizations.of(context)!.chatCallVideoOff
|
||||||
|
: AppLocalizations.of(context)!.chatCallVideoOn,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (_videoPublication!.subscribed) {
|
||||||
|
_videoPublication!.unsubscribe();
|
||||||
|
} else {
|
||||||
|
_videoPublication!.subscribe();
|
||||||
|
}
|
||||||
|
tookAction();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_videoPublication != null) const Divider(thickness: 0.3),
|
||||||
|
if (_videoPublication != null)
|
||||||
|
...[30, 15, 8].map(
|
||||||
|
(x) => ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
_videoPublication?.fps == x ? Icons.check_box_outlined : Icons.check_box_outline_blank,
|
||||||
|
),
|
||||||
|
title: Text('Set preferred frame-per-second to $x'),
|
||||||
|
onTap: () {
|
||||||
|
_videoPublication!.setVideoFPS(x);
|
||||||
|
tookAction();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_videoPublication != null) const Divider(thickness: 0.3),
|
||||||
|
if (_videoPublication != null)
|
||||||
|
...[
|
||||||
|
('High', VideoQuality.HIGH),
|
||||||
|
('Medium', VideoQuality.MEDIUM),
|
||||||
|
('Low', VideoQuality.LOW),
|
||||||
|
].map(
|
||||||
|
(x) => ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
_videoPublication?.videoQuality == x.$2 ? Icons.check_box_outlined : Icons.check_box_outline_blank,
|
||||||
|
),
|
||||||
|
title: Text('Set preferred quality to ${x.$1}'),
|
||||||
|
onTap: () {
|
||||||
|
_videoPublication!.setVideoQuality(x.$2);
|
||||||
|
tookAction();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user