1
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/request/api/user_api.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:app/widgets/base/button/index.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
import 'widgets/login_agree.dart';
|
||||
import 'widgets/login_input.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
@@ -23,8 +25,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||
bool _agree = false;
|
||||
|
||||
///输入框
|
||||
final TextEditingController _telController = TextEditingController();
|
||||
final TextEditingController _codeController = TextEditingController();
|
||||
final TextEditingController _telController = TextEditingController(text: "13343214321");
|
||||
final TextEditingController _codeController = TextEditingController(text: "1111");
|
||||
|
||||
///登录中
|
||||
bool _loading = false;
|
||||
|
||||
///验证码倒计时
|
||||
var _countDown = 0;
|
||||
@@ -42,6 +47,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
EasyLoading.showToast("请填写正确的手机号");
|
||||
return;
|
||||
}
|
||||
sendCodeApi(_telController.text);
|
||||
setState(() {
|
||||
_countDown = 60;
|
||||
});
|
||||
@@ -63,7 +69,29 @@ class _LoginPageState extends State<LoginPage> {
|
||||
EasyLoading.showToast("请填写完整手机号或验证码");
|
||||
return;
|
||||
}
|
||||
context.go(RoutePaths.sHome);
|
||||
try {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
var loginRes = await loginApi(_telController.text, _codeController.text);
|
||||
if (mounted) {
|
||||
UserStore userStore = context.read<UserStore>();
|
||||
|
||||
//设置登录信息l
|
||||
await userStore.setToken(loginRes.accessToken);
|
||||
await userStore.asyncUserInfo();
|
||||
if (!mounted) return;
|
||||
if (userStore.userInfo?.accountType == 1) {
|
||||
context.go(RoutePaths.sHome);
|
||||
} else {
|
||||
context.go(RoutePaths.sHome);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -130,21 +158,25 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 40),
|
||||
height: 50,
|
||||
child: Button(text: "登 录", onPressed: _handSubmit),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
alignment: Alignment.center,
|
||||
child: LoginAgree(
|
||||
value: _agree,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_agree = value!;
|
||||
});
|
||||
},
|
||||
child: Button(
|
||||
text: "登 录",
|
||||
loading: _loading,
|
||||
onPressed: _handSubmit,
|
||||
),
|
||||
),
|
||||
// Container(
|
||||
// width: double.infinity,
|
||||
// margin: EdgeInsets.only(top: 20),
|
||||
// alignment: Alignment.center,
|
||||
// child: LoginAgree(
|
||||
// value: _agree,
|
||||
// onChanged: (value) {
|
||||
// setState(() {
|
||||
// _agree = value!;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SplashPage extends StatefulWidget {
|
||||
const SplashPage({super.key});
|
||||
@@ -19,7 +21,24 @@ class _SplashPageState extends State<SplashPage> {
|
||||
///权限效验初始化
|
||||
void initPermission() async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
context.go(RoutePaths.login);
|
||||
String token = await UserStore.getToken();
|
||||
if (mounted) {
|
||||
// 未登录
|
||||
if (token.isEmpty) {
|
||||
context.go(RoutePaths.login);
|
||||
} else {
|
||||
UserStore userStore = context.read<UserStore>();
|
||||
userStore.setUserInfo();
|
||||
//去学生主页
|
||||
if (userStore.userInfo?.accountType == 1) {
|
||||
context.go(RoutePaths.sHome);
|
||||
} else {
|
||||
context.go(RoutePaths.tHome);
|
||||
}
|
||||
print("执行用户数据同步了");
|
||||
userStore.asyncUserInfo();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.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 'today/s_today_card.dart';
|
||||
@@ -12,6 +14,8 @@ class SHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SHomePageState extends State<SHomePage> {
|
||||
|
||||
|
||||
///刷新状态
|
||||
Future<void> _refresh() async {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
|
||||
@@ -16,7 +16,6 @@ class STodayCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _STodayCardState extends State<STodayCard> {
|
||||
|
||||
///进入自习室
|
||||
void _handleEnterRoom() {
|
||||
context.push(RoutePaths.sRoom);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class UserHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
@@ -34,7 +38,28 @@ class UserHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
PopupMenuButton(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
position: PopupMenuPosition.under,
|
||||
onSelected: (value) {
|
||||
if (value == 1) {
|
||||
UserStore userStore = context.read<UserStore>();
|
||||
userStore.logout();
|
||||
context.go(RoutePaths.login);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text("退出登录", textAlign: TextAlign.center),
|
||||
),
|
||||
],
|
||||
child: IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(RemixIcons.user_line),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'viewmodel/home_view_model.dart';
|
||||
import 'widgets/header.dart';
|
||||
import 'widgets/today_card.dart';
|
||||
|
||||
class THomePage extends StatefulWidget {
|
||||
class THomePage extends StatelessWidget {
|
||||
const THomePage({super.key});
|
||||
|
||||
@override
|
||||
State<THomePage> createState() => _THomePageState();
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => HomeViewModel(),
|
||||
child: const _HomeView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _THomePageState extends State<THomePage> {
|
||||
class _HomeView extends StatelessWidget {
|
||||
const _HomeView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.read<HomeViewModel>();
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
appBar: Header(),
|
||||
body: ListView(
|
||||
padding: EdgeInsets.symmetric(vertical: 20, horizontal: context.pagePadding),
|
||||
children: [
|
||||
TodayCard(),
|
||||
],
|
||||
body: RefreshIndicator(
|
||||
onRefresh: vm.loadData,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 20,
|
||||
horizontal: context.pagePadding,
|
||||
),
|
||||
children: [
|
||||
TodayCard(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
56
lib/pages/teacher/home/viewmodel/home_view_model.dart
Normal file
56
lib/pages/teacher/home/viewmodel/home_view_model.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
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/material.dart';
|
||||
|
||||
class HomeViewModel extends ChangeNotifier {
|
||||
RoomInfoDto? roomInfo;
|
||||
bool loading = true;
|
||||
|
||||
HomeViewModel() {
|
||||
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 = roomInfo!.startTime;
|
||||
final end = roomInfo!.endTime;
|
||||
|
||||
final s = DateTime.parse('2000-01-01 $start:00');
|
||||
final e = DateTime.parse('2000-01-01 $end:00');
|
||||
|
||||
return e.difference(s).inMinutes;
|
||||
}
|
||||
|
||||
///能否进入房间
|
||||
bool get canEnterRoom {
|
||||
final info = roomInfo;
|
||||
if (info == null) return false;
|
||||
|
||||
final now = DateTime.now();
|
||||
|
||||
//开始时间
|
||||
final startTime = parseTime(info.startTime);
|
||||
|
||||
// 当前时间距离开始时间是否超过 5 分钟
|
||||
if (now.isBefore(startTime) && startTime.difference(now).inMinutes > 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:app/providers/user_store.dart';
|
||||
import 'package:app/router/route_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
class Header extends StatelessWidget implements PreferredSizeWidget {
|
||||
@@ -18,6 +22,7 @@ class Header extends StatelessWidget implements PreferredSizeWidget {
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"学光自习室",
|
||||
@@ -31,9 +36,27 @@ class Header extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(RemixIcons.user_line),
|
||||
PopupMenuButton(
|
||||
color: Colors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
position: PopupMenuPosition.under,
|
||||
onSelected: (value) {
|
||||
if (value == 1) {
|
||||
UserStore userStore = context.read<UserStore>();
|
||||
userStore.logout();
|
||||
context.go(RoutePaths.login);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text("退出登录", textAlign: TextAlign.center),
|
||||
),
|
||||
],
|
||||
child: IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(RemixIcons.user_line),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,127 +1,174 @@
|
||||
import 'package:app/pages/teacher/home/viewmodel/home_view_model.dart';
|
||||
import 'package:app/request/dto/room/room_info_dto.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/card/g_card.dart';
|
||||
import 'package:app/widgets/base/config/config.dart';
|
||||
import 'package:app/widgets/base/tag/index.dart';
|
||||
import 'package:app/widgets/base/empty/index.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';
|
||||
|
||||
class TodayCard extends StatelessWidget {
|
||||
class TodayCard extends StatefulWidget {
|
||||
const TodayCard({super.key});
|
||||
|
||||
@override
|
||||
State<TodayCard> createState() => _TodayCardState();
|
||||
}
|
||||
|
||||
class _TodayCardState extends State<TodayCard> {
|
||||
///前往会议室
|
||||
void _goToRoom() {
|
||||
checkPermission(
|
||||
permissions: [Permission.microphone, Permission.camera],
|
||||
onGranted: () {
|
||||
final vm = context.read<HomeViewModel>();
|
||||
context.push(
|
||||
RoutePaths.tRoom,
|
||||
extra: {
|
||||
"roomId": vm.roomInfo!.id,
|
||||
"startTime": vm.roomInfo!.startTime,
|
||||
},
|
||||
);
|
||||
},
|
||||
onDenied: () {
|
||||
EasyLoading.showError("请开启权限");
|
||||
},
|
||||
onPermanentlyDenied: () {
|
||||
EasyLoading.showError("请手动开启麦克风和摄像头权限");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
/// item
|
||||
Widget item({
|
||||
required String title,
|
||||
required String value,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
return Consumer<HomeViewModel>(
|
||||
builder: (context, vm, _) {
|
||||
return GCard(
|
||||
child: Visibility(
|
||||
visible: !vm.loading && vm.roomInfo == null,
|
||||
replacement: Skeletonizer(
|
||||
enabled: vm.loading,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: Theme.of(context).textTheme.labelLarge),
|
||||
Text(value),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Skeleton.replace(
|
||||
replacement: Bone.text(),
|
||||
child: Text(vm.roomInfo?.roomName ?? ""),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
"和学生们一起专注学习、共同进步",
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Text("高三数学冲刺班"),
|
||||
Tag(text: "待开始"),
|
||||
_item(
|
||||
title: "开始时间",
|
||||
value: vm.roomInfo?.startTime ?? "",
|
||||
icon: RemixIcons.time_line,
|
||||
color: Color(0xff2b7efd),
|
||||
),
|
||||
_item(
|
||||
title: "结束时间",
|
||||
value: vm.roomInfo?.endTime ?? "",
|
||||
icon: RemixIcons.group_line,
|
||||
color: Color(0xff00c74f),
|
||||
),
|
||||
_item(
|
||||
title: "时长",
|
||||
value: "${vm.roomMinutes} 分钟",
|
||||
icon: RemixIcons.book_open_line,
|
||||
color: Color(0xffac45fd),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
"和学生们一起专注学习、共同进步",
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
height: 45,
|
||||
child: Button(
|
||||
text: vm.canEnterRoom ? "开始自习室" : "未到开始时间",
|
||||
type: ThemeType.success,
|
||||
// disabled: !vm.canEnterRoom,
|
||||
onPressed: _goToRoom,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Icon(RemixIcons.arrow_right_s_line, size: 30),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Empty(text: "未分配自习室"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _item({
|
||||
required String title,
|
||||
required String value,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
item(
|
||||
title: "开始时间",
|
||||
value: "14:00",
|
||||
icon: RemixIcons.time_line,
|
||||
color: Color(0xff2b7efd),
|
||||
),
|
||||
item(
|
||||
title: "学生人数",
|
||||
value: "8 名",
|
||||
icon: RemixIcons.group_line,
|
||||
color: Color(0xff00c74f),
|
||||
),
|
||||
item(
|
||||
title: "时长",
|
||||
value: "120 分钟",
|
||||
icon: RemixIcons.book_open_line,
|
||||
color: Color(0xffac45fd),
|
||||
),
|
||||
Text(title, style: Theme.of(context).textTheme.labelLarge),
|
||||
Text(value),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
height: 45,
|
||||
child: Button(
|
||||
text: "开始自习室",
|
||||
type: ThemeType.success,
|
||||
onPressed: () {
|
||||
context.push(RoutePaths.tRoom);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'controls/top_bar.dart';
|
||||
import 'view/student_item.dart';
|
||||
import 'view/waiting_start.dart';
|
||||
import 'widgets/status_view.dart';
|
||||
import 'viewmodel/students_view_model.dart';
|
||||
|
||||
class TRoomPage extends StatefulWidget {
|
||||
const TRoomPage({super.key});
|
||||
final int roomId;
|
||||
final String startTime;
|
||||
|
||||
const TRoomPage({
|
||||
super.key,
|
||||
required this.roomId,
|
||||
required this.startTime,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TRoomPage> createState() => _TRoomPageState();
|
||||
@@ -17,37 +23,15 @@ class _TRoomPageState extends State<TRoomPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<StudentsViewModel>(
|
||||
create: (BuildContext context) {
|
||||
return StudentsViewModel();
|
||||
return StudentsViewModel(
|
||||
roomId: widget.roomId,
|
||||
start: widget.startTime,
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Color(0xff2c3032),
|
||||
appBar: TopBar(),
|
||||
body: true
|
||||
? WaitingStart()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Expanded(
|
||||
child: StudentItem(),
|
||||
),
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return SizedBox(
|
||||
height: 250,
|
||||
child: StudentItem(),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
itemCount: 7,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: StatusView(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WaitingStart extends StatefulWidget {
|
||||
const WaitingStart({super.key});
|
||||
|
||||
@override
|
||||
State<WaitingStart> createState() => _WaitingStartState();
|
||||
}
|
||||
|
||||
class _WaitingStartState extends State<WaitingStart> {
|
||||
///剩余秒
|
||||
int _seconds = 0;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
startCountDown();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
///开始倒计时
|
||||
void startCountDown() {
|
||||
//当前时间
|
||||
DateTime now = DateTime.now();
|
||||
//远端时间
|
||||
DateTime remote = DateTime.parse("2025-11-19 17:10:00".replaceFirst(' ', 'T'));
|
||||
setState(() {
|
||||
_seconds = remote.difference(now).inSeconds;
|
||||
});
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
setState(() {
|
||||
_seconds--;
|
||||
});
|
||||
if (_seconds <= 0) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
_start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///倒计时结束开始
|
||||
void _start() {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"未到开播时间,到点后自动开播",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
formatSeconds(_seconds),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,78 @@
|
||||
import 'package:app/data/models/student.dart';
|
||||
import 'package:app/websocket/room_websocket.dart';
|
||||
import 'package:app/request/dto/room/room_user_dto.dart';
|
||||
import 'package:app/request/websocket/room_protocol.dart';
|
||||
import 'package:app/request/websocket/room_websocket.dart';
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class StudentsViewModel extends ChangeNotifier {
|
||||
///学生摄像头列表
|
||||
List<Student> _students = [];
|
||||
|
||||
///房间的基础信息,房间id、房间开始时间
|
||||
final int roomId;
|
||||
late final DateTime startTime;
|
||||
|
||||
StudentsViewModel({required this.roomId, String? start}) {
|
||||
startTime = parseTime(start!);
|
||||
_startRoom();
|
||||
}
|
||||
|
||||
List<Student> get students => _students;
|
||||
|
||||
///是否能开始自习室
|
||||
bool get canEnterRoom {
|
||||
final now = DateTime.now();
|
||||
|
||||
// 如果到了开始时间,则可以进入房间
|
||||
if (now.isAfter(startTime)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///websocket管理
|
||||
late RoomWebSocket _ws;
|
||||
|
||||
StudentsViewModel() {
|
||||
_startRoom();
|
||||
}
|
||||
|
||||
///开始链接房间
|
||||
void _startRoom() {
|
||||
void _startRoom() async {
|
||||
_ws = RoomWebSocket();
|
||||
_ws.connect();
|
||||
//如果socket的token没有,先初始化
|
||||
if (_ws.wsToken.isEmpty) {
|
||||
await _ws.initToken(roomId);
|
||||
}
|
||||
//启动连接
|
||||
await _ws.connect();
|
||||
//进入房间命令
|
||||
_ws.send(RoomCommand.joinRoom);
|
||||
|
||||
//监听各种ws事件
|
||||
_ws.stream.listen((msg) {
|
||||
_handleMessage();
|
||||
if (msg.event == RoomEvent.changeUser) {
|
||||
final list = msg.data['user_list'].map((x) => RoomUserDto.fromJson(x)).toList();
|
||||
onStudentChange(list);
|
||||
}
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///发送命令
|
||||
void _handleMessage() {
|
||||
print("监听webscoket传来的事件");
|
||||
///自习室的开关
|
||||
/// - [isOpen]: 是否开启
|
||||
void toggleRoom({required bool isOpen}) {
|
||||
if (isOpen) {
|
||||
_ws.send(RoomCommand.openRoom);
|
||||
} else {
|
||||
_ws.send(RoomCommand.closeRoom);
|
||||
}
|
||||
}
|
||||
|
||||
///学生人员变化事件,(如加入、退出、掉线)
|
||||
void onStudentChange(List<RoomUserDto> list) {}
|
||||
|
||||
//销毁
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_ws.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
35
lib/pages/teacher/room/widgets/content_view.dart
Normal file
35
lib/pages/teacher/room/widgets/content_view.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'student_item.dart';
|
||||
|
||||
class ContentView extends StatelessWidget {
|
||||
const ContentView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Expanded(
|
||||
child: StudentItem(),
|
||||
),
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return SizedBox(
|
||||
height: 250,
|
||||
child: StudentItem(),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
itemCount: 7,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
114
lib/pages/teacher/room/widgets/status_view.dart
Normal file
114
lib/pages/teacher/room/widgets/status_view.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'content_view.dart';
|
||||
import '../viewmodel/students_view_model.dart';
|
||||
|
||||
class StatusView extends StatefulWidget {
|
||||
const StatusView({super.key});
|
||||
|
||||
@override
|
||||
State<StatusView> createState() => _StatusViewState();
|
||||
}
|
||||
|
||||
class _StatusViewState extends State<StatusView> {
|
||||
///房间状态
|
||||
RoomStatus status = RoomStatus.loading;
|
||||
|
||||
///剩余秒
|
||||
int _seconds = 0;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
void _init() {
|
||||
final vm = context.read<StudentsViewModel>();
|
||||
//如果房间可以开始
|
||||
if (vm.canEnterRoom) {
|
||||
status = RoomStatus.start;
|
||||
} else {
|
||||
status = RoomStatus.waiting;
|
||||
startCountDown();
|
||||
}
|
||||
}
|
||||
|
||||
///开始倒计时
|
||||
void startCountDown() {
|
||||
final vm = context.read<StudentsViewModel>();
|
||||
//当前时间
|
||||
DateTime now = DateTime.now();
|
||||
//远端时间
|
||||
setState(() {
|
||||
_seconds = vm.startTime.difference(now).inSeconds;
|
||||
});
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
setState(() {
|
||||
_seconds--;
|
||||
});
|
||||
if (_seconds <= 0) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
setState(() {
|
||||
status = RoomStatus.start;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///开启自习室
|
||||
void openRoom() {
|
||||
final vm = context.read<StudentsViewModel>();
|
||||
vm.toggleRoom(isOpen: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (status == RoomStatus.waiting) {
|
||||
return Align(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"未到开播时间,到点后自动开播",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
formatSeconds(_seconds),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (status == RoomStatus.start) {
|
||||
return ContentView();
|
||||
}
|
||||
return SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomStatus {
|
||||
loading, // 加载中
|
||||
waiting, //房间倒计时等待中
|
||||
start, //房间开始中
|
||||
}
|
||||
Reference in New Issue
Block a user