自习室优化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,12 +1,13 @@
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
import 'package:app/widgets/room/file_drawer.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:remixicon/remixicon.dart';
import '../viewmodel/stu_room_vm.dart';
class BottomBar extends StatefulWidget {
const BottomBar({super.key});
final void Function()? onTap;
const BottomBar({super.key, this.onTap});
@override
State<BottomBar> createState() => _BottomBarState();
@@ -15,51 +16,77 @@ class BottomBar extends StatefulWidget {
class _BottomBarState extends State<BottomBar> {
///显示文件
void _handShowFile() {
showFileDialog(context);
final vm = context.read<StuRoomVM>();
if (vm.selfInfo == null) return;
showFileDialog(
context,
files: vm.selfInfo!.filesList,
onConfirm: (file) {
vm.uploadFile(file);
},
);
}
@override
Widget build(BuildContext context) {
final vm = context.watch<StuRoomVM>();
if (vm.roomInfo.roomStatus != 1) {
return SizedBox();
}
return Container(
decoration: BoxDecoration(
color: Color(0xff232426),
),
height: 70,
child: Consumer<StuRoomVM>(
builder: (context,vm,_) {
builder: (context, vm, _) {
//摄像头开关
return Row(
children: [
BarItem(
title: "摄像头",
icon: vm.cameraOpen ? RemixIcons.video_on_fill : RemixIcons.video_off_fill,
isOff: !vm.cameraOpen,
onTap: vm.changeCameraSwitch,
icon: vm.cameraClose ? RemixIcons.video_off_fill : RemixIcons.video_on_fill,
isOff: vm.cameraClose,
onTap: () {
vm.changeCameraSwitch(value: vm.cameraClose);
},
),
BarItem(
title: "麦克风",
icon: vm.micOpen ? RemixIcons.mic_fill : RemixIcons.mic_off_fill,
isOff: !vm.micOpen,
onTap: vm.changeMicSwitch,
icon: vm.micClose ? RemixIcons.mic_off_fill : RemixIcons.mic_fill,
isOff: vm.micClose,
onTap: () {
vm.changeMicSwitch(value: vm.micClose);
},
),
BarItem(
title: "声音",
icon: vm.speakerOpen ? RemixIcons.volume_up_fill : RemixIcons.volume_mute_fill,
isOff: !vm.speakerOpen,
onTap: vm.changeSpeakerSwitch,
icon: vm.speakerClose
? RemixIcons.volume_mute_fill
: RemixIcons.volume_up_fill,
isOff: vm.speakerClose,
onTap: () {
vm.changeSpeakerSwitch(value: vm.speakerClose);
},
),
BarItem(
title: "举手",
icon: RemixIcons.hand,
onTap: () {
vm.changeHandSwitch();
widget.onTap?.call();
},
),
BarItem(
title: "拍照",
title: "上传",
icon: RemixIcons.upload_2_fill,
onTap: _handShowFile,
),
],
);
}
},
),
);
}

View File

@@ -1,13 +1,12 @@
import 'dart:async';
import 'package:app/utils/time.dart';
import 'package:app/widgets/base/dialog/config_dialog.dart';
import 'package:app/widgets/room/core/count_down_vm.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:remixicon/remixicon.dart';
import '../viewmodel/stu_room_vm.dart';
class TopBar extends StatefulWidget implements PreferredSizeWidget {
class TopBar extends StatelessWidget implements PreferredSizeWidget {
final bool showOther;
final void Function()? onOther;
@@ -17,67 +16,63 @@ class TopBar extends StatefulWidget implements PreferredSizeWidget {
this.onOther,
});
@override
State<TopBar> createState() => _TopBarState();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _TopBarState extends State<TopBar> {
Timer? _timer;
int seconds = 0;
late DateTime startTime;
@override
void initState() {
super.initState();
final vm = context.read<StuRoomVM>();
startTime = parseTime(vm.roomInfo.startTime);
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
final diff = DateTime.now().difference(startTime).inSeconds;
setState(() {
seconds = diff < 0 ? 0 : diff;
});
});
}
/// 你若想外面主动停,可以暴露这个方法
void stopTimer() {
_timer?.cancel();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final vm = context.read<StuRoomVM>();
return AppBar(
foregroundColor: Colors.white,
titleTextStyle: const TextStyle(color: Colors.white, fontSize: 18),
backgroundColor: const Color(0xff232426),
centerTitle: true,
title: Column(
children: [
Text(vm.roomInfo.roomName),
Text(
formatSeconds(seconds),
style: const TextStyle(fontSize: 12, color: Colors.white24),
leadingWidth: 100,
leading: Container(
padding: EdgeInsets.only(left: 10),
alignment: Alignment.centerLeft,
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (_) {
return ConfigDialog(
content: "请确认是否退出自习室",
onCancel: () {
context.pop();
},
onConfirm: () {
context.pop();
context.pop();
},
);
},
);
},
child: Text(
"退出自习室",
style: TextStyle(color: Colors.red),
),
],
),
),
title: Consumer<CountDownVM>(
builder: (context, vm, _) {
return Column(
children: [
Text(vm.roomInfo!.roomName),
Text(
formatSeconds(vm.studyTime),
style: const TextStyle(fontSize: 12, color: Colors.white24),
),
],
);
},
),
actions: [
IconButton(
onPressed: widget.onOther,
icon: Icon(widget.showOther ? RemixIcons.team_fill : RemixIcons.team_line),
onPressed: onOther,
icon: Icon(showOther ? RemixIcons.team_fill : RemixIcons.team_line),
),
],
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

View File

@@ -1,17 +1,18 @@
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
import 'package:app/pages/student/room/widgets/status_view.dart';
import 'package:app/providers/user_store.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/widgets/base/transition/slide_hide.dart';
import 'package:app/widgets/room/core/count_down_vm.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'controls/bottom_bar.dart';
import 'controls/top_bar.dart';
import 'video/student_video_list.dart';
import 'video/teacher_video.dart';
import 'viewmodel/stu_room_vm.dart';
class SRoomPage extends StatefulWidget {
final RoomInfoDto roomInfo;
final RoomListItemDto roomInfo;
const SRoomPage({super.key, required this.roomInfo});
@@ -36,29 +37,40 @@ class _SRoomPageState extends State<SRoomPage> {
@override
Widget build(BuildContext context) {
UserStore userStore = context.read<UserStore>();
return ChangeNotifierProvider<StuRoomVM>(
create: (_) => StuRoomVM(
roomInfo: widget.roomInfo,
uid: userStore.userInfo!.id,
),
return MultiProvider(
providers: [
ChangeNotifierProvider<StuRoomVM>(
create: (_) => StuRoomVM(
info: widget.roomInfo,
uid: userStore.userInfo!.id,
),
),
ChangeNotifierProxyProvider<StuRoomVM, CountDownVM>(
create: (_) => CountDownVM(),
update: (_, stuVM, countDownVM) {
countDownVM!.bind(stuVM.roomInfo);
return countDownVM;
},
),
],
child: Scaffold(
body: Stack(
children: [
//底部控制显示
GestureDetector(
onTap: _toggleOverlay,
child: Container(color: Color(0xff2c3032)),
),
//老师视频画面
TeacherVideo(),
StatusView(),
//其他学生
Positioned(
right: 0,
top: 0,
bottom: 0,
child: Visibility(
visible: _showOtherStudent,
child: StudentVideoList(),
child: IgnorePointer(
child: Visibility(
visible: _showOtherStudent,
child: StudentVideoList(),
),
),
),
@@ -87,7 +99,9 @@ class _SRoomPageState extends State<SRoomPage> {
child: SlideHide(
direction: SlideDirection.down,
hide: !_controlsVisible,
child: BottomBar(),
child: BottomBar(
onTap: _toggleOverlay,
),
),
),
],

View File

@@ -1,4 +1,6 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
import 'package:app/widgets/room/video_surface.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -8,6 +10,9 @@ class StudentVideoList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final vm = context.watch<StuRoomVM>();
if (vm.roomInfo.roomStatus != 1) {
return SizedBox();
}
return SafeArea(
child: Container(
width: 250,
@@ -26,6 +31,17 @@ class StudentVideoList extends StatelessWidget {
color: Color(0xff373c3e),
borderRadius: BorderRadius.circular(10),
),
child: VideoSurface(
user: item,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: vm.engine!,
canvas: VideoCanvas(
uid: item.rtcUid,
),
),
),
),
),
),
Positioned(

View File

@@ -1,60 +1,83 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:app/widgets/base/dialog/config_dialog.dart';
import 'package:app/widgets/room/other_widget.dart';
import 'package:app/widgets/room/video_surface.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../viewmodel/stu_room_vm.dart';
class TeacherVideo extends StatefulWidget {
class TeacherVideo extends StatelessWidget {
const TeacherVideo({super.key});
@override
State<TeacherVideo> createState() => _TeacherVideoState();
}
class _TeacherVideoState extends State<TeacherVideo> {
@override
Widget build(BuildContext context) {
final vm = context.read<StuRoomVM>();
final vm = context.watch<StuRoomVM>();
final teacherInfo = vm.teacherInfo;
///没开始
if (vm.roomStatus == 0) {
return _empty("自习室还没开始");
}
///开始
if (vm.roomStatus == 1 && vm.engine != null) {
if (teacherInfo == null) {
return _empty("老师不在自习室内");
}
if (teacherInfo.online == 0) {
return _empty("老师掉线了,请耐心等待");
}
return AgoraVideoView(
controller: VideoViewController(
rtcEngine: vm.engine!,
canvas: VideoCanvas(
uid: vm.teacherInfo!.userId,
),
),
);
}
///结束
if (vm.roomStatus == 2) {
return _empty("自习室已结束");
}
return _empty("加载中");
}
Widget _empty(String title) {
return SafeArea(
child: Align(
child: Text(
title,
style: TextStyle(color: Colors.white),
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
showDialog(
context: context,
builder: (context) {
return ConfigDialog(
content: "是否退出自习室",
onCancel: () {
context.pop();
},
onConfirm: () {
context.pop();
context.pop();
},
);
},
);
}
},
child: IgnorePointer(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
VideoSurface(
user: teacherInfo!,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: vm.engine!,
canvas: VideoCanvas(
uid: teacherInfo.rtcUid,
),
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
width: 150,
color: Colors.black,
child: AspectRatio(
aspectRatio: 1 / 1.2,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: vm.engine!,
canvas: const VideoCanvas(uid: 0),
),
),
),
),
),
if (vm.selfInfo?.handup == 1)
Positioned(
bottom: 60,
child: HandRaiseButton(
onTap: vm.changeHandSwitch,
),
),
],
),
),
);
}
}
}

View File

@@ -2,24 +2,23 @@ import 'dart:async';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:app/config/config.dart';
import 'package:app/providers/user_store.dart';
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';
import 'package:app/request/websocket/room_websocket.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:logger/logger.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
Logger log = Logger();
class StuRoomVM extends ChangeNotifier {
///房间信息
final RoomInfoDto roomInfo;
///房间开启状态,0没开始1进行中2已结束
int roomStatus = 0;
///房间信息状态0没开始1进行中2已结束
late MeetingRoomDto roomInfo;
///其他学生列表,老师信息,自己信息
int uid;
@@ -27,12 +26,18 @@ class StuRoomVM extends ChangeNotifier {
RoomUserDto? teacherInfo;
RoomUserDto? selfInfo;
///本人的摄像头麦克风、扬声器状态是否打开了
bool get cameraOpen => selfInfo?.cameraStatus == 1;
// ///老师是否发送请求过来了0关闭1摄像头2麦克风
// bool cameraReq = false;
// bool micReq = false;
bool get micOpen => selfInfo?.microphoneStatus == 1;
///本人的摄像头、麦克风、扬声器、举手状态是否关闭了
bool get cameraClose => selfInfo?.cameraStatus == 0;
bool get speakerOpen => selfInfo?.speekerStatus == 1;
bool get micClose => selfInfo?.microphoneStatus == 0;
bool get speakerClose => selfInfo?.speekerStatus == 0;
bool get handClose => selfInfo?.handup == 0;
///ws管理
final RoomWebSocket _ws = RoomWebSocket();
@@ -43,12 +48,14 @@ class StuRoomVM extends ChangeNotifier {
RtcEngine? get engine => _engine;
StuRoomVM({required this.roomInfo, required this.uid}) {
StuRoomVM({required RoomListItemDto info, required this.uid}) {
roomInfo = MeetingRoomDto.fromRoomListItem(info);
_startRoom();
}
///初始化声网
Future<void> _initRtc() async {
if (_engine != null) return;
_engine = createAgoraRtcEngine();
//初始化 RtcEngine设置频道场景为 channelProfileLiveBroadcasting直播场景
await _engine!.initialize(
@@ -57,27 +64,34 @@ class StuRoomVM extends ChangeNotifier {
channelProfile: ChannelProfileType.channelProfileCommunication,
),
);
//启动视频模块
_engine!.getUserInfoByUid(1);
// 启用视频模块
await _engine!.enableVideo();
//加入频道
await _engine!.joinChannel(
token: _ws.rtcToken!.token,
channelId: _ws.rtcToken!.channel,
uid: uid,
// uid: _ws.rtcToken!.uid,
options: ChannelMediaOptions(
// 自动订阅所有视频流
autoSubscribeVideo: true,
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布摄像头采集的视频
publishCameraTrack: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
// 设置用户角色为 clientRoleBroadcaster主播或 clientRoleAudience观众
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
// 开启本地预览
await _engine!.startPreview();
WakelockPlus.enable();
final status = await _engine!.getConnectionState();
if (status == ConnectionStateType.connectionStateDisconnected) {
//加入频道
await _engine!.joinChannel(
token: _ws.rtcToken!.token,
channelId: _ws.rtcToken!.channel,
uid: _ws.rtcToken!.uid,
options: ChannelMediaOptions(
// 自动订阅所有视频流
autoSubscribeVideo: true,
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布摄像头采集的视频
publishCameraTrack: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
// 设置用户角色为 clientRoleBroadcaster主播或 clientRoleAudience观众
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
}
}
///开始链接房间
@@ -94,7 +108,30 @@ class StuRoomVM extends ChangeNotifier {
if (msg.event == RoomEvent.changeUser) {
final list = RoomUserDto.listFromJson(msg.data['user_list']);
onStudentChange(list);
onRoomStartStatus(RoomTypeDto.fromJson(msg.data['room_info']));
onRoomStartStatus(RoomInfoDto.fromJson(msg.data['room_info']));
} else if (msg.event == RoomEvent.closeStudentCamera) {
changeCameraSwitch(fromServer: false, value: false);
} else if (msg.event == RoomEvent.closeStudentMic) {
changeMicSwitch(fromServer: false, value: false);
} else if ([
RoomEvent.closeStudentSpeaker,
RoomEvent.openStudentSpeaker,
].contains(msg.event)) {
//控制扬声器
changeSpeakerSwitch(
value: msg.event == RoomEvent.openStudentSpeaker,
fromServer: false,
);
} else if (msg.event == RoomEvent.openStudentMic) {
EasyLoading.showToast("老师请求打开麦克风");
// 打开麦克风
} else if (msg.event == RoomEvent.openStudentCamera) {
EasyLoading.showToast("老师请求打开摄像头");
// 打开摄像头
} else if (msg.event == RoomEvent.clearHandUp) {
changeHandSwitch();
} else if (msg.event == RoomEvent.closeRoom) {
_closeRoom();
}
});
}
@@ -112,6 +149,10 @@ class StuRoomVM extends ChangeNotifier {
newList.add(t);
} else {
selfInfo = t;
//同步声网的状态
changeCameraSwitch(value: selfInfo!.cameraStatus == 1, fromServer: false);
changeMicSwitch(value: selfInfo!.microphoneStatus == 1, fromServer: false);
changeSpeakerSwitch(value: selfInfo!.speekerStatus == 1, fromServer: false);
}
}
}
@@ -120,52 +161,111 @@ class StuRoomVM extends ChangeNotifier {
}
///设置房间开启状态
void onRoomStartStatus(RoomTypeDto roomInfo) {
roomStatus = roomInfo.roomStatus;
void onRoomStartStatus(RoomInfoDto info) {
roomInfo = roomInfo.copyWith(
roomStatus: info.roomStatus,
actualStartTime: info.roomStartTime,
boardUuid: info.boardUuid,
);
//开启摄像头
if (roomInfo.roomStatus == 1) {
_initRtc();
}
notifyListeners();
}
///控制摄像头开关
void changeCameraSwitch() {
bool isOpen = selfInfo!.cameraStatus == 1;
selfInfo!.cameraStatus = isOpen ? 0 : 1;
//发送指令
_ws.send(RoomCommand.studentActon, {
"mute_type": "camera",
"is_mute": isOpen ? 1 : 0,
});
/// - [value] 摄像头状态true为开启false为关闭
/// - [fromServer] 发送指令给服务器默认true
void changeCameraSwitch({
required bool value,
bool fromServer = true,
}) {
//改变后的操作状态,true表示开false关
selfInfo!.cameraStatus = value ? 1 : 0;
// //发送指令
if (fromServer) {
_ws.send(RoomCommand.studentActon, {
"mute_type": "camera",
"is_mute": value ? 0 : 1,
});
}
_engine?.enableLocalVideo(value);
notifyListeners();
}
///控制麦克风开关
void changeMicSwitch() {
bool isOpen = selfInfo!.microphoneStatus == 1;
selfInfo!.microphoneStatus = isOpen ? 0 : 1;
print(selfInfo!.microphoneStatus);
/// - [value] 麦克风状态true为开启false为关闭
/// - [fromServer] 默认为true发送指令给服务器
void changeMicSwitch({required bool value, bool fromServer = true}) {
selfInfo!.microphoneStatus = value ? 1 : 0;
//发送指令
_ws.send(RoomCommand.studentActon, {
"mute_type": "microphone",
"is_mute": isOpen ? 1 : 0,
});
if (fromServer) {
_ws.send(RoomCommand.studentActon, {
"mute_type": "microphone",
"is_mute": value ? 0 : 1,
});
}
_engine?.enableLocalAudio(value);
notifyListeners();
}
/// 控制扬声器开关
void changeSpeakerSwitch() {
bool isOpen = selfInfo!.speekerStatus == 1;
selfInfo!.speekerStatus = isOpen ? 0 : 1;
/// - [value] 扬声器状态true为开启false为关闭
/// - [fromServer] 默认为true发送指令给服务器
void changeSpeakerSwitch({required bool value, bool fromServer = true}) {
//操作后是否是开启状态
selfInfo!.speekerStatus = value ? 1 : 0;
//发送指令
_ws.send(RoomCommand.studentActon, {
"mute_type": "speeker",
"is_mute": isOpen ? 1 : 0,
});
if (fromServer) {
_ws.send(RoomCommand.studentActon, {
"mute_type": "speeker",
"is_mute": value ? 0 : 1,
});
}
_engine?.muteAllRemoteAudioStreams(!value);
notifyListeners();
}
///控制举手
void changeHandSwitch({bool fromServer = true}) {
bool nextOpen = handClose;
selfInfo!.handup = nextOpen ? 1 : 0;
if (fromServer) {
_ws.send(RoomCommand.handUp, {
'is_handup': nextOpen ? 1 : 0,
});
}
notifyListeners();
}
///上传文件
void uploadFile(List<String> files) {
selfInfo?.filesList.addAll(files);
_ws.send(RoomCommand.uploadFile, {
"files": selfInfo!.filesList,
});
}
///自习室关闭
void _closeRoom() {
roomInfo.roomStatus = 2;
_dispose();
notifyListeners();
}
///销毁
void _dispose() {
_engine?.leaveChannel();
_engine?.release();
_sub?.cancel();
_ws.dispose();
WakelockPlus.disable();
}
@override
void dispose() {
super.dispose();
_sub?.cancel();
_ws.dispose();
_dispose();
}
}

View File

@@ -0,0 +1,99 @@
import 'package:app/utils/time.dart';
import 'package:app/widgets/base/button/index.dart';
import 'package:app/widgets/room/core/count_down_vm.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../video/teacher_video.dart';
import '../viewmodel/stu_room_vm.dart';
class StatusView extends StatelessWidget {
const StatusView({super.key});
@override
Widget build(BuildContext context) {
final vm = context.watch<StuRoomVM>();
final teacherInfo = vm.teacherInfo;
///没开始
if (vm.roomInfo.roomStatus == 0) {
return Consumer<CountDownVM>(
builder: (_, countVM, __) {
if (countVM.canEnterRoom) {
return _empty("等待老师进入自习室");
} 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,
),
),
),
],
),
);
}
},
);
}
///结束
if (vm.roomInfo.roomStatus == 2) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 5,
children: [
_empty("自习室已结束"),
SizedBox(
width: 120,
child: Button(
text: "返回首页",
onPressed: () {
context.pop();
},
),
),
],
);
}
///开始
if (vm.roomInfo.roomStatus == 1 && vm.engine != null) {
if (teacherInfo == null) {
return _empty("老师不在自习室内");
}
if (teacherInfo.online == 0) {
return _empty("老师暂时离开,请耐心等待");
}
return TeacherVideo();
}
return _empty("加载中");
}
Widget _empty(String title) {
return SafeArea(
child: Align(
child: Text(
title,
style: TextStyle(color: Colors.white),
),
),
);
}
}