488 lines
17 KiB
Dart
488 lines
17 KiB
Dart
import 'dart:developer';
|
|
import 'dart:io';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:elysia/plugin/HTTP.dart';
|
|
import 'package:elysia/plugin/Remind.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'dart:async';
|
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import '../../plugin/ApkInstaller.dart';
|
|
import '../../plugin/C.dart';
|
|
import 'TargetLog.dart';
|
|
|
|
class UpdateCheckPage extends StatefulWidget {
|
|
@override
|
|
_UpdateCheckPageState createState() => _UpdateCheckPageState();
|
|
}
|
|
|
|
class _UpdateCheckPageState extends State<UpdateCheckPage> {
|
|
String _currentVersion = '0';
|
|
int _currentVersionCode = 0;
|
|
bool _isChecking = false;
|
|
bool _hasUpdate = false;
|
|
String _latestVersion = '0';
|
|
String _changelog = '';
|
|
double _downloadProgress = 0.0;
|
|
bool _isDownloading = false;
|
|
|
|
int _downloadCode = 0;
|
|
String _downloadUrl = "";
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_getVersionInfo();
|
|
Future.delayed(Duration(milliseconds: 200), () {
|
|
_checkForUpdate();
|
|
});
|
|
}
|
|
|
|
Future<void> _checkForUpdate() async {
|
|
setState(() {
|
|
_isChecking = true;
|
|
});
|
|
|
|
try {
|
|
dynamic json = await HTTP.create("${C.BASE_URL}/server/latest-version").execute();
|
|
|
|
if (json["code"] != 200) {
|
|
setState(() {
|
|
_isChecking = false;
|
|
_hasUpdate = false;
|
|
});
|
|
} else {
|
|
_hasUpdate = int.parse(json["data"]["versionCode"].toString()) > _currentVersionCode;
|
|
|
|
if (_hasUpdate) {
|
|
_latestVersion = json["data"]["version"];
|
|
_downloadCode = json["data"]["versionCode"];
|
|
_downloadUrl = json["data"]["url"];
|
|
_changelog = json["data"]["updateInfo"];
|
|
}
|
|
|
|
setState(() {
|
|
_isChecking = false;
|
|
});
|
|
|
|
// ✅检查本地文件是否已存在
|
|
if (_hasUpdate) {
|
|
bool exist = await _isLocalFileExist(_downloadCode);
|
|
if (exist) {
|
|
setState(() {
|
|
_downloadProgress = 1.0;
|
|
_isDownloading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (_) {
|
|
setState(() {
|
|
_isChecking = false;
|
|
_hasUpdate = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _startDownload() async {
|
|
setState(() {
|
|
_downloadProgress = 0.0;
|
|
_isDownloading = true;
|
|
});
|
|
|
|
try {
|
|
final savePath = await _getLocalFilePath(_downloadCode);
|
|
log(savePath);
|
|
|
|
await Dio().download(
|
|
_downloadUrl,
|
|
savePath,
|
|
onReceiveProgress: (received, total) {
|
|
if (total != -1) {
|
|
double progress = received / total;
|
|
setState(() {
|
|
_downloadProgress = progress;
|
|
});
|
|
}
|
|
},
|
|
);
|
|
|
|
setState(() {
|
|
_isDownloading = false;
|
|
_downloadProgress = 1.0;
|
|
});
|
|
|
|
print('下载完成,文件保存在: $savePath');
|
|
_installApk();
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) {
|
|
print('下载已取消');
|
|
} else {
|
|
print('下载失败: $e');
|
|
}
|
|
setState(() {
|
|
_isDownloading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _installApk() async {
|
|
final path = await _getLocalFilePath(_downloadCode);
|
|
if (File(path).existsSync()) {
|
|
Remind.show(context, "下载完成", "是否安装?", cancel: "取消", confirm: "现在安装", onTap: (s) async {
|
|
if (!s) return;
|
|
log(path);
|
|
await ApkInstaller.installApk(path);
|
|
});
|
|
} else {
|
|
_startDownload();
|
|
}
|
|
}
|
|
|
|
Future<String> _getLocalFilePath(int code) async {
|
|
final directory = await getTemporaryDirectory();
|
|
String path = '${directory.path}/download/';
|
|
final Directory folder = Directory(path);
|
|
if (!(await folder.exists())) {
|
|
await folder.create(recursive: true);
|
|
}
|
|
return '${directory.path}/download/$code.apk';
|
|
}
|
|
|
|
Future<bool> _isLocalFileExist(int code) async {
|
|
final path = await _getLocalFilePath(code);
|
|
return File(path).existsSync();
|
|
}
|
|
|
|
|
|
Future<void> _getVersionInfo() async {
|
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
setState(() {
|
|
_currentVersion = packageInfo.version;
|
|
_currentVersionCode = int.tryParse(packageInfo.buildNumber) ?? 0;
|
|
});
|
|
}
|
|
|
|
static const Duration kFadeDuration = Duration(milliseconds: 300);
|
|
static const Duration kSwitchDuration = Duration(milliseconds: 350);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (didPop, result) async {
|
|
if (didPop) {
|
|
return;
|
|
}
|
|
if (_isDownloading) {
|
|
Remind.show(context, "退出", "退出后将取消下载,是否退出?", onTap: (s) {
|
|
if (s) {
|
|
Navigator.pop(context);
|
|
}
|
|
});
|
|
} else {
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
child: Scaffold(
|
|
backgroundColor: Colors.grey[50],
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.grey[100],
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
|
onPressed: () {
|
|
if (_isDownloading) {
|
|
Remind.show(context, "退出", "退出后将取消下载,是否退出?", onTap: (s) {
|
|
if (s) {
|
|
Navigator.pop(context);
|
|
}
|
|
});
|
|
} else {
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
),
|
|
title: Text(
|
|
"检查更新",
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
|
|
),
|
|
actions: [
|
|
Padding(
|
|
padding: EdgeInsets.only(right: 10),
|
|
child: GestureDetector(
|
|
child: Icon(Icons.info_outline, color: Colors.black),
|
|
onTap: () {
|
|
Remind.showWidget(context, 'V${_currentVersion}(${_currentVersionCode})',TargetLog(),showCancel: false);
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
_buildStatusCard(),
|
|
SizedBox(height: 16),
|
|
AnimatedSwitcher(
|
|
duration: kSwitchDuration,
|
|
transitionBuilder: (child, animation) {
|
|
return FadeTransition(
|
|
opacity: animation,
|
|
child: SlideTransition(
|
|
position: Tween<Offset>(begin: Offset(0, 0.03), end: Offset.zero).animate(animation),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: (_hasUpdate && !_isChecking)
|
|
? _buildUpdateContent(key: ValueKey('updateContent'))
|
|
: SizedBox(key: ValueKey('noUpdateContent'), height: 0),
|
|
),
|
|
AnimatedSwitcher(
|
|
duration: kSwitchDuration,
|
|
transitionBuilder: (child, animation) {
|
|
return FadeTransition(
|
|
opacity: animation,
|
|
child: SizeTransition(
|
|
axisAlignment: 1,
|
|
sizeFactor: animation,
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: _isDownloading ? _buildDownloadProgress(key: ValueKey('downloading')) : SizedBox(key: ValueKey('nodownload'), height: 0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusCard() {
|
|
return Container(
|
|
margin: EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue[50],
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: Colors.blue[100]!),
|
|
),
|
|
child: Center(
|
|
child: Image.asset('assets/app_icon.png', width: 70, height: 70, fit: BoxFit.cover),
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
Text('当前版本', style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
|
SizedBox(height: 4),
|
|
Text('V${_currentVersion}(${_currentVersionCode})',
|
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87)),
|
|
],
|
|
),
|
|
),
|
|
Divider(height: 1, color: Colors.grey[200]),
|
|
Container(
|
|
padding: EdgeInsets.all(16),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
_isChecking ? '正在检查更新...' : _hasUpdate ? '发现新版本' : '已是最新版本',
|
|
key: ValueKey<String>(_isChecking ? 'checking' : (_hasUpdate ? 'hasUpdate' : 'noUpdate')),
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: _hasUpdate ? Colors.lightBlueAccent : Colors.lightBlueAccent),
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
_isChecking ? '请稍等片刻' : _hasUpdate ? '立即体验新功能' : '您的应用已更新至最新版本',
|
|
key: ValueKey<String>(_isChecking ? 'checkingSub' : (_hasUpdate ? 'hasUpdateSub' : 'noUpdateSub')),
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
AnimatedSwitcher(
|
|
duration: kSwitchDuration,
|
|
transitionBuilder: (child, animation) {
|
|
return FadeTransition(opacity: animation, child: ScaleTransition(scale: animation, child: child));
|
|
},
|
|
child: _isChecking
|
|
? SizedBox(width: 32, height: 32, key: ValueKey('checkingIndicator'))
|
|
: (!_hasUpdate
|
|
? Icon(Icons.check_circle, color: Colors.lightBlueAccent, size: 32, key: ValueKey('checkIcon'))
|
|
: Container(
|
|
key: ValueKey('newBadge'),
|
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(color: Colors.blue[50], borderRadius: BorderRadius.circular(12)),
|
|
child: Text('NEW', style: TextStyle(color: Colors.lightBlueAccent, fontWeight: FontWeight.bold, fontSize: 12)),
|
|
)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (!_isChecking && !_isDownloading)
|
|
Container(
|
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
|
|
child: AnimatedSwitcher(
|
|
duration: kSwitchDuration,
|
|
transitionBuilder: (child, animation) {
|
|
return FadeTransition(
|
|
opacity: animation,
|
|
child: SlideTransition(
|
|
position: Tween<Offset>(begin: Offset(0, 0.05), end: Offset.zero).animate(animation), child: child));
|
|
},
|
|
child: ElevatedButton(
|
|
key: ValueKey<String>(
|
|
_hasUpdate ? (_downloadProgress == 1.0 ? 'install' : 'update') : 'check',
|
|
),
|
|
onPressed: _hasUpdate
|
|
? (_downloadProgress == 1.0 ? _installApk : _startDownload)
|
|
: _checkForUpdate,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _hasUpdate ? Colors.lightBlueAccent : Colors.lightBlueAccent,
|
|
foregroundColor: Colors.white,
|
|
minimumSize: Size(double.infinity, 48),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 0,
|
|
),
|
|
child: Text(
|
|
_hasUpdate ? (_downloadProgress == 1.0 ? '立即安装' : '立即更新') : '检查更新',
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildUpdateContent({Key? key}) {
|
|
return Container(
|
|
key: key,
|
|
margin: EdgeInsets.symmetric(horizontal: 16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.all(20),
|
|
child: Row(
|
|
children: [
|
|
AnimatedOpacity(duration: kFadeDuration, opacity: 1.0, child: Icon(Icons.new_releases, color: Colors.lightBlueAccent, size: 20)),
|
|
SizedBox(width: 8),
|
|
Text('更新内容', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87)),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(color: Colors.blue[50], borderRadius: BorderRadius.circular(8)),
|
|
child: Text('V$_latestVersion', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)),
|
|
),
|
|
SizedBox(width: 8),
|
|
Text('${_changelog.split('\n').length}项更新', style: TextStyle(color: Colors.grey[600], fontSize: 14)),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
AnimatedSize(
|
|
duration: kSwitchDuration,
|
|
curve: Curves.easeInOut,
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
child: Text(_changelog, style: TextStyle(fontSize: 15, height: 1.6, color: Colors.grey[700])),
|
|
),
|
|
),
|
|
SizedBox(height: 20),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDownloadProgress({Key? key}) {
|
|
return Container(
|
|
key: key,
|
|
margin: EdgeInsets.all(16),
|
|
padding: EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 2))],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.download, color: Colors.blue, size: 20),
|
|
SizedBox(width: 8),
|
|
Text('下载更新', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
Spacer(),
|
|
TweenAnimationBuilder<double>(
|
|
tween: Tween<double>(begin: 0, end: _downloadProgress),
|
|
duration: Duration(milliseconds: 300),
|
|
builder: (context, value, child) {
|
|
return Text('${(value * 100).toStringAsFixed(1)}%', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold));
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16),
|
|
TweenAnimationBuilder<double>(
|
|
tween: Tween<double>(begin: 0, end: _downloadProgress),
|
|
duration: Duration(milliseconds: 300),
|
|
builder: (context, value, child) {
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(4),
|
|
child: LinearProgressIndicator(
|
|
value: value,
|
|
backgroundColor: Colors.grey[200],
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
|
|
minHeight: 8,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
SizedBox(height: 8),
|
|
AnimatedSwitcher(
|
|
duration: Duration(milliseconds: 250),
|
|
child: Text(
|
|
_downloadProgress < 1 ? '正在下载更新包...' : '下载完成!',
|
|
key: ValueKey<bool>(_downloadProgress < 1),
|
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|