自习室优化ok
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
<application
|
<application
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:label="学光自习室">
|
android:label="学光自习室">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class Config {
|
|||||||
return "wss://xueguang.test.tuzuu.com/ws";
|
return "wss://xueguang.test.tuzuu.com/ws";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///网页域名地址
|
||||||
|
static String get webUrl => "http://xueguang.test.tuzuu.com";
|
||||||
|
|
||||||
///声网APPid
|
///声网APPid
|
||||||
static String get swAppId => "011c2fd2e1854511a80c1aebded4eee7";
|
static String get swAppId => "011c2fd2e1854511a80c1aebded4eee7";
|
||||||
}
|
}
|
||||||
33
lib/global/event_bus.dart
Normal file
33
lib/global/event_bus.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// 全局事件总线
|
||||||
|
class EventBus {
|
||||||
|
EventBus._();
|
||||||
|
|
||||||
|
static final _instance = EventBus._();
|
||||||
|
|
||||||
|
factory EventBus() => _instance;
|
||||||
|
|
||||||
|
|
||||||
|
// StreamController,广播模式可以让多个地方监听
|
||||||
|
final StreamController<GlobalEvent> _controller = StreamController.broadcast();
|
||||||
|
|
||||||
|
/// 发送事件
|
||||||
|
void fire(GlobalEvent event) {
|
||||||
|
_controller.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 监听事件
|
||||||
|
Stream<GlobalEvent> get stream => _controller.stream;
|
||||||
|
|
||||||
|
/// 关闭流(一般应用生命周期结束时调用)
|
||||||
|
void dispose() {
|
||||||
|
_controller.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 事件类型枚举
|
||||||
|
enum GlobalEvent {
|
||||||
|
unauthorized, // 401 需要重新登录
|
||||||
|
// 以后可以扩展其他事件
|
||||||
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:app/global/event_bus.dart';
|
||||||
import 'package:app/providers/user_store.dart';
|
import 'package:app/providers/user_store.dart';
|
||||||
|
import 'package:app/router/route_paths.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:app/router/routes.dart';
|
import 'package:app/router/routes.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'config/theme/theme.dart';
|
import 'global/theme/theme.dart';
|
||||||
|
import 'global/theme/themes/light_theme.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(
|
runApp(
|
||||||
@@ -19,9 +25,26 @@ void main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyApp> createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 1. 监听 401
|
||||||
|
EventBus().stream.listen((event) {
|
||||||
|
if (event == GlobalEvent.unauthorized) {
|
||||||
|
final ctx = navigatorKey.currentState?.context;
|
||||||
|
ctx?.go(RoutePaths.login);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ScreenUtilInit(
|
return ScreenUtilInit(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
||||||
import 'package:app/widgets/version/version_dialog.dart';
|
import 'package:app/widgets/version/version_dialog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
import 'package:app/pages/student/home/viewmodel/s_home_vm.dart';
|
||||||
import 'package:app/router/route_paths.dart';
|
import 'package:app/router/route_paths.dart';
|
||||||
import 'package:app/utils/permission.dart';
|
import 'package:app/utils/permission.dart';
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import 'package:app/pages/student/room/viewmodel/stu_room_vm.dart';
|
||||||
|
import 'package:app/providers/user_store.dart';
|
||||||
import 'package:app/utils/time.dart';
|
import 'package:app/utils/time.dart';
|
||||||
|
import 'package:app/widgets/base/button/index.dart';
|
||||||
import 'package:app/widgets/base/dialog/config_dialog.dart';
|
import 'package:app/widgets/base/dialog/config_dialog.dart';
|
||||||
|
import 'package:app/widgets/room/board/board_manager.dart';
|
||||||
import 'package:app/widgets/room/core/count_down_vm.dart';
|
import 'package:app/widgets/room/core/count_down_vm.dart';
|
||||||
|
import 'package:app/widgets/room/other_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -18,6 +23,8 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final vm = context.watch<StuRoomVM>();
|
||||||
|
final userStore = context.read<UserStore>();
|
||||||
return AppBar(
|
return AppBar(
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
titleTextStyle: const TextStyle(color: Colors.white, fontSize: 18),
|
titleTextStyle: const TextStyle(color: Colors.white, fontSize: 18),
|
||||||
@@ -58,16 +65,38 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
Text(vm.roomInfo!.roomName),
|
Text(vm.roomInfo!.roomName),
|
||||||
Text(
|
Text(
|
||||||
formatSeconds(vm.studyTime),
|
formatSeconds(vm.studyTime),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.white24),
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white24,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
ActionButton(
|
||||||
onPressed: onOther,
|
icon: showOther ? RemixIcons.team_fill : RemixIcons.team_line,
|
||||||
icon: Icon(showOther ? RemixIcons.team_fill : RemixIcons.team_line),
|
text: showOther ? "隐藏学生" : '显示学生',
|
||||||
|
onTap: onOther,
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: vm.roomInfo.roomStatus == 1,
|
||||||
|
child: ActionButton(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
icon: RemixIcons.artboard_line,
|
||||||
|
text: "进入白板",
|
||||||
|
onTap: () {
|
||||||
|
final boardManager = BoardManager();
|
||||||
|
final vm = context.read<StuRoomVM>();
|
||||||
|
boardManager.showBoardDialog(
|
||||||
|
context,
|
||||||
|
uid: userStore.userInfo!.name,
|
||||||
|
roomId: vm.roomInfo.id,
|
||||||
|
isTeacher: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ class TeacherVideo extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 30,
|
||||||
left: 0,
|
left: 10,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
width: 200,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1 / 1.2,
|
aspectRatio: 16 / 9,
|
||||||
child: AgoraVideoView(
|
child: AgoraVideoView(
|
||||||
controller: VideoViewController(
|
controller: VideoViewController(
|
||||||
rtcEngine: vm.engine!,
|
rtcEngine: vm.engine!,
|
||||||
@@ -80,4 +80,4 @@ class TeacherVideo extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||||
import 'package:app/config/config.dart';
|
import 'package:app/global/config.dart';
|
||||||
import 'package:app/data/models/meeting_room_dto.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_list_item_dto.dart';
|
||||||
import 'package:app/request/dto/room/room_info_dto.dart';
|
import 'package:app/request/dto/room/room_info_dto.dart';
|
||||||
@@ -138,8 +138,9 @@ class StuRoomVM extends ChangeNotifier {
|
|||||||
|
|
||||||
///学生人员变化事件,(如加入、退出、掉线)
|
///学生人员变化事件,(如加入、退出、掉线)
|
||||||
void onStudentChange(List<RoomUserDto> list) {
|
void onStudentChange(List<RoomUserDto> list) {
|
||||||
|
final lineList = list.where((t) => t.online == 1);
|
||||||
List<RoomUserDto> newList = [];
|
List<RoomUserDto> newList = [];
|
||||||
for (var t in list) {
|
for (var t in lineList) {
|
||||||
//设置老师
|
//设置老师
|
||||||
if (t.userType == 2) {
|
if (t.userType == 2) {
|
||||||
teacherInfo = t;
|
teacherInfo = t;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:app/widgets/version/version_dialog.dart';
|
import 'package:app/widgets/version/version_dialog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -13,7 +13,7 @@ class THomePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
showUpdateDialog(context);
|
// showUpdateDialog(context);
|
||||||
return ChangeNotifierProvider(
|
return ChangeNotifierProvider(
|
||||||
create: (_) => HomeViewModel(),
|
create: (_) => HomeViewModel(),
|
||||||
child: const _HomeView(),
|
child: const _HomeView(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:app/providers/user_store.dart';
|
import 'package:app/providers/user_store.dart';
|
||||||
import 'package:app/router/route_paths.dart';
|
import 'package:app/router/route_paths.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import 'package:app/global/theme/theme.dart';
|
||||||
|
import 'package:app/providers/user_store.dart';
|
||||||
import 'package:app/utils/time.dart';
|
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:app/widgets/base/dialog/config_dialog.dart';
|
||||||
|
import 'package:app/widgets/room/board/board_manager.dart';
|
||||||
|
import 'package:app/widgets/room/other_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -17,6 +19,7 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final userStore = context.read<UserStore>();
|
||||||
final vm = context.watch<TchRoomVM>();
|
final vm = context.watch<TchRoomVM>();
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
@@ -49,28 +52,35 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
_actionButton(
|
ActionButton(
|
||||||
context,
|
|
||||||
icon: RemixIcons.video_on_ai_line,
|
icon: RemixIcons.video_on_ai_line,
|
||||||
title: "关闭全部",
|
text: "关闭全部",
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
_closeAll(context, StudentAction.camera);
|
_closeAll(context, StudentAction.camera);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_actionButton(
|
ActionButton(
|
||||||
context,
|
|
||||||
icon: RemixIcons.volume_up_line,
|
icon: RemixIcons.volume_up_line,
|
||||||
title: "全部静音",
|
text: "全部静音",
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
_closeAll(context, StudentAction.speaker);
|
_closeAll(context, StudentAction.speaker);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Container(
|
Visibility(
|
||||||
margin: EdgeInsets.only(right: 15),
|
visible: vm.roomInfo.roomStatus == 1,
|
||||||
child: Button(
|
child: ActionButton(
|
||||||
text: "白板",
|
text: "打开白板",
|
||||||
textStyle: TextStyle(fontSize: 14),
|
color: Theme.of(context).primaryColor,
|
||||||
onPressed: (){},
|
icon: RemixIcons.artboard_line,
|
||||||
|
onTap: () {
|
||||||
|
final boardManager = BoardManager();
|
||||||
|
boardManager.showBoardDialog(
|
||||||
|
context,
|
||||||
|
uid: userStore.userInfo!.name,
|
||||||
|
roomId: vm.roomInfo.id,
|
||||||
|
isTeacher: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Consumer<TchRoomVM>(
|
Consumer<TchRoomVM>(
|
||||||
@@ -78,11 +88,11 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
if (vm.roomInfo.roomStatus != 1) {
|
if (vm.roomInfo.roomStatus != 1) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
return Button(
|
return ActionButton(
|
||||||
type: ThemeType.danger,
|
icon: RemixIcons.close_line,
|
||||||
textStyle: TextStyle(fontSize: 14),
|
color: context.danger,
|
||||||
text: "结束自习室",
|
text: "结束自习室",
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
@@ -103,12 +113,15 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(width: 10),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _infoItem(BuildContext context, {required String title, required IconData icon}) {
|
Widget _infoItem(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required IconData icon,
|
||||||
|
}) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: Colors.white54, size: 14),
|
Icon(icon, color: Colors.white54, size: 14),
|
||||||
@@ -118,32 +131,6 @@ class TopBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
void _closeAll(BuildContext context, StudentAction action) {
|
||||||
final vm = context.read<TchRoomVM>();
|
final vm = context.read<TchRoomVM>();
|
||||||
String content = (action == StudentAction.camera) ? '是否关闭所有学生的摄像头?' : '是否关闭所有学生的扬声器?';
|
String content = (action == StudentAction.camera) ? '是否关闭所有学生的摄像头?' : '是否关闭所有学生的扬声器?';
|
||||||
|
|||||||
@@ -176,7 +176,13 @@ class TchRoomVM extends ChangeNotifier {
|
|||||||
|
|
||||||
///学生人员变化事件,(如加入、退出、掉线)
|
///学生人员变化事件,(如加入、退出、掉线)
|
||||||
void onStudentChange(List<RoomUserDto> list) {
|
void onStudentChange(List<RoomUserDto> list) {
|
||||||
_students = list.where((t) => t.userType != 2).toList();
|
_students = list.where((t) => t.userType != 2 && t.online == 1).toList();
|
||||||
|
if (activeSId != 0) {
|
||||||
|
final it = _students.where((t) => t.userId == activeSId).firstOrNull;
|
||||||
|
if (it == null) {
|
||||||
|
activeSId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 如果当前没有学生,则选择第一个
|
// 如果当前没有学生,则选择第一个
|
||||||
if (activeSId == 0 && _students.isNotEmpty) {
|
if (activeSId == 0 && _students.isNotEmpty) {
|
||||||
activeSId = _students.first.userId;
|
activeSId = _students.first.userId;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||||
import 'package:app/config/config.dart';
|
import 'package:app/global/config.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
@@ -109,13 +109,13 @@ class _ContentViewState extends State<ContentView> {
|
|||||||
engine: _engine,
|
engine: _engine,
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 10,
|
||||||
left: 0,
|
right: 10,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
width: 200,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1 / 1.2,
|
aspectRatio: 16 / 9,
|
||||||
child: AgoraVideoView(
|
child: AgoraVideoView(
|
||||||
controller: VideoViewController(
|
controller: VideoViewController(
|
||||||
rtcEngine: _engine!,
|
rtcEngine: _engine!,
|
||||||
@@ -128,21 +128,24 @@ class _ContentViewState extends State<ContentView> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
Visibility(
|
||||||
width: 300,
|
visible: otherStudents.isNotEmpty,
|
||||||
child: ListView.separated(
|
child: SizedBox(
|
||||||
itemBuilder: (_, index) {
|
width: 300,
|
||||||
var item = otherStudents.elementAt(index);
|
child: ListView.separated(
|
||||||
return SizedBox(
|
itemBuilder: (_, index) {
|
||||||
height: 250,
|
var item = otherStudents.elementAt(index);
|
||||||
child: StudentItem(
|
return SizedBox(
|
||||||
user: item,
|
height: 250,
|
||||||
engine: _engine,
|
child: StudentItem(
|
||||||
),
|
user: item,
|
||||||
);
|
engine: _engine,
|
||||||
},
|
),
|
||||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
);
|
||||||
itemCount: otherStudents.length,
|
},
|
||||||
|
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||||
|
itemCount: otherStudents.length,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ class StudentItem extends StatefulWidget {
|
|||||||
|
|
||||||
class _StudentItemState extends State<StudentItem> {
|
class _StudentItemState extends State<StudentItem> {
|
||||||
///打开文件列表
|
///打开文件列表
|
||||||
void _openFileList() {
|
void _openFileList({required String name}) {
|
||||||
showFileDialog(
|
showFileDialog(
|
||||||
context,
|
context,
|
||||||
|
name: name,
|
||||||
isUpload: false,
|
isUpload: false,
|
||||||
files: widget.user.filesList,
|
files: widget.user.filesList,
|
||||||
);
|
);
|
||||||
@@ -160,7 +161,12 @@ class _StudentItemState extends State<StudentItem> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_actionItem(icon: RemixIcons.file_list_3_fill, onTap: _openFileList),
|
_actionItem(
|
||||||
|
icon: RemixIcons.file_list_3_fill,
|
||||||
|
onTap: (){
|
||||||
|
_openFileList(name: widget.user.userName);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ class UserStore extends ChangeNotifier {
|
|||||||
UserInfoDto? userInfo;
|
UserInfoDto? userInfo;
|
||||||
String token = "";
|
String token = "";
|
||||||
|
|
||||||
Future<void> init() async{
|
Future<void> init() async {
|
||||||
token = await getToken();
|
token = await getToken();
|
||||||
await setUserInfo();
|
await setUserInfo();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,4 +41,10 @@ class UserStore extends ChangeNotifier {
|
|||||||
token = '';
|
token = '';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///强制退出(不调用接口、不通知 UI)
|
||||||
|
static Future<void> forceLogout() async {
|
||||||
|
await Storage.remove('token');
|
||||||
|
await Storage.remove('user_info');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
import 'package:app/request/dto/room/board_token_dto.dart';
|
||||||
import 'package:app/request/dto/room/rtc_token_dto.dart';
|
import 'package:app/request/dto/room/rtc_token_dto.dart';
|
||||||
import 'package:app/request/network/request.dart';
|
import 'package:app/request/network/request.dart';
|
||||||
|
|
||||||
import '../dto/room/room_list_item_dto.dart';
|
import '../dto/room/room_list_item_dto.dart';
|
||||||
|
|
||||||
/// 获取房间列表
|
/// 获取房间列表
|
||||||
Future<List<RoomListItemDto >> getRoomListApi() async {
|
Future<List<RoomListItemDto>> getRoomListApi() async {
|
||||||
var res = await Request().get('/study_room/get_study_room_list');
|
var res = await Request().get('/study_room/get_study_room_list');
|
||||||
return List<RoomListItemDto >.from(res.map((x) => RoomListItemDto .fromJson(x)));
|
return List<RoomListItemDto>.from(res.map((x) => RoomListItemDto.fromJson(x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
///获取自习室的websocket令牌
|
///获取自习室的websocket令牌
|
||||||
@@ -24,3 +25,11 @@ Future<RtcTokenDto> getRtcTokenApi(int roomId) async {
|
|||||||
});
|
});
|
||||||
return RtcTokenDto.fromJson(res);
|
return RtcTokenDto.fromJson(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///获取白板的令牌
|
||||||
|
Future<BoardTokenDto> getBoardTokenApi(int roomId) async {
|
||||||
|
var res = await Request().get('/study_room/get_whiteboard_token', {
|
||||||
|
"study_room_id": roomId,
|
||||||
|
});
|
||||||
|
return BoardTokenDto.fromJson(res);
|
||||||
|
}
|
||||||
|
|||||||
32
lib/request/dto/room/board_token_dto.dart
Normal file
32
lib/request/dto/room/board_token_dto.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
class BoardTokenDto {
|
||||||
|
BoardTokenDto({
|
||||||
|
required this.expiresAt,
|
||||||
|
required this.whiteboardAppid,
|
||||||
|
required this.whiteboardUuid,
|
||||||
|
required this.expiresIn,
|
||||||
|
required this.whiteboardToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
DateTime expiresAt;
|
||||||
|
String whiteboardAppid;
|
||||||
|
String whiteboardUuid;
|
||||||
|
int expiresIn;
|
||||||
|
String whiteboardToken;
|
||||||
|
|
||||||
|
factory BoardTokenDto.fromJson(Map<dynamic, dynamic> json) => BoardTokenDto(
|
||||||
|
expiresAt: DateTime.parse(json["expires_at"]),
|
||||||
|
whiteboardAppid: json["whiteboard_appid"],
|
||||||
|
whiteboardUuid: json["whiteboard_uuid"],
|
||||||
|
expiresIn: json["expires_in"],
|
||||||
|
whiteboardToken: json["whiteboard_token"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<dynamic, dynamic> toJson() => {
|
||||||
|
"expires_at": expiresAt.toIso8601String(),
|
||||||
|
"whiteboard_appid": whiteboardAppid,
|
||||||
|
"whiteboard_uuid": whiteboardUuid,
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
"whiteboard_token": whiteboardToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,34 +1,46 @@
|
|||||||
|
import 'package:app/global/event_bus.dart';
|
||||||
import 'package:app/providers/user_store.dart';
|
import 'package:app/providers/user_store.dart';
|
||||||
|
import 'package:app/router/route_paths.dart';
|
||||||
|
import 'package:app/router/routes.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../dto/base_dto.dart';
|
import '../dto/base_dto.dart';
|
||||||
|
|
||||||
///请求拦截器
|
///请求拦截器
|
||||||
void onRequest(
|
void onRequest(RequestOptions options,
|
||||||
RequestOptions options,
|
RequestInterceptorHandler handler,) async {
|
||||||
RequestInterceptorHandler handler,
|
|
||||||
) async {
|
|
||||||
String token = await UserStore.getToken();
|
String token = await UserStore.getToken();
|
||||||
options.headers['Authorization'] = 'Bearer $token';
|
options.headers['Authorization'] = 'Bearer $token';
|
||||||
return handler.next(options);
|
return handler.next(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
///响应拦截器
|
///响应拦截器
|
||||||
void onResponse(
|
void onResponse(Response<dynamic> response,
|
||||||
Response<dynamic> response,
|
ResponseInterceptorHandler handler,) async {
|
||||||
ResponseInterceptorHandler handler,
|
|
||||||
) {
|
|
||||||
var apiResponse = ApiDto.fromJson(response.data);
|
var apiResponse = ApiDto.fromJson(response.data);
|
||||||
if (apiResponse.code == 1) {
|
if (apiResponse.code == 1) {
|
||||||
response.data = apiResponse.data;
|
response.data = apiResponse.data;
|
||||||
handler.next(response);
|
handler.next(response);
|
||||||
|
} else if (apiResponse.code == 401) {
|
||||||
|
// final bus = EventBus();
|
||||||
|
// bus.fire(GlobalEvent.unauthorized);
|
||||||
|
// final context = navigatorKey.currentState?.context;
|
||||||
|
// print("dsd");
|
||||||
|
// if (context != null) {
|
||||||
|
// // UserStore userStore = context.read<UserStore>();
|
||||||
|
// // userStore.logout();
|
||||||
|
// context.go(RoutePaths.login);
|
||||||
|
// }
|
||||||
|
handler.next(response);
|
||||||
} else {
|
} else {
|
||||||
handler.reject(
|
handler.reject(
|
||||||
DioException(
|
DioException(
|
||||||
requestOptions: response.requestOptions,
|
requestOptions: response.requestOptions,
|
||||||
response: response,
|
response: response,
|
||||||
error: {'code': 0, 'message': apiResponse.message},
|
error: {'code': apiResponse.code, 'message': apiResponse.message},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
showError(apiResponse.message);
|
showError(apiResponse.message);
|
||||||
@@ -36,15 +48,18 @@ void onResponse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
///错误响应
|
///错误响应
|
||||||
void onError(
|
void onError(DioException e,
|
||||||
DioException e,
|
ErrorInterceptorHandler handler,) async {
|
||||||
ErrorInterceptorHandler handler,
|
|
||||||
) {
|
|
||||||
var title = "";
|
var title = "";
|
||||||
if (e.type == DioExceptionType.connectionTimeout) {
|
if (e.type == DioExceptionType.connectionTimeout) {
|
||||||
title = "请求超时";
|
title = "请求超时";
|
||||||
} else if (e.type == DioExceptionType.badResponse) {
|
} else if (e.type == DioExceptionType.badResponse) {
|
||||||
if (e.response?.statusCode == 404) {
|
if (e.response?.statusCode == 401) {
|
||||||
|
final bus = EventBus();
|
||||||
|
bus.fire(GlobalEvent.unauthorized);
|
||||||
|
title = "登录信息已失效,请重新登录";
|
||||||
|
}
|
||||||
|
else if (e.response?.statusCode == 404) {
|
||||||
title = "接口404不存在";
|
title = "接口404不存在";
|
||||||
} else {
|
} else {
|
||||||
title = "500";
|
title = "500";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:app/config/config.dart';
|
import 'package:app/global/config.dart';
|
||||||
|
|
||||||
import 'interceptor.dart';
|
import 'interceptor.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:app/config/config.dart';
|
import 'package:app/global/config.dart';
|
||||||
import 'package:app/request/api/room_api.dart';
|
import 'package:app/request/api/room_api.dart';
|
||||||
import 'package:app/request/websocket/room_protocol.dart';
|
import 'package:app/request/websocket/room_protocol.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:app/router/modules/student_routes.dart';
|
|||||||
import 'package:app/router/modules/teacher_routes.dart';
|
import 'package:app/router/modules/teacher_routes.dart';
|
||||||
import 'package:app/router/route_paths.dart';
|
import 'package:app/router/route_paths.dart';
|
||||||
import 'package:app/router/router_config.dart';
|
import 'package:app/router/router_config.dart';
|
||||||
|
import 'package:flutter/material.dart' hide RouterConfig;
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
List<RouterConfig> routeConfigs = [
|
List<RouterConfig> routeConfigs = [
|
||||||
@@ -21,8 +22,12 @@ List<RouteBase> routes = routeConfigs.map((item) {
|
|||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
|
||||||
//变量命名
|
//变量命名
|
||||||
GoRouter goRouter = GoRouter(
|
GoRouter goRouter = GoRouter(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
initialLocation: RoutePaths.splash,
|
initialLocation: RoutePaths.splash,
|
||||||
routes: routes,
|
routes: routes,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app/config/config.dart';
|
import 'package:app/global/config.dart';
|
||||||
import 'package:app/request/api/common_api.dart';
|
import 'package:app/request/api/common_api.dart';
|
||||||
import 'package:app/request/dto/common/qiu_token_dto.dart';
|
import 'package:app/request/dto/common/qiu_token_dto.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'package:app/config/theme/theme.dart';
|
import 'package:app/global/theme/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../config/config.dart';
|
import '../config/config.dart';
|
||||||
|
|
||||||
|
|
||||||
class Button extends StatelessWidget {
|
class Button extends StatelessWidget {
|
||||||
final double? width;
|
final double? width;
|
||||||
final String text;
|
final String text;
|
||||||
@@ -12,14 +13,16 @@ class Button extends StatelessWidget {
|
|||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final bool loading;
|
final bool loading;
|
||||||
final bool disabled;
|
final bool disabled;
|
||||||
|
final Widget? icon;
|
||||||
|
|
||||||
const Button({
|
const Button({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.icon,
|
||||||
this.width,
|
this.width,
|
||||||
this.textStyle = const TextStyle(),
|
this.textStyle = const TextStyle(),
|
||||||
this.radius = const BorderRadius.all(Radius.circular(80)),
|
this.radius = const BorderRadius.all(Radius.circular(80)),
|
||||||
required this.text,
|
required this.text,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
this.type = ThemeType.primary,
|
this.type = ThemeType.primary,
|
||||||
this.loading = false,
|
this.loading = false,
|
||||||
this.disabled = false,
|
this.disabled = false,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GCard extends StatelessWidget {
|
class GCard extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../config/color.dart';
|
import '../config/color.dart';
|
||||||
import '../config/config.dart';
|
import '../config/config.dart';
|
||||||
|
|
||||||
|
|
||||||
class Tag extends StatelessWidget {
|
class Tag extends StatelessWidget {
|
||||||
final String text;
|
final String text;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|||||||
73
lib/widgets/room/board/board_dialog.dart
Normal file
73
lib/widgets/room/board/board_dialog.dart
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:remixicon/remixicon.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class BoardDialog extends StatefulWidget {
|
||||||
|
final WebViewController controller;
|
||||||
|
const BoardDialog({super.key, required this.controller});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardDialog> createState() => _BoardDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardDialogState extends State<BoardDialog> {
|
||||||
|
bool _loading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// 绑定加载事件
|
||||||
|
widget.controller.setNavigationDelegate(
|
||||||
|
NavigationDelegate(
|
||||||
|
onPageStarted: (_) {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
},
|
||||||
|
onPageFinished: (_) {
|
||||||
|
setState(() => _loading = false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('白板', style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
icon: const Icon(RemixIcons.close_circle_fill),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
WebViewWidget(
|
||||||
|
controller: widget.controller,
|
||||||
|
),
|
||||||
|
if (_loading)
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
101
lib/widgets/room/board/board_manager.dart
Normal file
101
lib/widgets/room/board/board_manager.dart
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:app/global/config.dart';
|
||||||
|
import 'package:app/request/api/room_api.dart';
|
||||||
|
import 'package:app/request/dto/room/board_token_dto.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
|
import 'board_dialog.dart';
|
||||||
|
|
||||||
|
class BoardManager {
|
||||||
|
BoardManager._();
|
||||||
|
|
||||||
|
static final BoardManager _instance = BoardManager._();
|
||||||
|
|
||||||
|
factory BoardManager() => _instance;
|
||||||
|
|
||||||
|
WebViewController? _webViewController;
|
||||||
|
BoardTokenDto? _boardToken;
|
||||||
|
|
||||||
|
///获取token
|
||||||
|
Future<void> _fetchToken(int roomId) async {
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
if (_boardToken == null) {
|
||||||
|
EasyLoading.show(status: "开启白板中");
|
||||||
|
_boardToken = await getBoardTokenApi(roomId);
|
||||||
|
} else {
|
||||||
|
//离过期差多少小时
|
||||||
|
int remainSeconds = _boardToken!.expiresAt.difference(now).inSeconds;
|
||||||
|
// 剩余不足 2 小时 = 7200 秒
|
||||||
|
if (remainSeconds <= 7200) {
|
||||||
|
EasyLoading.show(status: "开启白板中");
|
||||||
|
_boardToken = await getBoardTokenApi(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
///生成完整 URL
|
||||||
|
/// -[uid] 用户
|
||||||
|
/// -[roomId] 房间id
|
||||||
|
/// -[isTeacher] 是否是老师
|
||||||
|
Future<String> buildUrl({
|
||||||
|
required String uid,
|
||||||
|
required int roomId,
|
||||||
|
required bool isTeacher,
|
||||||
|
}) async {
|
||||||
|
await _fetchToken(roomId);
|
||||||
|
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
"appid": _boardToken!.whiteboardAppid,
|
||||||
|
"uid": uid,
|
||||||
|
"uuid": _boardToken!.whiteboardUuid,
|
||||||
|
"room_token": _boardToken!.whiteboardToken,
|
||||||
|
"write": isTeacher ? 1 : 0,
|
||||||
|
};
|
||||||
|
String paramStr = params.entries.map((e) => "${e.key}=${e.value}").join("&");
|
||||||
|
|
||||||
|
return "${Config.webUrl}/board?$paramStr";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取全局唯一 WebViewController
|
||||||
|
Future<WebViewController> getController(String url) async {
|
||||||
|
if (_webViewController != null) return _webViewController!;
|
||||||
|
|
||||||
|
_webViewController = WebViewController()
|
||||||
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
|
..setNavigationDelegate(
|
||||||
|
// ⭐ 加上加载监听(BoardDialog 再 listen 一次也没关系)
|
||||||
|
NavigationDelegate(
|
||||||
|
onPageStarted: (_) {},
|
||||||
|
onPageFinished: (_) {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..loadRequest(Uri.parse(url));
|
||||||
|
|
||||||
|
return _webViewController!;
|
||||||
|
}
|
||||||
|
|
||||||
|
///打开白板
|
||||||
|
/// -[uid] 用户名+id
|
||||||
|
/// -[roomId] 房间id
|
||||||
|
/// -[isTeacher] 是否是老师
|
||||||
|
Future<void> showBoardDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
required String uid,
|
||||||
|
required int roomId,
|
||||||
|
required bool isTeacher,
|
||||||
|
}) async {
|
||||||
|
String url = await buildUrl(
|
||||||
|
uid: uid,
|
||||||
|
roomId: roomId,
|
||||||
|
isTeacher: isTeacher,
|
||||||
|
);
|
||||||
|
final controller = await getController(url);
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => BoardDialog(controller: controller),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
import 'package:app/global/theme/base/app_theme_ext.dart';
|
||||||
import 'package:app/utils/transfer/upload.dart';
|
import 'package:app/utils/transfer/upload.dart';
|
||||||
import 'package:app/widgets/base/actionSheet/action_sheet.dart';
|
import 'package:app/widgets/base/actionSheet/action_sheet.dart';
|
||||||
import 'package:app/widgets/base/actionSheet/type.dart';
|
import 'package:app/widgets/base/actionSheet/type.dart';
|
||||||
@@ -150,7 +150,7 @@ class _FileDrawerState extends State<FileDrawer> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"${widget.name ?? ""}上传文件列表",
|
"${widget.name ?? ""}文件列表",
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// 举手按钮
|
||||||
class HandRaiseButton extends StatelessWidget {
|
class HandRaiseButton extends StatelessWidget {
|
||||||
final void Function() onTap;
|
final void Function() onTap;
|
||||||
|
|
||||||
const HandRaiseButton({super.key, required this.onTap});
|
const HandRaiseButton({super.key, required this.onTap});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -24,3 +26,41 @@ class HandRaiseButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///topBar的操作按钮
|
||||||
|
class ActionButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String text;
|
||||||
|
final Color? color;
|
||||||
|
final void Function()? onTap;
|
||||||
|
|
||||||
|
const ActionButton({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.text,
|
||||||
|
this.color,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
margin: EdgeInsets.only(right: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color ?? Color(0xff4a4f4f),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 16),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(text, style: TextStyle(fontSize: 14)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
32
pubspec.lock
32
pubspec.lock
@@ -898,6 +898,38 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
webview_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: webview_flutter
|
||||||
|
sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.13.0"
|
||||||
|
webview_flutter_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_android
|
||||||
|
sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.10.1"
|
||||||
|
webview_flutter_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_platform_interface
|
||||||
|
sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.14.0"
|
||||||
|
webview_flutter_wkwebview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_wkwebview
|
||||||
|
sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.23.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ dependencies:
|
|||||||
app_installer: ^1.3.1
|
app_installer: ^1.3.1
|
||||||
wakelock_plus: ^1.3.3
|
wakelock_plus: ^1.3.3
|
||||||
image_picker: ^1.2.0
|
image_picker: ^1.2.0
|
||||||
|
webview_flutter: ^4.13.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user