2024-04-26 17:36:54 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-04-27 05:12:26 +00:00
|
|
|
import 'package:livekit_client/livekit_client.dart';
|
2024-04-26 17:36:54 +00:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:solian/models/call.dart';
|
2024-04-30 12:31:54 +00:00
|
|
|
import 'package:solian/providers/chat.dart';
|
2024-04-28 11:36:06 +00:00
|
|
|
import 'package:solian/widgets/chat/call/controls.dart';
|
2024-04-27 05:12:26 +00:00
|
|
|
import 'package:solian/widgets/chat/call/participant.dart';
|
2024-04-28 15:05:59 +00:00
|
|
|
import 'package:solian/widgets/chat/call/participant_menu.dart';
|
2024-04-26 17:36:54 +00:00
|
|
|
import 'package:solian/widgets/indent_wrapper.dart';
|
|
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
2024-04-27 05:12:26 +00:00
|
|
|
import 'dart:math' as math;
|
|
|
|
|
2024-04-26 17:36:54 +00:00
|
|
|
class ChatCall extends StatefulWidget {
|
|
|
|
final Call call;
|
|
|
|
|
|
|
|
const ChatCall({super.key, required this.call});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<ChatCall> createState() => _ChatCallState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ChatCallState extends State<ChatCall> {
|
2024-04-30 12:31:54 +00:00
|
|
|
bool _isHandled = false;
|
2024-04-27 05:12:26 +00:00
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
late ChatProvider _chat;
|
2024-04-28 13:49:03 +00:00
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
ChatCallInstance get _call => _chat.call!;
|
2024-04-27 05:12:26 +00:00
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2024-04-27 16:21:16 +00:00
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
_chat.setShown(true);
|
2024-04-27 05:12:26 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
_chat = context.watch<ChatProvider>();
|
|
|
|
if (!_isHandled) {
|
|
|
|
_isHandled = true;
|
|
|
|
if (_chat.handleCall(widget.call, widget.call.channel)) {
|
|
|
|
_chat.call?.init();
|
2024-04-27 05:12:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-30 12:31:54 +00:00
|
|
|
Widget content;
|
|
|
|
if (_chat.call == null) {
|
|
|
|
content = const Center(
|
|
|
|
child: CircularProgressIndicator(),
|
|
|
|
);
|
2024-04-27 05:12:26 +00:00
|
|
|
} else {
|
2024-04-30 12:31:54 +00:00
|
|
|
content = FutureBuilder(
|
|
|
|
future: _call.exchangeToken(context),
|
2024-04-26 17:36:54 +00:00
|
|
|
builder: (context, snapshot) {
|
|
|
|
if (!snapshot.hasData || snapshot.data == null) {
|
|
|
|
return const Center(child: CircularProgressIndicator());
|
|
|
|
}
|
|
|
|
|
2024-04-27 05:12:26 +00:00
|
|
|
return Stack(
|
|
|
|
children: [
|
|
|
|
Column(
|
|
|
|
children: [
|
|
|
|
Expanded(
|
2024-04-27 16:07:32 +00:00
|
|
|
child: Container(
|
|
|
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
2024-04-30 12:31:54 +00:00
|
|
|
child: _call.focusTrack != null
|
2024-04-28 15:05:59 +00:00
|
|
|
? InteractiveParticipantWidget(
|
2024-04-30 12:31:54 +00:00
|
|
|
isFixed: false,
|
|
|
|
participant: _call.focusTrack!,
|
2024-04-28 15:05:59 +00:00
|
|
|
onTap: () {},
|
|
|
|
)
|
2024-04-27 16:07:32 +00:00
|
|
|
: Container(),
|
|
|
|
),
|
2024-04-27 05:12:26 +00:00
|
|
|
),
|
2024-04-30 12:31:54 +00:00
|
|
|
if (_call.room.localParticipant != null)
|
|
|
|
ControlsWidget(
|
|
|
|
_call.room,
|
|
|
|
_call.room.localParticipant!,
|
|
|
|
),
|
2024-04-27 05:12:26 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
Positioned(
|
|
|
|
left: 0,
|
|
|
|
right: 0,
|
|
|
|
top: 0,
|
|
|
|
child: SizedBox(
|
2024-04-28 15:05:59 +00:00
|
|
|
height: 128,
|
2024-04-27 05:12:26 +00:00
|
|
|
child: ListView.builder(
|
|
|
|
scrollDirection: Axis.horizontal,
|
2024-04-30 12:31:54 +00:00
|
|
|
itemCount: math.max(0, _call.participantTracks.length),
|
2024-04-28 15:05:59 +00:00
|
|
|
itemBuilder: (BuildContext context, int index) {
|
2024-04-30 12:31:54 +00:00
|
|
|
final track = _call.participantTracks[index];
|
|
|
|
if (track.participant.sid == _call.focusTrack?.participant.sid) {
|
2024-04-28 15:05:59 +00:00
|
|
|
return Container();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.only(top: 8, left: 8),
|
|
|
|
child: ClipRRect(
|
|
|
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
|
|
child: InteractiveParticipantWidget(
|
2024-04-30 12:31:54 +00:00
|
|
|
isFixed: true,
|
2024-04-28 15:05:59 +00:00
|
|
|
width: 120,
|
|
|
|
height: 120,
|
|
|
|
color: Theme.of(context).cardColor,
|
|
|
|
participant: track,
|
|
|
|
onTap: () {
|
2024-04-30 12:31:54 +00:00
|
|
|
if (track.participant.sid != _call.focusTrack?.participant.sid) {
|
|
|
|
_call.changeFocusTrack(track);
|
2024-04-28 15:05:59 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
2024-04-27 16:21:16 +00:00
|
|
|
),
|
2024-04-28 15:05:59 +00:00
|
|
|
);
|
|
|
|
},
|
2024-04-27 05:12:26 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
2024-04-26 17:36:54 +00:00
|
|
|
},
|
2024-04-30 12:31:54 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return IndentWrapper(
|
|
|
|
title: AppLocalizations.of(context)!.chatCall,
|
|
|
|
hideDrawer: true,
|
|
|
|
child: content,
|
2024-04-26 17:36:54 +00:00
|
|
|
);
|
|
|
|
}
|
2024-04-27 05:12:26 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void deactivate() {
|
2024-04-30 12:31:54 +00:00
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _chat.setShown(false));
|
2024-04-27 05:12:26 +00:00
|
|
|
super.deactivate();
|
|
|
|
}
|
2024-04-26 17:36:54 +00:00
|
|
|
}
|
2024-04-28 15:05:59 +00:00
|
|
|
|
|
|
|
class InteractiveParticipantWidget extends StatelessWidget {
|
|
|
|
final double? width;
|
|
|
|
final double? height;
|
|
|
|
final Color? color;
|
2024-04-30 12:37:08 +00:00
|
|
|
final bool isFixed;
|
2024-04-28 15:05:59 +00:00
|
|
|
final ParticipantTrack participant;
|
|
|
|
final Function() onTap;
|
|
|
|
|
|
|
|
const InteractiveParticipantWidget({
|
|
|
|
super.key,
|
|
|
|
this.width,
|
|
|
|
this.height,
|
|
|
|
this.color,
|
2024-04-29 12:22:06 +00:00
|
|
|
this.isFixed = false,
|
2024-04-28 15:05:59 +00:00
|
|
|
required this.participant,
|
|
|
|
required this.onTap,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return InkWell(
|
|
|
|
child: Container(
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
color: color,
|
2024-04-30 12:37:08 +00:00
|
|
|
child: ParticipantWidget.widgetFor(participant, isFixed: isFixed),
|
2024-04-28 15:05:59 +00:00
|
|
|
),
|
|
|
|
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,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|