1
This commit is contained in:
@@ -1,33 +1,36 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
||||
import 'package:app/request/api/room_api.dart';
|
||||
import 'package:app/request/dto/room/room_type_dto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'today/s_today_card.dart';
|
||||
import 'widgets/user_header.dart';
|
||||
|
||||
class SHomePage extends StatefulWidget {
|
||||
class SHomePage extends StatelessWidget {
|
||||
const SHomePage({super.key});
|
||||
|
||||
@override
|
||||
State<SHomePage> createState() => _SHomePageState();
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => SHomeVm(),
|
||||
child: _HomeView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SHomePageState extends State<SHomePage> {
|
||||
|
||||
|
||||
///刷新状态
|
||||
Future<void> _refresh() async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
class _HomeView extends StatelessWidget {
|
||||
const _HomeView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.read<SHomeVm>();
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
appBar: UserHeader(),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
onRefresh: vm.loadData,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(context.pagePadding),
|
||||
children: [
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../viewmodel/s_home_vm.dart';
|
||||
|
||||
///banner
|
||||
class BannerInfo extends StatelessWidget {
|
||||
@@ -7,11 +11,12 @@ class BannerInfo extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.read<SHomeVm>();
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.network(
|
||||
"https://images.unsplash.com/photo-1505209487757-5114235191e5?w=800",
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: "https://www.gxgif.com/pic/fj/2025115155717.jpg",
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@@ -35,34 +40,38 @@ class BannerInfo extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
margin: EdgeInsets.only(bottom: 30),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: context.success,
|
||||
shape: BoxShape.circle,
|
||||
Visibility(
|
||||
visible: false,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
margin: EdgeInsets.only(bottom: 30),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 5,
|
||||
children: [
|
||||
Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: context.success,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"进行中",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
"进行中",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 50),
|
||||
Text(
|
||||
"高中数学专场",
|
||||
vm.roomInfo?.roomName ?? "",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
@@ -82,4 +91,4 @@ class BannerInfo extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:app/utils/permission.dart';
|
||||
import 'package:app/widgets/base/button/index.dart';
|
||||
import 'package:app/widgets/base/empty/index.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'banner_info.dart';
|
||||
import 'teacher_info.dart';
|
||||
|
||||
class STodayCard extends StatefulWidget {
|
||||
const STodayCard({super.key});
|
||||
@@ -16,60 +23,124 @@ class STodayCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _STodayCardState extends State<STodayCard> {
|
||||
///进入自习室
|
||||
void _handleEnterRoom() {
|
||||
context.push(RoutePaths.sRoom);
|
||||
///前往会议室
|
||||
void _goToRoom() {
|
||||
checkPermission(
|
||||
permissions: [Permission.microphone, Permission.camera],
|
||||
onGranted: () {
|
||||
final vm = context.read<SHomeVm>();
|
||||
context.push(RoutePaths.sRoom,extra: vm.roomInfo);
|
||||
},
|
||||
onDenied: () {
|
||||
EasyLoading.showError("请开启权限");
|
||||
},
|
||||
onPermanentlyDenied: () {
|
||||
EasyLoading.showError("请手动开启麦克风和摄像头权限");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BannerInfo(),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(horizontal: context.pagePadding, vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
final vm = context.watch<SHomeVm>();
|
||||
if (!vm.loading && vm.roomInfo == null) {
|
||||
return Empty(text: "没有自习室");
|
||||
}
|
||||
return Skeletonizer(
|
||||
enabled: vm.loading,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Skeleton.unite(
|
||||
child: BannerInfo(),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 30,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TeacherInfo(),
|
||||
Row(
|
||||
spacing: 20,
|
||||
children: [
|
||||
InfoItem(
|
||||
label: "自习时间",
|
||||
value: "19:00-21:00",
|
||||
icon: RemixIcons.time_line,
|
||||
color: Theme.of(context).primaryColor,
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(horizontal: context.pagePadding, vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 30,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(context.pagePadding),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffeef2ff),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
InfoItem(
|
||||
label: "在线人数",
|
||||
value: "8/12 人",
|
||||
icon: RemixIcons.time_line,
|
||||
color: context.success,
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Skeleton.replace(
|
||||
replacement: Bone.circle(),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: vm.roomInfo?.teacherAvatar ?? "",
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(vm.roomInfo?.teacherName ?? ""),
|
||||
Text(
|
||||
vm.roomInfo?.teacherBackground ?? "",
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: Button(
|
||||
text: "进入自习室",
|
||||
onPressed: _handleEnterRoom,
|
||||
),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
spacing: 20,
|
||||
children: [
|
||||
InfoItem(
|
||||
label: "自习时间",
|
||||
value: "${vm.roomInfo?.startTime}-${vm.roomInfo?.endTime}",
|
||||
icon: RemixIcons.time_line,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
InfoItem(
|
||||
label: "自习时长",
|
||||
value: "${vm.roomMinutes} 分钟",
|
||||
icon: RemixIcons.timer_line,
|
||||
color: context.success,
|
||||
),
|
||||
],
|
||||
),
|
||||
Skeleton.unite(
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: Button(
|
||||
text: "进入自习室",
|
||||
onPressed: _goToRoom,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
//老师信息
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TeacherInfo extends StatelessWidget {
|
||||
const TeacherInfo({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(context.pagePadding),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xffeef2ff),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://doaf.asia/api/assets/1/图/62865798_p0.jpg',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("张老师"),
|
||||
Text(
|
||||
"资深数学教师 · 10年教学经验",
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
34
lib/pages/student/home/viewmodel/s_home_vm.dart
Normal file
34
lib/pages/student/home/viewmodel/s_home_vm.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:app/request/api/room_api.dart';
|
||||
import 'package:app/request/dto/room/room_info_dto.dart';
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class SHomeVm extends ChangeNotifier {
|
||||
RoomInfoDto? roomInfo;
|
||||
bool loading = true;
|
||||
|
||||
SHomeVm() {
|
||||
loadData();
|
||||
}
|
||||
|
||||
//加载数据
|
||||
Future<void> loadData() async {
|
||||
final list = await getRoomListApi();
|
||||
loading = false;
|
||||
|
||||
if (list.isNotEmpty) {
|
||||
roomInfo = list.first;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///计算会议时间
|
||||
int get roomMinutes {
|
||||
if (roomInfo == null) return 0;
|
||||
final start = parseTime(roomInfo!.startTime);
|
||||
final end = parseTime(roomInfo!.endTime);
|
||||
|
||||
return end.difference(start).inMinutes;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -10,6 +11,7 @@ class UserHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userStore = context.read<UserStore>();
|
||||
return AppBar(
|
||||
title: const Text('学光自习室'),
|
||||
actions: [
|
||||
@@ -32,7 +34,7 @@ class UserHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
size: 18,
|
||||
),
|
||||
Text(
|
||||
"会员至 2025-03-12",
|
||||
"会员至 ${formatDate(userStore.userInfo?.extraInfo.vipEndTime,'YYYY-MM-DD')}",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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';
|
||||
|
||||
|
||||
@@ -23,31 +25,41 @@ class _BottomBarState extends State<BottomBar> {
|
||||
color: Color(0xff232426),
|
||||
),
|
||||
height: 70,
|
||||
child: Row(
|
||||
children: [
|
||||
BarItem(
|
||||
title: "摄像头",
|
||||
icon: RemixIcons.video_on_fill,
|
||||
),
|
||||
BarItem(
|
||||
title: "麦克风",
|
||||
icon: RemixIcons.mic_off_fill,
|
||||
),
|
||||
BarItem(
|
||||
title: "已静音",
|
||||
icon: RemixIcons.volume_mute_fill,
|
||||
isOff: true,
|
||||
),
|
||||
BarItem(
|
||||
title: "举手",
|
||||
icon: RemixIcons.hand,
|
||||
),
|
||||
BarItem(
|
||||
title: "拍照",
|
||||
icon: RemixIcons.upload_2_fill,
|
||||
onTap: _handShowFile,
|
||||
),
|
||||
],
|
||||
child: Consumer<StuRoomVM>(
|
||||
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,
|
||||
),
|
||||
BarItem(
|
||||
title: "麦克风",
|
||||
icon: vm.micOpen ? RemixIcons.mic_fill : RemixIcons.mic_off_fill,
|
||||
isOff: !vm.micOpen,
|
||||
onTap: vm.changeMicSwitch,
|
||||
),
|
||||
BarItem(
|
||||
title: "声音",
|
||||
icon: vm.speakerOpen ? RemixIcons.volume_up_fill : RemixIcons.volume_mute_fill,
|
||||
isOff: !vm.speakerOpen,
|
||||
onTap: vm.changeSpeakerSwitch,
|
||||
),
|
||||
BarItem(
|
||||
title: "举手",
|
||||
icon: RemixIcons.hand,
|
||||
),
|
||||
BarItem(
|
||||
title: "拍照",
|
||||
icon: RemixIcons.upload_2_fill,
|
||||
onTap: _handShowFile,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
import '../viewmodel/stu_room_vm.dart';
|
||||
|
||||
class TopBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
final bool showOther;
|
||||
final void Function()? onOther;
|
||||
|
||||
const TopBar({super.key, this.showOther = false, this.onOther});
|
||||
const TopBar({
|
||||
super.key,
|
||||
this.showOther = false,
|
||||
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: TextStyle(color: Colors.white, fontSize: 18),
|
||||
backgroundColor: Color(0xff232426),
|
||||
titleTextStyle: const TextStyle(color: Colors.white, fontSize: 18),
|
||||
backgroundColor: const Color(0xff232426),
|
||||
centerTitle: true,
|
||||
title: Column(
|
||||
children: [
|
||||
Text("会议"),
|
||||
Text(vm.roomInfo.roomName),
|
||||
Text(
|
||||
"01:12",
|
||||
style: TextStyle(fontSize: 12, color: Colors.white24),
|
||||
formatSeconds(seconds),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white24),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: onOther,
|
||||
icon: Icon(showOther ? RemixIcons.team_fill : RemixIcons.team_line),
|
||||
onPressed: widget.onOther,
|
||||
icon: Icon(widget.showOther ? RemixIcons.team_fill : RemixIcons.team_line),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/request/dto/room/room_info_dto.dart';
|
||||
import 'package:app/widgets/base/transition/slide_hide.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'controls/bottom_bar.dart';
|
||||
import 'controls/top_bar.dart';
|
||||
@@ -7,7 +11,9 @@ import 'video/student_video_list.dart';
|
||||
import 'video/teacher_video.dart';
|
||||
|
||||
class SRoomPage extends StatefulWidget {
|
||||
const SRoomPage({super.key});
|
||||
final RoomInfoDto roomInfo;
|
||||
|
||||
const SRoomPage({super.key, required this.roomInfo});
|
||||
|
||||
@override
|
||||
State<SRoomPage> createState() => _SRoomPageState();
|
||||
@@ -29,57 +35,63 @@ class _SRoomPageState extends State<SRoomPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
//底部控制显示
|
||||
GestureDetector(
|
||||
onTap: _toggleOverlay,
|
||||
child: Container(color: Color(0xff2c3032)),
|
||||
),
|
||||
|
||||
//老师视频画面
|
||||
TeacherVideo(),
|
||||
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Visibility(
|
||||
visible: _showOtherStudent,
|
||||
child: StudentVideoList(),
|
||||
UserStore userStore = context.read<UserStore>();
|
||||
return ChangeNotifierProvider<StuRoomVM>(
|
||||
create: (_) => StuRoomVM(
|
||||
roomInfo: widget.roomInfo,
|
||||
uid: userStore.userInfo!.id,
|
||||
),
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
//底部控制显示
|
||||
GestureDetector(
|
||||
onTap: _toggleOverlay,
|
||||
child: Container(color: Color(0xff2c3032)),
|
||||
),
|
||||
),
|
||||
//老师视频画面
|
||||
TeacherVideo(),
|
||||
|
||||
///控制栏
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SlideHide(
|
||||
direction: SlideDirection.up,
|
||||
hide: !_controlsVisible,
|
||||
child: TopBar(
|
||||
showOther: _showOtherStudent,
|
||||
onOther: () {
|
||||
setState(() {
|
||||
_showOtherStudent = !_showOtherStudent;
|
||||
});
|
||||
},
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Visibility(
|
||||
visible: _showOtherStudent,
|
||||
child: StudentVideoList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SlideHide(
|
||||
direction: SlideDirection.down,
|
||||
hide: !_controlsVisible,
|
||||
child: BottomBar(),
|
||||
|
||||
///控制栏
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SlideHide(
|
||||
direction: SlideDirection.up,
|
||||
hide: !_controlsVisible,
|
||||
child: TopBar(
|
||||
showOther: _showOtherStudent,
|
||||
onOther: () {
|
||||
setState(() {
|
||||
_showOtherStudent = !_showOtherStudent;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SlideHide(
|
||||
direction: SlideDirection.down,
|
||||
hide: !_controlsVisible,
|
||||
child: BottomBar(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class StudentVideoList extends StatefulWidget {
|
||||
class StudentVideoList extends StatelessWidget {
|
||||
const StudentVideoList({super.key});
|
||||
|
||||
@override
|
||||
State<StudentVideoList> createState() => _StudentVideoListState();
|
||||
}
|
||||
|
||||
class _StudentVideoListState extends State<StudentVideoList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<StuRoomVM>();
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
width: 250,
|
||||
padding: EdgeInsets.only(bottom: 30),
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.all(10),
|
||||
itemCount: 8,
|
||||
itemCount: vm.otherStuList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return VideoItem();
|
||||
final item = vm.otherStuList[index];
|
||||
return Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1.5 / 1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xff373c3e),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Text(
|
||||
item.userName,
|
||||
style: TextStyle(fontSize: 12, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => SizedBox(height: 15),
|
||||
),
|
||||
@@ -26,39 +52,3 @@ class _StudentVideoListState extends State<StudentVideoList> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoItem extends StatelessWidget {
|
||||
const VideoItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1.5 / 1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xff373c3e),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Text(
|
||||
"小红",
|
||||
style: TextStyle(fontSize: 12, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
133
lib/pages/student/room/viewmodel/stu_room_vm.dart
Normal file
133
lib/pages/student/room/viewmodel/stu_room_vm.dart
Normal file
@@ -0,0 +1,133 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/providers/user_store.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:logger/logger.dart';
|
||||
|
||||
Logger log = Logger();
|
||||
|
||||
class StuRoomVM extends ChangeNotifier {
|
||||
///房间信息
|
||||
final RoomInfoDto roomInfo;
|
||||
|
||||
///房间开启状态,0没开始,1进行中,2已结束
|
||||
int roomStatus = 0;
|
||||
|
||||
///其他学生列表,老师信息,自己信息
|
||||
int uid;
|
||||
List<RoomUserDto> otherStuList = [];
|
||||
RoomUserDto? teacherInfo;
|
||||
RoomUserDto? selfInfo;
|
||||
|
||||
///本人的摄像头、麦克风、扬声器状态是否打开了
|
||||
bool get cameraOpen => selfInfo?.cameraStatus == 1;
|
||||
|
||||
bool get micOpen => selfInfo?.microphoneStatus == 1;
|
||||
|
||||
bool get speakerOpen => selfInfo?.speekerStatus == 1;
|
||||
|
||||
///ws管理
|
||||
final RoomWebSocket _ws = RoomWebSocket();
|
||||
StreamSubscription<RoomMessage>? _sub;
|
||||
|
||||
RtcTokenDto? get rtcToken => _ws.rtcToken;
|
||||
|
||||
StuRoomVM({required this.roomInfo, required this.uid}) {
|
||||
_startRoom();
|
||||
}
|
||||
|
||||
///开始链接房间
|
||||
Future<void> _startRoom() async {
|
||||
//如果socket的token没有,先初始化
|
||||
if (_ws.wsToken.isEmpty) {
|
||||
await _ws.initToken(roomInfo.id);
|
||||
}
|
||||
//启动连接
|
||||
await _ws.connect();
|
||||
//
|
||||
_sub = _ws.stream.listen((msg) {
|
||||
//自习室人员变化,同时也设置房间是否开了
|
||||
if (msg.event == RoomEvent.changeUser) {
|
||||
final list = RoomUserDto.listFromJson(msg.data['user_list']);
|
||||
onStudentChange(list);
|
||||
onRoomStartStatus(RoomTypeDto.fromJson(msg.data['room_info']));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///学生人员变化事件,(如加入、退出、掉线)
|
||||
void onStudentChange(List<RoomUserDto> list) {
|
||||
List<RoomUserDto> newList = [];
|
||||
for (var t in list) {
|
||||
//设置老师
|
||||
if (t.userType == 2) {
|
||||
teacherInfo = t;
|
||||
} else {
|
||||
//要过滤自己,只要其他学生
|
||||
if (t.userId != uid) {
|
||||
newList.add(t);
|
||||
} else {
|
||||
selfInfo = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
otherStuList = newList;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///设置房间开启状态
|
||||
void onRoomStartStatus(RoomTypeDto roomInfo) {
|
||||
roomStatus = roomInfo.roomStatus;
|
||||
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,
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///控制麦克风开关
|
||||
void changeMicSwitch() {
|
||||
bool isOpen = selfInfo!.microphoneStatus == 1;
|
||||
selfInfo!.microphoneStatus = isOpen ? 0 : 1;
|
||||
print(selfInfo!.microphoneStatus);
|
||||
//发送指令
|
||||
_ws.send(RoomCommand.studentActon, {
|
||||
"mute_type": "microphone",
|
||||
"is_mute": isOpen ? 1 : 0,
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 控制扬声器开关
|
||||
void changeSpeakerSwitch() {
|
||||
bool isOpen = selfInfo!.speekerStatus == 1;
|
||||
selfInfo!.speekerStatus = isOpen ? 0 : 1;
|
||||
//发送指令
|
||||
_ws.send(RoomCommand.studentActon, {
|
||||
"mute_type": "speeker",
|
||||
"is_mute": isOpen ? 1 : 0,
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_sub?.cancel();
|
||||
_ws.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user