自习室优化ok

This commit is contained in:
zhutao
2025-11-28 13:31:23 +08:00
parent 4ecb0c35d6
commit 57305c5804
57 changed files with 2500 additions and 597 deletions

View File

@@ -1,9 +1,11 @@
import 'package:app/config/theme/base/app_theme_ext.dart';
import 'package:app/widgets/version/version_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'viewmodel/home_view_model.dart';
import 'widgets/header.dart';
import 'widgets/tip_card.dart';
import 'widgets/today_card.dart';
class THomePage extends StatelessWidget {
@@ -11,6 +13,7 @@ class THomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
showUpdateDialog(context);
return ChangeNotifierProvider(
create: (_) => HomeViewModel(),
child: const _HomeView(),
@@ -24,6 +27,7 @@ class _HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final vm = context.read<HomeViewModel>();
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
appBar: Header(),
@@ -36,6 +40,8 @@ class _HomeView extends StatelessWidget {
),
children: [
TodayCard(),
TipCard1(),
TipCard2(),
],
),
),

View File

@@ -1,10 +1,10 @@
import 'package:app/request/api/room_api.dart';
import 'package:app/request/dto/room/room_info_dto.dart';
import 'package:app/request/dto/room/room_list_item_dto.dart';
import 'package:app/utils/time.dart';
import 'package:flutter/material.dart';
class HomeViewModel extends ChangeNotifier {
RoomInfoDto? roomInfo;
RoomListItemDto ? roomInfo;
bool loading = true;
HomeViewModel() {

View File

@@ -0,0 +1,143 @@
import 'package:app/widgets/base/card/g_card.dart';
import 'package:flutter/material.dart';
import 'package:remixicon/remixicon.dart';
class TipCard1 extends StatelessWidget {
const TipCard1({super.key});
@override
Widget build(BuildContext context) {
final list = [
{
"icon": RemixIcons.video_on_line,
"title": "实时视频互动",
"subtitle": "高清视频连接,随时与学生面对面交流",
},
{
"icon": RemixIcons.file_list_line,
"title": "查看学生资料",
"subtitle": "查看学生上传的作业、题目和笔记",
},
{
"icon": RemixIcons.message_line,
"title": "灵活管控",
"subtitle": "一键控制学生的视频、音频状态",
},
{
"icon": RemixIcons.lightbulb_line,
"title": "白板演示",
"subtitle": "开启白板功能,为学生讲解疑难问题",
},
];
return Container(
margin: EdgeInsets.only(top: 15),
child: Column(
spacing: 10,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("核心功能"),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisExtent: 80,
crossAxisSpacing: 15,
mainAxisSpacing: 15,
),
itemBuilder: (_, index) {
final item = list[index] as dynamic;
return GCard(
child: Row(
spacing: 10,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(10),
),
child: Icon(
item["icon"],
color: Colors.white,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(item["title"]),
Text(
item["subtitle"],
style: Theme.of(context).textTheme.labelLarge,
),
],
),
],
),
);
},
itemCount: list.length,
),
],
),
);
}
}
class TipCard2 extends StatelessWidget {
const TipCard2({super.key});
@override
Widget build(BuildContext context) {
final tipList = [
"请确保网络环境良好,保证视频通话质量",
"建议提前5分钟进入自习室准备教学材料",
"合理使用白板功能,帮助学生更好地理解知识点",
"关注每位学生的学习状态,及时提供帮助",
];
return Container(
margin: EdgeInsets.only(top: 15),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Color(0xfffffbeb),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Color(0xfffee685),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(bottom: 10),
child: Text("温馨提示"),
),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
return Row(
spacing: 4,
children: [
Container(
width: 5,
height: 5,
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.black),
),
Text(
tipList[index],
style: Theme.of(context).textTheme.labelLarge,
),
],
);
},
separatorBuilder: (_, __) => SizedBox(height: 3),
itemCount: tipList.length,
),
],
),
);
}
}

View File

@@ -1,11 +1,10 @@
import 'package:app/router/route_paths.dart';
import 'package:app/utils/permission.dart';
import 'package:app/utils/time.dart';
import 'package:app/widgets/base/button/index.dart';
import 'package:app/widgets/base/card/g_card.dart';
import 'package:app/widgets/base/config/config.dart';
import 'package:app/widgets/base/dialog/config_dialog.dart';
import 'package:app/widgets/base/empty/index.dart';
import 'package:app/widgets/room/file_drawer.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:go_router/go_router.dart';
@@ -93,7 +92,7 @@ class _TodayCardState extends State<TodayCard> {
),
_item(
title: "时长",
value: "${vm.roomMinutes} 分钟",
value: "${formatSeconds(vm.roomMinutes * 60, 'hh小时mm分钟')} ",
icon: RemixIcons.book_open_line,
color: Color(0xffac45fd),
),
@@ -106,7 +105,7 @@ class _TodayCardState extends State<TodayCard> {
child: Button(
text: vm.canEnterRoom ? "开始自习室" : "未到开始时间",
type: ThemeType.success,
// disabled: !vm.canEnterRoom,
disabled: !vm.canEnterRoom,
onPressed: _goToRoom,
),
),

View File

@@ -1,43 +1,23 @@
import 'package:app/utils/time.dart';
import 'package:app/widgets/base/button/index.dart';
import 'package:app/widgets/base/config/config.dart';
import 'package:app/widgets/base/dialog/config_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:remixicon/remixicon.dart';
import '../../../../widgets/room/core/count_down_vm.dart';
import '../viewmodel/tch_room_vm.dart';
import '../viewmodel/type.dart';
class TopBar extends StatelessWidget implements PreferredSizeWidget {
const TopBar({super.key});
@override
Widget build(BuildContext context) {
//标题子显示内容
Widget infoItem({required String title, required IconData icon}) {
return Row(
spacing: 4,
children: [
Icon(icon, color: Colors.white54, size: 14),
Text(
title,
style: TextStyle(fontSize: 12, color: Colors.white54),
),
],
);
}
//操作按钮
Widget actionButton({required IconData icon, required String title}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: EdgeInsets.only(right: 15),
decoration: BoxDecoration(
color: Color(0xff4a4f4f),
borderRadius: BorderRadius.circular(8),
),
child: Row(
spacing: 8,
children: [
Icon(icon, size: 16),
Text(title, style: TextStyle(fontSize: 14)),
],
),
);
}
final vm = context.watch<TchRoomVM>();
return AppBar(
backgroundColor: Color(0xff373c3e),
@@ -46,29 +26,144 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
spacing: 5,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("高三数学重置版", style: TextStyle(color: Colors.white, fontSize: 18)),
Text(vm.roomInfo.roomName, style: TextStyle(color: Colors.white, fontSize: 18)),
Row(
spacing: 15,
children: [
infoItem(title: "剩余 1小时23分钟", icon: RemixIcons.time_line),
infoItem(title: "8 名学生", icon: RemixIcons.group_line),
Consumer<CountDownVM>(
builder: (context, countVM, __) {
return _infoItem(
context,
title: "剩余 ${formatSeconds(countVM.endCountDown)}",
icon: RemixIcons.time_line,
);
},
),
_infoItem(
context,
title: "${vm.students.length} 名学生",
icon: RemixIcons.group_line,
),
],
),
],
),
actions: [
actionButton(
_actionButton(
context,
icon: RemixIcons.video_on_ai_line,
title: "关闭全部",
onPressed: () {
_closeAll(context, StudentAction.camera);
},
),
actionButton(
_actionButton(
context,
icon: RemixIcons.volume_up_line,
title: "全部静音",
onPressed: () {
_closeAll(context, StudentAction.speaker);
},
),
Container(
margin: EdgeInsets.only(right: 15),
child: Button(
text: "白板",
textStyle: TextStyle(fontSize: 14),
onPressed: (){},
),
),
Consumer<TchRoomVM>(
builder: (context, vm, _) {
if (vm.roomInfo.roomStatus != 1) {
return SizedBox();
}
return Button(
type: ThemeType.danger,
textStyle: TextStyle(fontSize: 14),
text: "结束自习室",
onPressed: () {
showDialog(
context: context,
builder: (_) {
return ConfigDialog(
content: '是否结束自习室?结束后无法在进入',
onCancel: () {
context.pop();
},
onConfirm: () {
context.pop();
vm.endRoom();
EasyLoading.showToast("会议室已结束");
},
);
},
);
},
);
},
),
SizedBox(width: 10),
],
);
}
Widget _infoItem(BuildContext context, {required String title, required IconData icon}) {
return Row(
children: [
Icon(icon, color: Colors.white54, size: 14),
SizedBox(width: 4),
Text(title, style: TextStyle(fontSize: 12, color: Colors.white54)),
],
);
}
Widget _actionButton(
BuildContext context, {
required IconData icon,
required String title,
required VoidCallback onPressed,
}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: EdgeInsets.only(right: 15),
decoration: BoxDecoration(
color: Color(0xff4a4f4f),
borderRadius: BorderRadius.circular(8),
),
child: InkWell(
onTap: onPressed,
child: Row(
children: [
Icon(icon, size: 16),
SizedBox(width: 8),
Text(title, style: TextStyle(fontSize: 14)),
],
),
),
);
}
void _closeAll(BuildContext context, StudentAction action) {
final vm = context.read<TchRoomVM>();
String content = (action == StudentAction.camera) ? '是否关闭所有学生的摄像头?' : '是否关闭所有学生的扬声器?';
showDialog(
context: context,
builder: (_) {
return ConfigDialog(
content: content,
onCancel: () => context.pop(),
onConfirm: () {
context.pop();
vm.closeAllStudentAction(action);
EasyLoading.showToast("操作已完成");
},
);
},
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

View File

@@ -1,4 +1,5 @@
import 'package:app/request/dto/room/room_info_dto.dart';
import 'package:app/widgets/room/core/count_down_vm.dart';
import 'package:app/request/dto/room/room_list_item_dto.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'controls/top_bar.dart';
@@ -6,7 +7,7 @@ import 'widgets/status_view.dart';
import 'viewmodel/tch_room_vm.dart';
class TRoomPage extends StatefulWidget {
final RoomInfoDto roomInfo;
final RoomListItemDto roomInfo;
const TRoomPage({
super.key,
@@ -20,12 +21,19 @@ class TRoomPage extends StatefulWidget {
class _TRoomPageState extends State<TRoomPage> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TchRoomVM>(
create: (BuildContext context) {
return TchRoomVM(
roomInfo: widget.roomInfo,
);
},
return MultiProvider(
providers: [
ChangeNotifierProvider<TchRoomVM>(
create: (_) => TchRoomVM(info: widget.roomInfo),
),
ChangeNotifierProxyProvider<TchRoomVM, CountDownVM>(
create: (_) => CountDownVM(),
update: (_, tchVM, countDownVM) {
countDownVM!.bind(tchVM.roomInfo);
return countDownVM;
},
),
],
child: Scaffold(
backgroundColor: Color(0xff2c3032),
appBar: TopBar(),

View File

@@ -1,7 +1,8 @@
import 'dart:async';
import 'package:app/data/models/meeting_room_dto.dart';
import 'package:app/request/dto/room/room_list_item_dto.dart';
import 'package:app/request/dto/room/room_info_dto.dart';
import 'package:app/request/dto/room/room_type_dto.dart';
import 'package:app/request/dto/room/room_user_dto.dart';
import 'package:app/request/dto/room/rtc_token_dto.dart';
import 'package:app/request/websocket/room_protocol.dart';
@@ -13,18 +14,17 @@ import 'type.dart';
class TchRoomVM extends ChangeNotifier {
TchRoomVM({
required this.roomInfo,
String? start,
required RoomListItemDto info,
}) {
roomInfo = MeetingRoomDto.fromRoomListItem(info).copyWith(roomStatus: -1);
_startRoom();
}
///学生摄像头列表
List<RoomUserDto> _students = [];
///房间的基础信息
final RoomInfoDto roomInfo;
int roomStatus = -1; // //-1加载中0没开始1进行中2关闭
///房间的基础信息,其中状态-1加载中0没开始1进行中2关闭
late MeetingRoomDto roomInfo;
///老师选中的学生id
int activeSId = 0;
@@ -42,8 +42,6 @@ class TchRoomVM extends ChangeNotifier {
///websocket管理
final RoomWebSocket _ws = RoomWebSocket();
// bool wsConnected = false; // socket连接状态
StreamSubscription<RoomMessage>? _sub;
RtcTokenDto? get rtcToken => _ws.rtcToken;
@@ -61,8 +59,8 @@ class TchRoomVM extends ChangeNotifier {
// 自习室人员变化
if (msg.event == RoomEvent.changeUser) {
final list = RoomUserDto.listFromJson(msg.data['user_list']);
final room = RoomTypeDto.fromJson(msg.data['room_info']);
roomStatus = room.roomStatus;
final room = RoomInfoDto.fromJson(msg.data['room_info']);
_updateRoomInfo(room);
onStudentChange(list);
} else if ([
RoomEvent.openSpeaker,
@@ -74,11 +72,26 @@ class TchRoomVM extends ChangeNotifier {
RoomEvent.handUp,
].contains(msg.event)) {
onSyncStudentItem(RoomUserDto.fromJson(msg.data));
} else if (msg.event == RoomEvent.fileUploadComplete) {
updateStudentFile(
msg.data['user_id'],
(msg.data['flies'] as List).map((e) => e.toString()).toList(),
);
}
});
notifyListeners();
}
///更新房间信息
void _updateRoomInfo(RoomInfoDto info) {
roomInfo = roomInfo.copyWith(
roomStatus: info.roomStatus,
actualStartTime: info.roomStartTime,
boardUuid: info.boardUuid,
);
notifyListeners();
}
///自习室的开关
/// - [isOpen]: 是否开启
void toggleRoom({required bool isOpen}) {
@@ -92,6 +105,7 @@ class TchRoomVM extends ChangeNotifier {
///学生选择
void selectStudent(int id) {
activeSId = id;
clearHandUp(id);
notifyListeners();
}
@@ -114,20 +128,38 @@ class TchRoomVM extends ChangeNotifier {
student.speekerStatus = isOpen ? 0 : 1;
data['is_mute'] = isOpen ? 1 : 0;
} else if (action == StudentAction.camera) {
//如果是摄像头,只能关
if (student.cameraStatus == 0) return;
//如果是摄像头
bool isOpen = student.cameraStatus == 1;
student.cameraStatus = 0;
data['is_mute'] = 1;
data['is_mute'] = isOpen ? 1 : 0;
} else if (action == StudentAction.microphone) {
//如果是麦克风,只能关
if (student.microphoneStatus == 0) return;
//如果是麦克风
bool isOpen = student.microphoneStatus == 1;
student.microphoneStatus = 0;
data['is_mute'] = 1;
data['is_mute'] = isOpen ? 1 : 0;
}
notifyListeners();
_ws.send(RoomCommand.switchStudentCamera, data);
}
///关闭全部学生的摄像头或者扬声器
void closeAllStudentAction(StudentAction action) {
_students.forEach((item) {
if (action == StudentAction.speaker) {
item.speekerStatus = 0;
} else if (action == StudentAction.camera) {
item.cameraStatus = 0;
}
});
notifyListeners();
Map<String, dynamic> data = {
'target_user_id': "all",
"mute_type": action.value,
"is_mute": 1,
};
_ws.send(RoomCommand.switchStudentCamera, data);
}
//清除全部学生举手,或者是指定
void clearHandUp(int? id) {
Map<String, dynamic> data = {};
@@ -155,13 +187,28 @@ class TchRoomVM extends ChangeNotifier {
/// 同步单个学生的最新状态
void onSyncStudentItem(RoomUserDto userInfo) {
final index = _students.indexWhere((t) => t.userId == userInfo.userId);
print(userInfo.toString());
if (index != -1) {
_students[index] = userInfo;
notifyListeners();
}
}
///更新学生的文件
void updateStudentFile(int uId, List<String> files) {
final index = _students.indexWhere((t) => t.userId == uId);
if (index != -1) {
_students[index].filesList = files;
notifyListeners();
}
}
///结束会议
void endRoom() {
roomInfo = roomInfo.copyWith(roomStatus: 2);
_ws.send(RoomCommand.closeRoom);
notifyListeners();
}
//销毁
@override
void dispose() {

View File

@@ -1,8 +1,8 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:app/config/config.dart';
import 'package:app/providers/user_store.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../viewmodel/tch_room_vm.dart';
import 'student_item.dart';
@@ -27,11 +27,11 @@ class _ContentViewState extends State<ContentView> {
@override
void dispose() {
super.dispose();
WakelockPlus.disable();
_dispose();
}
void _initRtc() async {
UserStore userStore = context.read<UserStore>();
final vm = context.read<TchRoomVM>();
_engine = createAgoraRtcEngine();
//初始化 RtcEngine设置频道场景为 channelProfileLiveBroadcasting直播场景
@@ -41,39 +41,33 @@ class _ContentViewState extends State<ContentView> {
channelProfile: ChannelProfileType.channelProfileCommunication,
),
);
//添加回调
_engine!.registerEventHandler(
RtcEngineEventHandler(
// 成功加入频道回调
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {});
},
// 远端用户或主播加入当前频道回调
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {},
// 远端用户或主播离开当前频道回调
onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {},
),
);
//启动视频模块
// 启用视频模块
await _engine!.enableVideo();
//加入频道
await _engine!.joinChannel(
token: vm.rtcToken!.token,
channelId: vm.rtcToken!.channel,
uid: userStore.userInfo!.id,
options: ChannelMediaOptions(
// 自动订阅所有视频流
autoSubscribeVideo: true,
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布摄像头采集的视频
publishCameraTrack: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
// 设置用户角色为 clientRoleBroadcaster主播或 clientRoleAudience观众
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
// 开启本地预览
await _engine!.startPreview();
final status = await _engine!.getConnectionState();
WakelockPlus.enable();
if (status == ConnectionStateType.connectionStateDisconnected) {
//加入频道
await _engine!.joinChannel(
token: vm.rtcToken!.token,
channelId: vm.rtcToken!.channel,
uid: vm.rtcToken!.uid,
options: ChannelMediaOptions(
// 自动订阅所有视频流
autoSubscribeVideo: true,
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布摄像头采集的视频
publishCameraTrack: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
// 设置用户角色为 clientRoleBroadcaster主播或 clientRoleAudience观众
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
}
}
//销毁
@@ -89,8 +83,11 @@ class _ContentViewState extends State<ContentView> {
return Consumer<TchRoomVM>(
builder: (context, vm, _) {
if (vm.students.isEmpty) {
return Center(
child: Text('准备中'),
return Align(
child: Text(
'学生还没入场',
style: TextStyle(color: Colors.white),
),
);
}
//选中的学生
@@ -105,9 +102,30 @@ class _ContentViewState extends State<ContentView> {
spacing: 15,
children: [
Expanded(
child: StudentItem(
user: activeStudent,
engine: _engine,
child: Stack(
children: [
StudentItem(
user: activeStudent,
engine: _engine,
),
Positioned(
top: 0,
left: 0,
child: Container(
width: 150,
color: Colors.black,
child: AspectRatio(
aspectRatio: 1 / 1.2,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine!,
canvas: const VideoCanvas(uid: 0),
),
),
),
),
),
],
),
),
SizedBox(

View File

@@ -1,11 +1,11 @@
import 'dart:async';
import 'package:app/utils/time.dart';
import 'package:app/widgets/base/dialog/config_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../../../widgets/room/core/count_down_vm.dart';
import 'content_view.dart';
import '../viewmodel/tch_room_vm.dart';
@@ -17,40 +17,12 @@ class StatusView extends StatefulWidget {
}
class _StatusViewState extends State<StatusView> {
int _seconds = 0;
Timer? _timer;
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
void _startCountDown(DateTime startTime) {
// 避免重复计时器
if (_timer != null) return;
final now = DateTime.now();
int diff = startTime.difference(now).inSeconds;
if (diff <= 0) {
return;
}
setState(() {
_seconds = diff;
});
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) return;
setState(() {
_seconds--;
});
if (_seconds <= 0) {
_timer?.cancel();
_timer = null;
}
});
void initState() {
super.initState();
final countVM = context.read<CountDownVM>();
countVM.removeListener(_onCountDownEnd);
countVM.addListener(_onCountDownEnd);
}
///开播中返回拦截弹窗
@@ -72,55 +44,65 @@ class _StatusViewState extends State<StatusView> {
);
}
///监听会议室倒计时结束的时候
void _onCountDownEnd() {
final countVM = context.read<CountDownVM>();
if (countVM.endCountDown == 0) {
EasyLoading.showToast("自习室已到结束时间,请记得关闭会议室");
countVM.removeListener(_onCountDownEnd);
}
}
@override
Widget build(BuildContext context) {
final vm = context.watch<TchRoomVM>();
final tchVM = context.watch<TchRoomVM>();
var roomStatus = tchVM.roomInfo.roomStatus;
/// 1. 未加载
if (vm.roomStatus == -1) {
if (roomStatus == -1) {
return const Align(
child: Text("加载中", style: TextStyle(color: Colors.white)),
);
}
/// 2. 未开始的房间
if (vm.roomStatus == 0) {
if (vm.canEnterRoom) {
// 到时间了 → 自动开播
WidgetsBinding.instance.addPostFrameCallback((_) {
vm.toggleRoom(isOpen: true);
});
} else {
// 没到时间 → 启动倒计时
_startCountDown(parseTime(vm.roomInfo.startTime));
}
return Align(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"未到开播时间,到点后自动开播",
style: TextStyle(color: Colors.white),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: Text(
formatSeconds(_seconds),
style: const TextStyle(
color: Colors.white,
fontSize: 26,
fontWeight: FontWeight.bold,
),
if (roomStatus == 0) {
return Consumer<CountDownVM>(
builder: (_, countVM, __) {
if (countVM.canEnterRoom) {
tchVM.toggleRoom(isOpen: true);
return SizedBox();
} else {
countVM.startStartCountdown();
return Align(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"未到开播时间,到点后自动开播",
style: TextStyle(color: Colors.white),
),
Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: Text(
formatSeconds(countVM.startCountDown),
style: const TextStyle(
color: Colors.white,
fontSize: 26,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
);
}
},
);
}
/// 3. 已开播
if (vm.roomStatus == 1) {
if (roomStatus == 1) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {

View File

@@ -2,6 +2,7 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:app/pages/teacher/room/viewmodel/type.dart';
import 'package:app/request/dto/room/room_user_dto.dart';
import 'package:app/widgets/room/file_drawer.dart';
import 'package:app/widgets/room/other_widget.dart';
import 'package:app/widgets/room/video_surface.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -26,7 +27,11 @@ class StudentItem extends StatefulWidget {
class _StudentItemState extends State<StudentItem> {
///打开文件列表
void _openFileList() {
showFileDialog(context, isUpload: false);
showFileDialog(
context,
isUpload: false,
files: widget.user.filesList,
);
}
@override
@@ -40,7 +45,6 @@ class _StudentItemState extends State<StudentItem> {
///声音是否开启
bool isSpeakerOpen = widget.user.speekerStatus == 1;
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
@@ -51,15 +55,18 @@ class _StudentItemState extends State<StudentItem> {
child: SizedBox(
width: double.infinity,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
if (widget.engine != null)
AgoraVideoView(
controller: VideoViewController(
rtcEngine: widget.engine!,
canvas: VideoCanvas(uid: widget.user.rtcUid),
VideoSurface(
user: widget.user,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: widget.engine!,
canvas: VideoCanvas(uid: widget.user.rtcUid),
),
),
),
// VideoSurface(),
Positioned(
bottom: 0,
left: 0,
@@ -79,6 +86,8 @@ class _StudentItemState extends State<StudentItem> {
),
),
),
///右上角选中
if (widget.user.userId != vm.activeSId)
Positioned(
right: 5,
@@ -99,6 +108,17 @@ class _StudentItemState extends State<StudentItem> {
),
),
),
///举手
if (widget.user.handup == 1)
Positioned(
bottom: 40,
child: HandRaiseButton(
onTap: () {
vm.clearHandUp(widget.user.userId);
},
),
),
],
),
),
@@ -106,6 +126,7 @@ class _StudentItemState extends State<StudentItem> {
ColoredBox(
color: Color(0xFF232426),
child: Row(
spacing: 1,
children: [
_actionItem(
icon: isCameraOpen ? RemixIcons.video_on_fill : RemixIcons.video_off_fill,