forked from lxm_flutter/FlutterUnit
应用内升级检测逻辑
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
|
||||
40
lib/app/api/app_info.dart
Normal file
40
lib/app/api/app_info.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter_unit/app/res/path_unit.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/http_util.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/result_bean.dart';
|
||||
|
||||
class AppInfoApi {
|
||||
|
||||
static Future<ResultBean<AppInfo>> getAppVersion() async {
|
||||
String errorMsg = "";
|
||||
var result = await HttpUtil.getInstance()
|
||||
.client
|
||||
.get(PathUnit.appInfo)
|
||||
.catchError((err) {
|
||||
errorMsg = err.toString();
|
||||
});
|
||||
|
||||
// 获取的数据非空且 status = true
|
||||
if (result.data != null && result.data['status']) {
|
||||
// 说明有数据
|
||||
if (result.data['data'] != null) {
|
||||
return ResultBean.ok<AppInfo>(
|
||||
AppInfo(
|
||||
appName: result.data['data']['appName'],
|
||||
appVersion: result.data['data']['appVersion'],
|
||||
appUrl: result.data['data']['appUrl'],
|
||||
));
|
||||
} else {
|
||||
return ResultBean.ok<AppInfo>(null);
|
||||
}
|
||||
}
|
||||
return ResultBean.error('请求错误: $errorMsg');
|
||||
}
|
||||
}
|
||||
|
||||
class AppInfo{
|
||||
final String appName;
|
||||
final String appVersion;
|
||||
final String appUrl;
|
||||
|
||||
AppInfo({this.appName, this.appVersion, this.appUrl});
|
||||
}
|
||||
@@ -12,6 +12,7 @@ class PathUnit{
|
||||
|
||||
static const categoryDataSync = '/categoryData/sync';
|
||||
static const categoryData = '/categoryData';
|
||||
static const appInfo = '/appInfo/name/FlutterUnit';
|
||||
|
||||
static const login = '/login';
|
||||
|
||||
|
||||
@@ -14,4 +14,20 @@ class Toast {
|
||||
backgroundColor: color??Theme.of(context).primaryColor,
|
||||
));
|
||||
}
|
||||
|
||||
static void error(BuildContext context,String msg){
|
||||
toast(context,msg, color:Colors.red, );
|
||||
}
|
||||
|
||||
static void warning(BuildContext context,String msg){
|
||||
toast(context,msg, color:Colors.orange, );
|
||||
}
|
||||
|
||||
static void success(BuildContext context,String msg){
|
||||
toast(context,msg, color:Theme.of(context).primaryColor, );
|
||||
}
|
||||
|
||||
static void green(BuildContext context,String msg){
|
||||
toast(context,msg, color:Colors.green, );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,18 @@ class Convert {
|
||||
GalleryType.anim: "动画手势",
|
||||
GalleryType.art: "艺术画廊",
|
||||
};
|
||||
|
||||
static String convertFileSize(int size){
|
||||
if(size==null) return '0 kb';
|
||||
double result = size / 1024.0;
|
||||
if(result<1024){
|
||||
return "${result.toStringAsFixed(2)}Kb";
|
||||
}else if(result>1024&&result<1024*1024){
|
||||
return "${(result/1024).toStringAsFixed(2)}Mb";
|
||||
}else{
|
||||
return "${(result/1024/1024).toStringAsFixed(2)}Gb";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
197
lib/painter_system/particle/out/clock_fx.dart
Normal file
197
lib/painter_system/particle/out/clock_fx.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'rnd.dart';
|
||||
|
||||
import 'particle.dart';
|
||||
|
||||
|
||||
final easingDelayDuration = Duration(seconds: 10);
|
||||
|
||||
/// Probabilities of Hour, Minute, Noise.
|
||||
// final particleDistributions = [2, 4, 100];
|
||||
|
||||
/// Number of "arms" to emit noise particles from center.
|
||||
final int noiseAngles = 2000;
|
||||
|
||||
/// Threshold for particles to go rouge. Lower = more particles.
|
||||
final rougeDistributionLmt = 85;
|
||||
|
||||
/// Threshold for particles to go jelly. Lower = more particles.
|
||||
final jellyDistributionLmt = 97;
|
||||
|
||||
|
||||
class ClockFx with ChangeNotifier {
|
||||
|
||||
double width; //宽
|
||||
double height;//高
|
||||
double sizeMin; // 宽高最小值
|
||||
Offset center; //画布中心
|
||||
Rect spawnArea; // 粒子活动区域
|
||||
|
||||
List<Particle> particles; // 所有粒子
|
||||
|
||||
int numParticles;// 最大粒子数
|
||||
|
||||
DateTime time; //时间
|
||||
|
||||
ClockFx({
|
||||
@required Size size,
|
||||
@required DateTime time,
|
||||
this.numParticles = 5000,
|
||||
}) {
|
||||
this.time = time;
|
||||
particles = List<Particle>.filled(numParticles, null);
|
||||
setSize(size);
|
||||
}
|
||||
|
||||
void init() {
|
||||
for (int i = 0; i < numParticles; i++) {
|
||||
particles[i] = Particle(color:Colors.black );
|
||||
resetParticle(i);
|
||||
}
|
||||
}
|
||||
|
||||
void setTime(DateTime time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
void setSize(Size size) {
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
sizeMin = min(width, height);
|
||||
center = Offset(width / 2, height / 2);
|
||||
spawnArea = Rect.fromLTRB(
|
||||
center.dx - sizeMin / 100,
|
||||
center.dy - sizeMin / 100,
|
||||
center.dx + sizeMin / 100,
|
||||
center.dy + sizeMin / 100,
|
||||
);
|
||||
init();
|
||||
}
|
||||
|
||||
/// Resets a particle's values.
|
||||
Particle resetParticle(int i) {
|
||||
Particle p = particles[i];
|
||||
p.size = p.a = p.vx = p.vy = p.life = p.lifeLeft = 0;
|
||||
p.x = center.dx;
|
||||
p.y = center.dy;
|
||||
return p;
|
||||
}
|
||||
|
||||
void tick(Duration duration) {
|
||||
updateParticles(duration); // 更新粒子
|
||||
notifyListeners();// 通知监听者(画板)更新
|
||||
}
|
||||
|
||||
void updateParticles(Duration duration){
|
||||
var secFrac = DateTime.now().millisecond / 1000;
|
||||
|
||||
var vecSpeed = duration.compareTo(easingDelayDuration) > 0
|
||||
? max(.2, Curves.easeInOutSine.transform(1 - secFrac))
|
||||
: 1;
|
||||
|
||||
var vecSpeedInv = Curves.easeInSine.transform(secFrac);
|
||||
|
||||
var maxSpawnPerTick = 10;
|
||||
|
||||
particles.asMap().forEach((i, p) {
|
||||
p.x -= p.vx * vecSpeed;
|
||||
p.y -= p.vy * vecSpeed;
|
||||
|
||||
p.dist = _getDistanceFromCenter(p);
|
||||
p.distFrac = p.dist / (sizeMin / 2);
|
||||
|
||||
p.lifeLeft = p.life - p.distFrac;
|
||||
|
||||
p.vx -= p.lifeLeft * p.vx * .001;
|
||||
p.vy -= p.lifeLeft * p.vy * .001;
|
||||
|
||||
if (p.lifeLeft < .3) {
|
||||
p.size -= p.size * .0015;
|
||||
}
|
||||
|
||||
if (p.distribution > rougeDistributionLmt && p.distribution < jellyDistributionLmt) {
|
||||
var r = Rnd.getDouble(.2, 2.5) * vecSpeedInv * p.distFrac;
|
||||
p.x -= p.vx * r + (p.distFrac * Rnd.getDouble(-.4, .4));
|
||||
p.y -= p.vy * r + (p.distFrac * Rnd.getDouble(-.4, .4));
|
||||
}
|
||||
|
||||
if (p.distribution >= jellyDistributionLmt) {
|
||||
var r = Rnd.getDouble(.1, .9) * vecSpeedInv * (1 - p.lifeLeft);
|
||||
p.x += p.vx * r;
|
||||
p.y += p.vy * r;
|
||||
}
|
||||
|
||||
if (p.lifeLeft <= 0 || p.size <= .5) {
|
||||
resetParticle(i);
|
||||
if (maxSpawnPerTick > 0) {
|
||||
_activateParticle(p);
|
||||
maxSpawnPerTick--;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
void _activateParticle(Particle p) {
|
||||
p.x = Rnd.getDouble(spawnArea.left, spawnArea.right);
|
||||
p.y = Rnd.getDouble(spawnArea.top, spawnArea.bottom);
|
||||
p.isFilled = Rnd.getBool();
|
||||
p.size = Rnd.getDouble(3, 8);
|
||||
p.distFrac = 0;
|
||||
p.distribution = Rnd.getInt(1, 2);
|
||||
|
||||
double angle = Rnd.ratio * pi * 2;
|
||||
|
||||
|
||||
var am = _getMinuteRadians();
|
||||
var ah = _getHourRadians() % (pi * 2);
|
||||
var d = pi / 18;
|
||||
//
|
||||
// Probably not the most efficient solution right here.
|
||||
do {
|
||||
angle = Rnd.ratio * pi * 2;
|
||||
} while (_isBetween(angle, am - d, am + d) ||
|
||||
_isBetween(angle, ah - d, ah + d) );
|
||||
|
||||
p.life = Rnd.getDouble(0.75, .8);
|
||||
|
||||
p.size = sizeMin *
|
||||
(Rnd.ratio > .8
|
||||
? Rnd.getDouble(.0015, .003)
|
||||
: Rnd.getDouble(.002, .006));
|
||||
|
||||
p.vx = sin(-angle);
|
||||
p.vy = cos(-angle);
|
||||
|
||||
p.a = atan2(p.vy, p.vx) + pi;
|
||||
|
||||
double v = Rnd.getDouble(.5, 1);
|
||||
|
||||
p.vx *= v;
|
||||
p.vy *= v;
|
||||
}
|
||||
|
||||
double _getDistanceFromCenter(Particle p) {
|
||||
var a = pow(center.dx - p.x, 2);
|
||||
var b = pow(center.dy - p.y, 2);
|
||||
return sqrt(a + b);
|
||||
}
|
||||
|
||||
/// Gets the radians of the hour hand.
|
||||
double _getHourRadians() =>
|
||||
(time.hour * pi / 6) +
|
||||
(time.minute * pi / (6 * 60)) +
|
||||
(time.second * pi / (360 * 60));
|
||||
|
||||
/// Gets the radians of the minute hand.
|
||||
double _getMinuteRadians() =>
|
||||
(time.minute * (2 * pi) / 60) + (time.second * pi / (30 * 60));
|
||||
|
||||
/// Checks if a value is between two other values.
|
||||
bool _isBetween(double value, double min, double max) {
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
}
|
||||
88
lib/painter_system/particle/out/clock_widget.dart
Normal file
88
lib/painter_system/particle/out/clock_widget.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import 'clock_fx.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2021/2/7
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
|
||||
class ClockWidget extends StatefulWidget {
|
||||
final double radius;
|
||||
|
||||
const ClockWidget({Key key, this.radius = 100}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ClockWidgetState createState() => _ClockWidgetState();
|
||||
}
|
||||
|
||||
class _ClockWidgetState extends State<ClockWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Ticker _ticker;
|
||||
ClockFx _fx;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ticker = createTicker(_tick)..start();
|
||||
_fx = ClockFx(
|
||||
size: Size(widget.radius * 2, widget.radius * 2),
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker.dispose();
|
||||
_fx.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _tick(Duration duration) {
|
||||
_fx.tick(duration);
|
||||
if (_fx.time.second != DateTime.now().second) {
|
||||
_fx.setTime(DateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
size: Size(widget.radius * 2, widget.radius * 2),
|
||||
painter: ClockFxPainter(fx: _fx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Alpha value for noise particles.
|
||||
const double noiseAlpha = 160;
|
||||
|
||||
class ClockFxPainter extends CustomPainter {
|
||||
final ClockFx fx;
|
||||
|
||||
ClockFxPainter({@required this.fx}) : super(repaint: fx);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
fx.particles.forEach((p) {
|
||||
double a;
|
||||
a = max(0.0, (p.distFrac - .13) / p.distFrac) * 255;
|
||||
a = min(a, min(noiseAlpha, p.lifeLeft * 3 * 255));
|
||||
int alpha = a.floor();
|
||||
|
||||
Paint circlePaint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = p.color.withAlpha(alpha);
|
||||
|
||||
canvas.drawCircle(Offset(p.x, p.y), p.size, circlePaint);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant ClockFxPainter oldDelegate) =>
|
||||
oldDelegate.fx != fx;
|
||||
}
|
||||
40
lib/painter_system/particle/out/particle.dart
Normal file
40
lib/painter_system/particle/out/particle.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class Particle {
|
||||
|
||||
double x; // x 坐标
|
||||
double y; // y 坐标
|
||||
double vx; // x 速度
|
||||
double vy; // y 速度
|
||||
double a; // 发射弧度
|
||||
double dist; // 距离画布中心的长度
|
||||
double distFrac;// 距离画布中心的百分比
|
||||
double size;// 粒子大小
|
||||
double life; // 粒子寿命
|
||||
double lifeLeft; // 粒子剩余寿命
|
||||
bool isFilled; // 是否填充
|
||||
Color color; // 颜色
|
||||
int distribution; // 分配情况
|
||||
|
||||
|
||||
Particle({
|
||||
this.x = 0,
|
||||
this.y = 0,
|
||||
this.a = 0,
|
||||
this.vx = 0,
|
||||
this.vy = 0,
|
||||
this.dist = 0,
|
||||
this.distFrac = 0,
|
||||
this.size = 0,
|
||||
this.life = 0,
|
||||
this.lifeLeft = 0,
|
||||
this.isFilled = false,
|
||||
this.color = Colors.blueAccent,
|
||||
this.distribution = 0,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
48
lib/painter_system/particle/out/rnd.dart
Normal file
48
lib/painter_system/particle/out/rnd.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'dart:math';
|
||||
|
||||
|
||||
class Rnd {
|
||||
static int _seed = DateTime.now().millisecondsSinceEpoch;
|
||||
static Random random = Random(_seed);
|
||||
|
||||
static set seed(int val) => random = Random(_seed = val);
|
||||
static int get seed => _seed;
|
||||
|
||||
/// Gets the next double.
|
||||
static get ratio => random.nextDouble();
|
||||
|
||||
/// Gets a random int between [min] and [max].
|
||||
static int getInt(int min, int max) {
|
||||
return min + random.nextInt(max - min);
|
||||
}
|
||||
|
||||
/// Gets a random double between [min] and [max].
|
||||
static double getDouble(double min, double max) {
|
||||
return min + random.nextDouble() * (max - min);
|
||||
}
|
||||
|
||||
/// Gets a random boolean with chance [chance].
|
||||
static bool getBool([double chance = 0.5]) {
|
||||
return random.nextDouble() < chance;
|
||||
}
|
||||
|
||||
/// Randomize the positions of items in a list.
|
||||
static List shuffle(List list) {
|
||||
for (int i = 0, l = list.length; i < l; i++) {
|
||||
int j = random.nextInt(l);
|
||||
if (j == i) {
|
||||
continue;
|
||||
}
|
||||
dynamic item = list[j];
|
||||
list[j] = list[i];
|
||||
list[i] = item;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Randomly selects an item from a list.
|
||||
static dynamic getItem(List list) {
|
||||
return list[random.nextInt(list.length)];
|
||||
}
|
||||
|
||||
}
|
||||
166
lib/views/pages/about/version/app_version_checker.dart
Normal file
166
lib/views/pages/about/version/app_version_checker.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_unit/app/api/app_info.dart';
|
||||
import 'package:flutter_unit/app/utils/Toast.dart';
|
||||
import 'package:flutter_unit/app/utils/convert.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/http_util.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/result_bean.dart';
|
||||
import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class AppVersionChecker extends StatefulWidget {
|
||||
const AppVersionChecker({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AppVersionCheckerState createState() => _AppVersionCheckerState();
|
||||
}
|
||||
|
||||
enum VersionState { none, loading, shouldUpdate, downloading }
|
||||
|
||||
class _AppVersionCheckerState extends State<AppVersionChecker> {
|
||||
final TextStyle labelStyle = TextStyle(fontSize: 13);
|
||||
String oldVersion = '';
|
||||
String newVersion = '';
|
||||
int totalSize =0;
|
||||
String url = 'http://toly1994.com/file/FlutterUnit.apk';
|
||||
ValueNotifier<VersionState> versionState =
|
||||
ValueNotifier<VersionState>(VersionState.none);
|
||||
|
||||
ValueNotifier<double> progress = ValueNotifier<double>(0);
|
||||
|
||||
_doDownload() async {
|
||||
Directory dir = await getExternalStorageDirectory();
|
||||
String dstPath = path.join(dir.path, 'FlutterUnit.apk');
|
||||
|
||||
if(File(dstPath).existsSync()){
|
||||
InstallPlugin.installApk(dstPath, 'com.toly1994.flutter_unit');
|
||||
return;
|
||||
}
|
||||
|
||||
versionState.value = VersionState.downloading;
|
||||
|
||||
await HttpUtil.getInstance().client.download(url, dstPath,
|
||||
onReceiveProgress: _onReceiveProgress,
|
||||
options: Options(receiveTimeout: 24 * 60 * 60 * 1000));
|
||||
versionState.value = VersionState.none;
|
||||
InstallPlugin.installApk(dstPath, 'com.toly1994.flutter_unit');
|
||||
}
|
||||
|
||||
void _onReceiveProgress(int count, int total) {
|
||||
totalSize = total;
|
||||
progress.value = count / total;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text('检查新版本', style: labelStyle),
|
||||
trailing: ValueListenableBuilder(
|
||||
valueListenable: versionState,
|
||||
builder: _buildTrailByState,
|
||||
),
|
||||
onTap: () async {
|
||||
if (versionState.value == VersionState.shouldUpdate &&
|
||||
Platform.isAndroid) {
|
||||
_doDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (versionState.value == VersionState.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
versionState.value = VersionState.loading;
|
||||
ResultBean<AppInfo> result = await AppInfoApi.getAppVersion();
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
if (result.status) {
|
||||
print('${result.data.appName}:${result.data.appVersion}');
|
||||
if (packageInfo.version == result.data.appVersion) {
|
||||
Toast.success(context, '当前应用已是最新版本!');
|
||||
versionState.value = VersionState.none;
|
||||
} else {
|
||||
oldVersion = packageInfo.version;
|
||||
newVersion = result.data.appVersion;
|
||||
Toast.green(context, '检测到新版本【${result.data.appVersion}】,可点击更新!');
|
||||
versionState.value = VersionState.shouldUpdate;
|
||||
}
|
||||
} else {
|
||||
print('${result.msg}');
|
||||
versionState.value = VersionState.none;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTrailByState(
|
||||
BuildContext context, VersionState value, Widget child) {
|
||||
switch (value) {
|
||||
case VersionState.none:
|
||||
return const SizedBox();
|
||||
case VersionState.loading:
|
||||
return const CupertinoActivityIndicator();
|
||||
case VersionState.shouldUpdate:
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'$oldVersion --> $newVersion ',
|
||||
style: TextStyle(height: 1, fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
const Icon(
|
||||
Icons.update,
|
||||
color: Colors.green,
|
||||
)
|
||||
]);
|
||||
case VersionState.downloading:
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: progress, builder: _buildProgress);
|
||||
}
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
Widget _buildProgress(BuildContext context, double value, Widget child) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'${(value * 100).toStringAsFixed(2)} %',
|
||||
style: TextStyle(height: 1, fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
'${Convert.convertFileSize((totalSize * value).floor())}/${Convert.convertFileSize(totalSize)}',
|
||||
style: TextStyle(height: 1, fontSize: 10, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
backgroundColor: Colors.grey,
|
||||
value: value,
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
32
lib/views/pages/about/version/version_shower.dart
Normal file
32
lib/views/pages/about/version/version_shower.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
|
||||
class VersionShower extends StatefulWidget {
|
||||
const VersionShower({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_VersionShowerState createState() => _VersionShowerState();
|
||||
}
|
||||
|
||||
class _VersionShowerState extends State<VersionShower> {
|
||||
String version = '1.0.0';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchVersion();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text('Version $version');
|
||||
}
|
||||
|
||||
void _fetchVersion() async{
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
if(mounted)
|
||||
setState(() {
|
||||
version= packageInfo.version;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_unit/app/api/app_info.dart';
|
||||
import 'package:flutter_unit/app/res/str_unit.dart';
|
||||
import 'package:flutter_unit/app/router/unit_router.dart';
|
||||
import 'package:flutter_unit/app/res/style/behavior/no_scroll_behavior.dart';
|
||||
import 'package:flutter_unit/app/router/unit_router.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/result_bean.dart';
|
||||
import 'package:flutter_unit/views/components/permanent/circle_image.dart';
|
||||
import 'package:flutter_unit/views/components/permanent/feedback_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'version/app_version_checker.dart';
|
||||
import 'version/version_shower.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020/6/16
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明:
|
||||
@@ -54,13 +59,11 @@ class VersionInfo extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
CircleImage(image: AssetImage("assets/images/icon_head.webp"),size: 80,),
|
||||
Text('Flutter Unit',style: TextStyle(fontSize: 20,fontWeight: FontWeight.bold),),
|
||||
Text('Version ${StrUnit.version}'),
|
||||
const VersionShower(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Widget _buildCenter(BuildContext context) {
|
||||
final labelStyle= TextStyle(fontSize: 13);
|
||||
return Padding(
|
||||
@@ -77,13 +80,7 @@ class VersionInfo extends StatelessWidget {
|
||||
onTap: () => Navigator.of(context).pushNamed(UnitRouter.about_app),
|
||||
),
|
||||
Divider(height: 1,indent: 10),
|
||||
ListTile(
|
||||
title: Text('检查新版本',style: labelStyle),
|
||||
trailing: _nextIcon(context),
|
||||
onTap: () {
|
||||
|
||||
},
|
||||
),
|
||||
const AppVersionChecker(),
|
||||
Divider(height: 1,indent: 10),
|
||||
ListTile(
|
||||
title: Text('检查数据库新版本',style: labelStyle),
|
||||
|
||||
@@ -19,8 +19,8 @@ const BorderRadius _kBorderRadius = BorderRadius.only(
|
||||
bottomRight: Radius.circular(15),
|
||||
);
|
||||
|
||||
const _kTabTextStyle = TextStyle(color: Colors.white, shadows: [
|
||||
const Shadow(color: Colors.black, offset: Offset(0.5, 0.5), blurRadius: 0.5)
|
||||
const TextStyle _kTabTextStyle = TextStyle(color: Colors.white, shadows: [
|
||||
Shadow(color: Colors.black, offset: Offset(0.5, 0.5), blurRadius: 0.5)
|
||||
]);
|
||||
|
||||
class _TolyAppBarState extends State<TolyAppBar>
|
||||
|
||||
14
pubspec.lock
14
pubspec.lock
@@ -149,6 +149,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
install_plugin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: install_plugin
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -205,6 +212,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -15,11 +15,12 @@ dependencies:
|
||||
|
||||
flutter_bloc: ^7.0.0 # 状态管理
|
||||
equatable: ^2.0.0 # 相等辅助
|
||||
|
||||
package_info: ^2.0.2 # 应用包信息
|
||||
sqflite: ^2.0.0+3 # 数据库
|
||||
shared_preferences: ^2.0.5 # xml 固化
|
||||
jwt_decoder: ^2.0.1 # jwt 解析
|
||||
toggle_rotate: ^0.0.5
|
||||
install_plugin: ^2.0.1
|
||||
flutter_star: ^0.1.2 # 星星组件
|
||||
url_launcher: ^6.0.3 # url
|
||||
share: ^2.0.1 # 文字分享
|
||||
|
||||
Reference in New Issue
Block a user