自习室优化ok
This commit is contained in:
26
lib/widgets/base/actionSheet/action_sheet.dart
Normal file
26
lib/widgets/base/actionSheet/action_sheet.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'action_sheet_ui.dart';
|
||||
import 'type.dart';
|
||||
|
||||
void showActionSheet(
|
||||
BuildContext context, {
|
||||
required List<ActionSheetItem> actions,
|
||||
bool showCancel = false,
|
||||
required Function(ActionSheetItem) onConfirm,
|
||||
}) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.white,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
builder: (context) {
|
||||
return ActionSheetUi(
|
||||
actions: actions,
|
||||
showCancel: showCancel,
|
||||
onConfirm: onConfirm,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
65
lib/widgets/base/actionSheet/action_sheet_ui.dart
Normal file
65
lib/widgets/base/actionSheet/action_sheet_ui.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'type.dart';
|
||||
|
||||
class ActionSheetUi extends StatelessWidget {
|
||||
final List<ActionSheetItem> actions;
|
||||
final bool showCancel;
|
||||
final double actionHeight = 50;
|
||||
final Function(ActionSheetItem) onConfirm;
|
||||
|
||||
const ActionSheetUi({
|
||||
super.key,
|
||||
required this.actions,
|
||||
this.showCancel = false,
|
||||
required this.onConfirm,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...actions.map((item) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
onConfirm(item);
|
||||
context.pop();
|
||||
},
|
||||
child: Container(
|
||||
height: actionHeight,
|
||||
alignment: Alignment.center,
|
||||
child: Text(item.title),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Visibility(
|
||||
visible: showCancel,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 8,
|
||||
color: const Color.fromRGBO(238, 239, 243, 1.0),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
context.pop();
|
||||
},
|
||||
child: Container(
|
||||
height: actionHeight,
|
||||
alignment: Alignment.center,
|
||||
child: const Text("取消"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/widgets/base/actionSheet/type.dart
Normal file
18
lib/widgets/base/actionSheet/type.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 底部弹窗类型
|
||||
class ActionSheetItem {
|
||||
String title; //标题
|
||||
int value;
|
||||
bool disabled; //是否禁用
|
||||
Color color; //选项颜色
|
||||
Widget? child;
|
||||
|
||||
ActionSheetItem({
|
||||
required this.title,
|
||||
this.value = 0,
|
||||
this.disabled = false,
|
||||
this.color = Colors.black,
|
||||
this.child,
|
||||
});
|
||||
}
|
||||
@@ -6,18 +6,20 @@ import '../config/config.dart';
|
||||
class Button extends StatelessWidget {
|
||||
final double? width;
|
||||
final String text;
|
||||
final TextStyle textStyle;
|
||||
final ThemeType type;
|
||||
final BorderRadius radius;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback? onPressed;
|
||||
final bool loading;
|
||||
final bool disabled;
|
||||
|
||||
const Button({
|
||||
super.key,
|
||||
this.width,
|
||||
this.textStyle = const TextStyle(),
|
||||
this.radius = const BorderRadius.all(Radius.circular(80)),
|
||||
required this.text,
|
||||
required this.onPressed,
|
||||
this.onPressed,
|
||||
this.type = ThemeType.primary,
|
||||
this.loading = false,
|
||||
this.disabled = false,
|
||||
@@ -34,7 +36,7 @@ class Button extends StatelessWidget {
|
||||
};
|
||||
|
||||
return Opacity(
|
||||
opacity: disabled || loading ? 0.5 : 1,
|
||||
opacity: disabled || loading ? 0.5 : 1,
|
||||
child: Container(
|
||||
width: width,
|
||||
decoration: bgDecoration.copyWith(borderRadius: radius),
|
||||
@@ -62,7 +64,7 @@ class Button extends StatelessWidget {
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
style: textStyle.copyWith(
|
||||
color: type != ThemeType.info ? Colors.white : Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
|
||||
@@ -40,6 +40,9 @@ class FilePreviewer extends StatelessWidget {
|
||||
child = InteractiveViewer(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: url,
|
||||
placeholder: (_, __) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (_isPdf(suffix)) {
|
||||
|
||||
93
lib/widgets/room/core/count_down_vm.dart
Normal file
93
lib/widgets/room/core/count_down_vm.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app/data/models/meeting_room_dto.dart';
|
||||
import 'package:app/utils/time.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class CountDownVM extends ChangeNotifier {
|
||||
MeetingRoomDto? roomInfo;
|
||||
|
||||
///会议开始倒计时秒数
|
||||
Timer? _startTime;
|
||||
int _startCountDown = 0;
|
||||
|
||||
///会议结束倒计时秒数
|
||||
Timer? _endTime;
|
||||
int _endCountDown = -1;
|
||||
|
||||
///会议进行中的秒数
|
||||
int studyTime = 0;
|
||||
|
||||
int get startCountDown => _startCountDown;
|
||||
|
||||
int get endCountDown => _endCountDown;
|
||||
|
||||
///是否能开始自习室
|
||||
bool get canEnterRoom {
|
||||
final now = DateTime.now();
|
||||
if (now.isAfter(parseTime(roomInfo!.startTime))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//绑定
|
||||
void bind(MeetingRoomDto info) {
|
||||
if (roomInfo == info) return;
|
||||
roomInfo = info;
|
||||
_startEndCountdown();
|
||||
//如果会议室结束,停止计时器
|
||||
if (roomInfo?.roomStatus == 2) {
|
||||
_endTime?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
///启动距离会议结束还有多少秒
|
||||
void _startEndCountdown() {
|
||||
if (roomInfo!.actualStartTime.isEmpty || roomInfo!.roomStatus != 1) return;
|
||||
_endTime?.cancel();
|
||||
|
||||
DateTime endTime = parseTime(roomInfo!.endTime);
|
||||
DateTime startTime = DateTime.parse(roomInfo!.actualStartTime);
|
||||
|
||||
_endCountDown = endTime.difference(startTime).inSeconds;
|
||||
|
||||
_endTime = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
_endCountDown--;
|
||||
studyTime++;
|
||||
if (_endCountDown <= 0) {
|
||||
_endTime?.cancel();
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
///启动距离会议开始还有多少秒
|
||||
void startStartCountdown() {
|
||||
if (roomInfo?.roomStatus != 0) return;
|
||||
_startTime?.cancel();
|
||||
final now = DateTime.now();
|
||||
final startTime = parseTime(roomInfo!.startTime);
|
||||
|
||||
_startCountDown = startTime.difference(now).inSeconds;
|
||||
if (_startCountDown <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_startTime = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
_startCountDown--;
|
||||
if (_startCountDown <= 0) {
|
||||
_startTime?.cancel();
|
||||
_startTime = null;
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_startTime?.cancel();
|
||||
_endTime?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app/config/theme/base/app_theme_ext.dart';
|
||||
import 'package:app/utils/transfer/upload.dart';
|
||||
import 'package:app/widgets/base/actionSheet/action_sheet.dart';
|
||||
import 'package:app/widgets/base/actionSheet/type.dart';
|
||||
import 'package:app/widgets/base/button/index.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../common/preview/file_previewer.dart';
|
||||
|
||||
@@ -8,6 +16,9 @@ import '../common/preview/file_previewer.dart';
|
||||
void showFileDialog(
|
||||
BuildContext context, {
|
||||
bool isUpload = true,
|
||||
String? name,
|
||||
List<String> files = const [],
|
||||
ValueChanged<List<String>>? onConfirm,
|
||||
}) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
@@ -16,7 +27,10 @@ void showFileDialog(
|
||||
barrierLabel: "RightSheet",
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return FileDrawer(
|
||||
name: name,
|
||||
isUpload: isUpload,
|
||||
files: files,
|
||||
onConfirm: onConfirm,
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
@@ -31,15 +45,99 @@ void showFileDialog(
|
||||
|
||||
///文件弹窗
|
||||
class FileDrawer extends StatefulWidget {
|
||||
final String? name;
|
||||
final List<String> files;
|
||||
final bool isUpload;
|
||||
final ValueChanged<List<String>>? onConfirm;
|
||||
|
||||
const FileDrawer({super.key, this.isUpload = true});
|
||||
const FileDrawer({
|
||||
super.key,
|
||||
this.name,
|
||||
this.isUpload = true,
|
||||
this.files = const [],
|
||||
this.onConfirm,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FileDrawer> createState() => _FileDrawerState();
|
||||
}
|
||||
|
||||
class _FileDrawerState extends State<FileDrawer> {
|
||||
///文件列表
|
||||
List<String> _fileList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fileList = List<String>.from(widget.files);
|
||||
}
|
||||
|
||||
///打开选择面板
|
||||
void _handOpenActionSheet() {
|
||||
showActionSheet(
|
||||
context,
|
||||
showCancel: true,
|
||||
actions: [
|
||||
ActionSheetItem(title: "拍照", value: 1),
|
||||
ActionSheetItem(title: "选择图片", value: 2),
|
||||
ActionSheetItem(title: "选择PDF", value: 3),
|
||||
],
|
||||
onConfirm: (res) async {
|
||||
List<File> filesToUpload = [];
|
||||
try {
|
||||
if (res.value == 1) {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? photo = await picker.pickImage(source: ImageSource.camera);
|
||||
|
||||
if (photo == null) return; // 用户取消
|
||||
filesToUpload.add(File(photo.path));
|
||||
} else if (res.value == 2) {
|
||||
//选择图片
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowMultiple: true,
|
||||
type: FileType.image,
|
||||
);
|
||||
if (result == null) return;
|
||||
filesToUpload.addAll(result.paths.whereType<String>().map((e) => File(e)));
|
||||
} else if (res.value == 3) {
|
||||
//选择pdf
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowMultiple: true,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['pdf'],
|
||||
);
|
||||
if (result == null) return;
|
||||
filesToUpload.addAll(result.paths.whereType<String>().map((e) => File(e)));
|
||||
}
|
||||
if (filesToUpload.isEmpty) return;
|
||||
// 4) 上传文件
|
||||
EasyLoading.show(status: "文件上传中");
|
||||
|
||||
final uploadTasks = filesToUpload.map((file) {
|
||||
return QinUpload.upload(
|
||||
file: file,
|
||||
path: "room/classroom",
|
||||
);
|
||||
});
|
||||
final List<String> uploadedPaths = (await Future.wait(
|
||||
uploadTasks,
|
||||
)).whereType<String>().toList();
|
||||
|
||||
EasyLoading.dismiss();
|
||||
|
||||
// 更新 UI
|
||||
setState(() => _fileList.addAll(uploadedPaths));
|
||||
|
||||
// 回调
|
||||
widget.onConfirm?.call(uploadedPaths);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
EasyLoading.showToast("文件上传失败");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
@@ -52,39 +150,45 @@ class _FileDrawerState extends State<FileDrawer> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'上传文件列表',
|
||||
"${widget.name ?? ""}上传文件列表",
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 15),
|
||||
itemBuilder: (_, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
showFilePreviewer(
|
||||
context,
|
||||
url: "https://doaf.asia/api/assets/1/图/65252305_p0.jpg",
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Visibility(
|
||||
visible: _fileList.isNotEmpty,
|
||||
replacement: Align(
|
||||
child: Text("未上传文件"),
|
||||
),
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 15),
|
||||
itemBuilder: (_, index) {
|
||||
String item = _fileList[index];
|
||||
String suffix = item.split(".").last;
|
||||
return InkWell(
|
||||
key: Key(item),
|
||||
onTap: () {
|
||||
showFilePreviewer(context, url: item);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Text("文件${index + 1}.$suffix", style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
child: Text("文件1.png", style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
itemCount: 15,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
itemCount: _fileList.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: widget.isUpload,
|
||||
child: Button(
|
||||
text: "上传",
|
||||
onPressed: () {},
|
||||
onPressed: _handOpenActionSheet,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -93,3 +197,12 @@ class _FileDrawerState extends State<FileDrawer> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///数据类
|
||||
class UploadFileItem {
|
||||
String url;
|
||||
String name;
|
||||
bool loading;
|
||||
|
||||
UploadFileItem({required this.url, this.loading = false, required this.name});
|
||||
}
|
||||
|
||||
26
lib/widgets/room/other_widget.dart
Normal file
26
lib/widgets/room/other_widget.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HandRaiseButton extends StatelessWidget {
|
||||
final void Function() onTap;
|
||||
const HandRaiseButton({super.key, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black12,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.back_hand_rounded,
|
||||
color: Color(0xFFFDC400),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,52 @@
|
||||
import 'package:app/config/config.dart';
|
||||
import 'package:app/request/dto/room/room_user_dto.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
import '../../request/dto/room/rtc_token_dto.dart';
|
||||
|
||||
/// 视频画面显示状态
|
||||
enum VideoState {
|
||||
/// 正常显示视频
|
||||
normal,
|
||||
|
||||
/// 摄像头关闭
|
||||
closed,
|
||||
|
||||
/// 掉线 / 未连接
|
||||
offline,
|
||||
|
||||
/// 加载中(进房、拉流等)
|
||||
loading,
|
||||
|
||||
/// 错误状态(拉流失败等)
|
||||
error,
|
||||
}
|
||||
|
||||
class VideoSurface extends StatelessWidget {
|
||||
final VideoState state;
|
||||
final RoomUserDto user;
|
||||
final Widget child;
|
||||
|
||||
const VideoSurface({super.key, this.state = VideoState.normal});
|
||||
const VideoSurface({
|
||||
super.key,
|
||||
required this.user,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String stateText = switch (state) {
|
||||
VideoState.closed => "摄像头已关闭",
|
||||
VideoState.offline => "掉线",
|
||||
VideoState.loading => "加载中",
|
||||
VideoState.error => "错误",
|
||||
_ => "未知",
|
||||
};
|
||||
//如果不是正常
|
||||
if (state != VideoState.normal) {
|
||||
//摄像头是否关闭
|
||||
if (user.cameraStatus == 0) {
|
||||
return Align(
|
||||
child: Text(stateText, style: TextStyle(color: Colors.white70)),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 70,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 1),
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: user.avatar,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
if (user.online == 0) {
|
||||
return _empty('暂时离开');
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _empty(String title) {
|
||||
return Center(
|
||||
child: Text(title, style: TextStyle(color: Colors.white70)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
80
lib/widgets/version/version_dialog.dart
Normal file
80
lib/widgets/version/version_dialog.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:app/request/api/common_api.dart';
|
||||
import 'package:app/utils/common.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'version_ui.dart';
|
||||
|
||||
|
||||
///显示版本更新弹窗
|
||||
void showUpdateDialog(BuildContext context) async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
//如果是安卓
|
||||
if (isAndroid()) {
|
||||
//版本信息
|
||||
var versionRes = await getAppVersionApi();
|
||||
|
||||
//比较版本
|
||||
int compareResult = compareVersions(
|
||||
packageInfo.version,
|
||||
versionRes.latestVersion,
|
||||
);
|
||||
if (compareResult > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
//如果是最新版本
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
barrierLabel: "Update",
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: AppUpdateUi(
|
||||
version: versionRes.latestVersion,
|
||||
updateNotice: versionRes.updateContent,
|
||||
uploadUrl: versionRes.downloadUrl,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
var res = await Dio().get("https://itunes.apple.com/lookup?bundleId=com.curainhealth.akhome");
|
||||
Map<String, dynamic> resJson = json.decode(res.data);
|
||||
if (resJson['results'].length == 0) {
|
||||
return;
|
||||
}
|
||||
var newVersion = resJson['results'][0]['version'];
|
||||
//比较版本
|
||||
int compareResult = compareVersions(
|
||||
packageInfo.version,
|
||||
newVersion,
|
||||
);
|
||||
if (compareResult > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
///比较版本号
|
||||
int compareVersions(String version1, String version2) {
|
||||
List<String> v1Parts = version1.split('.');
|
||||
List<String> v2Parts = version2.split('.');
|
||||
int length = v1Parts.length > v2Parts.length ? v1Parts.length : v2Parts.length;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int v1Part = i < v1Parts.length ? int.tryParse(v1Parts[i]) ?? 0 : 0;
|
||||
int v2Part = i < v2Parts.length ? int.tryParse(v2Parts[i]) ?? 0 : 0;
|
||||
|
||||
if (v1Part > v2Part) return 1;
|
||||
if (v1Part < v2Part) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
174
lib/widgets/version/version_ui.dart
Normal file
174
lib/widgets/version/version_ui.dart
Normal file
@@ -0,0 +1,174 @@
|
||||
import 'package:app/widgets/base/button/index.dart';
|
||||
import 'package:app/widgets/base/tag/index.dart';
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../utils/transfer/download.dart';
|
||||
import '../base/config/config.dart';
|
||||
|
||||
///下载状态枚举
|
||||
enum UploadState {
|
||||
notStarted, //未开始下载
|
||||
downloading, //下载中
|
||||
completed, //下载完毕
|
||||
}
|
||||
|
||||
class AppUpdateUi extends StatefulWidget {
|
||||
final String version;
|
||||
final List<String> updateNotice;
|
||||
final String uploadUrl; //下载地址
|
||||
|
||||
const AppUpdateUi({
|
||||
super.key,
|
||||
required this.version,
|
||||
required this.updateNotice,
|
||||
required this.uploadUrl,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AppUpdateUi> createState() => _UpdateUiState();
|
||||
}
|
||||
|
||||
class _UpdateUiState extends State<AppUpdateUi> {
|
||||
int _uploadProgress = 0; //下载进度
|
||||
UploadState _uploadState = UploadState.notStarted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getLocalApk();
|
||||
}
|
||||
|
||||
///读取本地是否有下载记录
|
||||
void getLocalApk() async {
|
||||
String url = await LocalDownload.getFilePath(url: widget.uploadUrl, path: '/apk');
|
||||
if (url.isNotEmpty) {
|
||||
setState(() {
|
||||
_uploadState = UploadState.completed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
///下载apk
|
||||
void _handUploadApk() async {
|
||||
if (_uploadState == UploadState.notStarted) {
|
||||
setState(() {
|
||||
_uploadState = UploadState.downloading;
|
||||
});
|
||||
LocalDownload.downLoadFile(
|
||||
url: widget.uploadUrl,
|
||||
path: "/apk",
|
||||
onProgress: (double double) {
|
||||
setState(() {
|
||||
_uploadProgress = double.toInt();
|
||||
});
|
||||
},
|
||||
onDone: (apk) async {
|
||||
setState(() {
|
||||
_uploadState = UploadState.completed;
|
||||
});
|
||||
AppInstaller.installApk(apk);
|
||||
},
|
||||
);
|
||||
} else if (_uploadState == UploadState.completed) {
|
||||
String url = await LocalDownload.getFilePath(url: widget.uploadUrl, path: '/apk');
|
||||
AppInstaller.installApk(url);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String text;
|
||||
if (_uploadState == UploadState.downloading) {
|
||||
text = "$_uploadProgress%";
|
||||
} else if (_uploadState == UploadState.completed) {
|
||||
text = '安装';
|
||||
} else {
|
||||
text = '立即升级';
|
||||
}
|
||||
return IntrinsicHeight(
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
padding: EdgeInsets.symmetric(horizontal: 40),
|
||||
alignment: Alignment.center,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: 500,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
Image.asset("assets/image/version_bg.png"),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
child: FractionalTranslation(
|
||||
translation: Offset(0.35, 0.3),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("发现新版本"),
|
||||
SizedBox(width: 10),
|
||||
Tag(
|
||||
text: "V ${widget.version}",
|
||||
type: ThemeType.warning,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Material(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20),
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(15),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...widget.updateNotice.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final item = entry.value;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Text("${index + 1}.$item"),
|
||||
);
|
||||
}),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
height: 40,
|
||||
child: Button(
|
||||
text: text,
|
||||
onPressed: _uploadState == UploadState.downloading
|
||||
? null
|
||||
: _handUploadApk,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user