Solian/lib/screens/chat/call.dart
2024-04-30 20:31:54 +08:00

183 lines
5.4 KiB
Dart

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