539 lines
19 KiB
Dart
539 lines
19 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
|
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:yumi/app/constants/sc_screen.dart';
|
|
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
|
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
|
|
|
import '../../../shared/data_sources/models/enum/sc_room_special_mike_type.dart';
|
|
|
|
///麦位
|
|
class SCSeatItem extends StatefulWidget {
|
|
final num index;
|
|
final bool isGameModel;
|
|
|
|
const SCSeatItem({Key? key, required this.index, this.isGameModel = false})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_SCSeatItemState createState() => _SCSeatItemState();
|
|
}
|
|
|
|
class _SCSeatItemState extends State<SCSeatItem> with TickerProviderStateMixin {
|
|
RtcProvider? provider;
|
|
JoinRoomRes? room;
|
|
MicRes? roomSeat;
|
|
final GlobalKey _targetKey = GlobalKey();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
provider = Provider.of<RtcProvider>(context, listen: false);
|
|
provider?.bindTargetKey(widget.index, _targetKey);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
roomSeat = provider?.roomWheatMap[widget.index];
|
|
room = provider?.currenRoom;
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Column(
|
|
children: [
|
|
SizedBox(
|
|
key: _targetKey,
|
|
width: widget.isGameModel ? 40.w : 55.w,
|
|
height: widget.isGameModel ? 40.w : 55.w,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Sonic(
|
|
index: widget.index,
|
|
room: room,
|
|
isGameModel: widget.isGameModel,
|
|
),
|
|
roomSeat?.user != null
|
|
? head(
|
|
url: roomSeat?.user?.userAvatar ?? "",
|
|
width: widget.isGameModel ? 40.w : 55.w,
|
|
headdress:
|
|
SCPathUtils.getFileExtension(
|
|
roomSeat?.user
|
|
?.getHeaddress()
|
|
?.sourceUrl ??
|
|
"",
|
|
).toLowerCase() ==
|
|
".mp4" &&
|
|
window.locale.languageCode == "ar"
|
|
? ""
|
|
: roomSeat?.user?.getHeaddress()?.sourceUrl,
|
|
)
|
|
: (roomSeat!.micLock!
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_seat_lock.png",
|
|
width: widget.isGameModel ? 38.w : 52.w,
|
|
height: widget.isGameModel ? 38.w : 52.w,
|
|
)
|
|
: Image.asset(
|
|
"sc_images/room/sc_icon_seat_open.png",
|
|
width: widget.isGameModel ? 38.w : 52.w,
|
|
height: widget.isGameModel ? 38.w : 52.w,
|
|
)),
|
|
Positioned(
|
|
bottom: widget.isGameModel ? 2.w : 5.w,
|
|
right: widget.isGameModel ? 2.w : 5.w,
|
|
child:
|
|
roomSeat!.micMute!
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_room_seat_mic_mute.png",
|
|
width: 14.w,
|
|
height: 14.w,
|
|
)
|
|
: Container(),
|
|
),
|
|
IgnorePointer(
|
|
child: Emoticons(
|
|
index: widget.index,
|
|
isGameModel: widget.isGameModel,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
widget.isGameModel
|
|
? Container()
|
|
: (roomSeat?.user != null
|
|
? Container(
|
|
width: 64.w,
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// 角色标签
|
|
msgRoleTag(
|
|
roomSeat?.user?.roles ?? "",
|
|
width: 15.w,
|
|
height: 15.w,
|
|
),
|
|
// 用户名文本
|
|
Flexible(
|
|
child: socialchatNickNameText(
|
|
fontWeight: FontWeight.w600,
|
|
roomSeat?.user?.userNickname ?? "",
|
|
fontSize: 10.sp,
|
|
type: roomSeat?.user?.getVIP()?.name ?? "",
|
|
needScroll:
|
|
(roomSeat
|
|
?.user
|
|
?.userNickname
|
|
?.characters
|
|
.length ??
|
|
0) >
|
|
8,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
SizedBox(height: 16.w),
|
|
text(
|
|
"NO.${widget.index}",
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
],
|
|
)),
|
|
widget.isGameModel
|
|
? Container()
|
|
: (room?.roomProfile?.roomSetting?.showHeartbeat == true
|
|
? Container(
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: 1.w,
|
|
horizontal: 5.w,
|
|
),
|
|
margin: EdgeInsets.symmetric(horizontal: 5.w),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Image.asset(
|
|
"sc_images/room/sc_icon_gift_heartbeat.png",
|
|
width: 13.w,
|
|
),
|
|
SizedBox(width: 3.w),
|
|
text(
|
|
_heartbeatVaFormat(),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 10.sp,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: Container()),
|
|
],
|
|
),
|
|
onTap: () {
|
|
Provider.of<RtcProvider>(
|
|
context,
|
|
listen: false,
|
|
).clickSite(widget.index);
|
|
},
|
|
);
|
|
}
|
|
|
|
String _heartbeatVaFormat() {
|
|
int value = (roomSeat?.user?.heartbeatVal ?? 0).toInt();
|
|
if (value > 99999) {
|
|
return "${(value / 1000).toStringAsFixed(0)}k";
|
|
}
|
|
return "$value";
|
|
}
|
|
}
|
|
|
|
class Emoticons extends StatefulWidget {
|
|
final num index;
|
|
final bool isGameModel;
|
|
|
|
const Emoticons({Key? key, required this.index, this.isGameModel = false})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_EmoticonsState createState() => _EmoticonsState();
|
|
}
|
|
|
|
class _EmoticonsState extends State<Emoticons> with TickerProviderStateMixin {
|
|
late AnimationController emoticonsAniCtr;
|
|
late CurvedAnimation curvedAnimation;
|
|
|
|
bool showIn = false;
|
|
MicRes? playingRes;
|
|
|
|
List<MicRes?> pathList = [];
|
|
String? giftPath;
|
|
List<String> giftList = [];
|
|
bool showResult = false;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
emoticonsAniCtr = AnimationController(
|
|
vsync: this,
|
|
duration: Duration(milliseconds: 350),
|
|
);
|
|
curvedAnimation = CurvedAnimation(
|
|
curve: Curves.ease,
|
|
parent: emoticonsAniCtr,
|
|
);
|
|
emoticonsAniCtr.addStatusListener((status) {
|
|
if (status == AnimationStatus.dismissed) {
|
|
_checkStart();
|
|
}
|
|
});
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Consumer<RtcProvider>(
|
|
builder: (BuildContext context, RtcProvider provider, Widget? child) {
|
|
MicRes? micRes = provider.roomWheatMap[widget.index];
|
|
if (micRes?.user == null) {
|
|
return Container();
|
|
}
|
|
String? emojiPath = provider.roomWheatMap[widget.index]?.emojiPath;
|
|
if (emojiPath != null) {
|
|
pathList.add(micRes);
|
|
micRes?.setEmojiPath = null;
|
|
_checkStart();
|
|
}
|
|
if (playingRes != null) {
|
|
return FadeTransition(
|
|
opacity: curvedAnimation,
|
|
child:
|
|
playingRes?.type == SCRoomMsgType.roomDice
|
|
? FutureBuilder<void>(
|
|
future: Future.delayed(Duration(milliseconds: 2000)),
|
|
// 传入Future
|
|
builder: (
|
|
BuildContext context,
|
|
AsyncSnapshot<void> snapshot,
|
|
) {
|
|
// 根据snapshot的状态来构建UI
|
|
if (snapshot.connectionState == ConnectionState.done) {
|
|
return Image.asset(
|
|
"sc_images/room/sc_icon_dice_${micRes?.number}.png",
|
|
height: widget.isGameModel ? 38.w : 45.w,
|
|
);
|
|
} else {
|
|
return Image.asset(
|
|
"sc_images/room/sc_icon_dice_animl.webp",
|
|
height: widget.isGameModel ? 38.w : 45.w,
|
|
);
|
|
}
|
|
},
|
|
)
|
|
: (playingRes?.type == SCRoomMsgType.roomRPS
|
|
? FutureBuilder<void>(
|
|
future: Future.delayed(Duration(milliseconds: 2000)),
|
|
// 传入Future
|
|
builder: (
|
|
BuildContext context,
|
|
AsyncSnapshot<void> snapshot,
|
|
) {
|
|
// 根据snapshot的状态来构建UI
|
|
if (snapshot.connectionState ==
|
|
ConnectionState.done) {
|
|
return Image.asset(
|
|
"sc_images/room/sc_icon_rps_${micRes?.number}.png",
|
|
height: widget.isGameModel ? 38.w : 45.w,
|
|
);
|
|
} else {
|
|
return Image.asset(
|
|
"sc_images/room/sc_icon_rps_animal.webp",
|
|
height: widget.isGameModel ? 38.w : 45.w,
|
|
);
|
|
}
|
|
},
|
|
)
|
|
: netImage(
|
|
url: playingRes?.number ?? "",
|
|
width: widget.isGameModel ? 65.w : 75.w,
|
|
)),
|
|
);
|
|
} else if (giftPath != null) {
|
|
return FadeTransition(
|
|
opacity: curvedAnimation,
|
|
child: netImage(
|
|
url: "$giftPath",
|
|
width: widget.isGameModel ? 65.w : 75.w,
|
|
borderRadius: BorderRadius.circular(4.w),
|
|
),
|
|
);
|
|
}
|
|
return Container();
|
|
},
|
|
);
|
|
}
|
|
|
|
void _checkStart() {
|
|
print('emoticonsAniCtr.status:${emoticonsAniCtr.status}');
|
|
print('emoticonsAniCtr.path:${playingRes}');
|
|
if (!showIn) {
|
|
if (pathList.isNotEmpty) {
|
|
playingRes = pathList.first;
|
|
pathList.removeAt(0);
|
|
print('播放表情:$playingRes');
|
|
emoticonsAniCtr.forward();
|
|
Future.delayed(Duration(milliseconds: 5)).then((value) {
|
|
setState(() {
|
|
showIn = true;
|
|
});
|
|
});
|
|
|
|
/// 延迟3秒移除表情包
|
|
Future.delayed(Duration(seconds: 3)).then((value) {
|
|
emoticonsAniCtr.reverse();
|
|
showIn = false;
|
|
playingRes = null;
|
|
_checkStart();
|
|
});
|
|
} else if (giftList.isNotEmpty) {
|
|
giftPath = giftList.first;
|
|
giftList.removeAt(0);
|
|
print('播放礼物:$playingRes');
|
|
emoticonsAniCtr.forward();
|
|
Future.delayed(Duration(milliseconds: 10)).then((value) {
|
|
setState(() {
|
|
showIn = true;
|
|
});
|
|
});
|
|
|
|
/// 延迟3秒移除表情包
|
|
Future.delayed(Duration(seconds: 1)).then((value) {
|
|
emoticonsAniCtr.reverse();
|
|
showIn = false;
|
|
giftPath = null;
|
|
_checkStart();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Sonic extends StatefulWidget {
|
|
final num index;
|
|
final bool isGameModel;
|
|
final JoinRoomRes? room;
|
|
|
|
const Sonic({
|
|
Key? key,
|
|
required this.index,
|
|
required this.room,
|
|
this.isGameModel = false,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_SonicState createState() => _SonicState();
|
|
}
|
|
|
|
class _SonicState extends State<Sonic> with TickerProviderStateMixin {
|
|
List<AnimationController> ctrList = [];
|
|
int lastIndex = 0;
|
|
late RtcProvider provider;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
super.initState();
|
|
for (int i = 0; i < 4; i++) {
|
|
ctrList.add(
|
|
AnimationController(duration: Duration(milliseconds: 900), vsync: this),
|
|
);
|
|
}
|
|
for (var element in ctrList) {
|
|
element.addStatusListener((status) {
|
|
if (status == AnimationStatus.completed) {
|
|
element.reset();
|
|
}
|
|
});
|
|
}
|
|
provider = Provider.of<RtcProvider>(context, listen: false);
|
|
|
|
provider.addSoundVoiceChangeListener(_onVoiceChange);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// TODO: implement dispose
|
|
provider.removeSoundVoiceChangeListener(_onVoiceChange);
|
|
for (var element in ctrList) {
|
|
element.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
SonicItem(ctrList[0], widget.room, isGameModel: widget.isGameModel),
|
|
SonicItem(ctrList[1], widget.room, isGameModel: widget.isGameModel),
|
|
SonicItem(ctrList[2], widget.room, isGameModel: widget.isGameModel),
|
|
SonicItem(ctrList[3], widget.room, isGameModel: widget.isGameModel),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _checkSoundAni(int volume) async {
|
|
if (volume <= 20) return;
|
|
// await Future.delayed(Duration(milliseconds: 10));
|
|
// var dateTime = DateTime.now();
|
|
// for(int i =0 ; i < 5000000;i++){
|
|
// lastIndex = 1;
|
|
// }
|
|
// print('执行耗时:${DateTime.now().millisecondsSinceEpoch - dateTime.millisecondsSinceEpoch}');
|
|
ctrList.forEach((element) {
|
|
if (element.value == 0) {
|
|
if (lastIndex == 0) {
|
|
element.forward();
|
|
lastIndex = ctrList.indexOf(element);
|
|
return;
|
|
} else {
|
|
if (ctrList[lastIndex].value >= 0.35 ||
|
|
ctrList[lastIndex].status == AnimationStatus.dismissed) {
|
|
element.forward();
|
|
lastIndex = ctrList.indexOf(element);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
_onVoiceChange(num index, int volum) {
|
|
print('说话声音:$volum');
|
|
if (widget.index == index) {
|
|
_checkSoundAni(volum);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SonicItem extends AnimatedWidget {
|
|
Animation<double> animation;
|
|
JoinRoomRes? room;
|
|
bool isGameModel = false;
|
|
|
|
SonicItem(this.animation, this.room, {Key? key, this.isGameModel = false})
|
|
: super(listenable: animation);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Visibility(
|
|
visible: animation!.value > 0,
|
|
child: ScaleTransition(
|
|
scale: Tween(begin: 1.0, end: 1.4).animate(animation!),
|
|
child: Container(
|
|
height: width(isGameModel ? 34 : 52),
|
|
width: width(isGameModel ? 34 : 52),
|
|
decoration:
|
|
(room?.roomProfile?.roomSetting?.roomSpecialMikeType ?? "")
|
|
.isEmpty ||
|
|
(room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
|
|
SCRoomSpecialMikeType.TYPE_VIP3.name &&
|
|
room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
|
|
SCRoomSpecialMikeType.TYPE_VIP4.name &&
|
|
room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
|
|
SCRoomSpecialMikeType.TYPE_VIP5.name &&
|
|
room?.roomProfile?.roomSetting?.roomSpecialMikeType !=
|
|
SCRoomSpecialMikeType.TYPE_VIP6.name)
|
|
? BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: SocialChatTheme.primaryColor.withOpacity(
|
|
1 - animation!.value,
|
|
),
|
|
)
|
|
: BoxDecoration(),
|
|
child:
|
|
room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
|
|
SCRoomSpecialMikeType.TYPE_VIP3.name
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_room_vip3_sonic_anim.webp",
|
|
)
|
|
: (room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
|
|
SCRoomSpecialMikeType.TYPE_VIP4.name
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_room_vip4_sonic_anim.webp",
|
|
)
|
|
: (room?.roomProfile?.roomSetting?.roomSpecialMikeType ==
|
|
SCRoomSpecialMikeType.TYPE_VIP5.name
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_room_vip5_sonic_anim.webp",
|
|
)
|
|
: (room
|
|
?.roomProfile
|
|
?.roomSetting
|
|
?.roomSpecialMikeType ==
|
|
SCRoomSpecialMikeType.TYPE_VIP6.name
|
|
? Image.asset(
|
|
"sc_images/room/sc_icon_room_vip6_sonic_anim.webp",
|
|
)
|
|
: Container()))),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|