forked from lxm_flutter/FlutterUnit
app 应用内更新
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.toly1994.flutter_unit">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="flutter_unit"
|
||||
android:icon="@mipmap/logo">
|
||||
|
||||
@@ -12,7 +12,7 @@ class PathUnit{
|
||||
|
||||
static const categoryDataSync = '/categoryData/sync';
|
||||
static const categoryData = '/categoryData';
|
||||
static const appInfo = '/appInfo/name/FlutterUnit';
|
||||
static const appInfo = '/appInfo/name';
|
||||
|
||||
static const login = '/login';
|
||||
|
||||
|
||||
@@ -36,14 +36,13 @@ class Convert {
|
||||
};
|
||||
|
||||
static String convertFileSize(int size){
|
||||
if(size==null) return '0 kb';
|
||||
double result = size / 1024.0;
|
||||
if(result<1024){
|
||||
return "${result.toStringAsFixed(2)}Kb";
|
||||
return "${result.toStringAsFixed(2)} Kb";
|
||||
}else if(result>1024&&result<1024*1024){
|
||||
return "${(result/1024).toStringAsFixed(2)}Mb";
|
||||
return "${(result/1024).toStringAsFixed(2)} Mb";
|
||||
}else{
|
||||
return "${(result/1024/1024).toStringAsFixed(2)}Gb";
|
||||
return "${(result/1024/1024).toStringAsFixed(2)} Gb";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.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:flutter_unit/point_system/api/app_info.dart';
|
||||
// import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:package_info_plus/package_info_plus.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();
|
||||
// if(dir ==null) return;
|
||||
//
|
||||
// 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&&result.data!=null) {
|
||||
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,
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import 'package:flutter_unit/app/router/unit_router.dart';
|
||||
|
||||
import 'package:flutter_unit/components/permanent/circle_image.dart';
|
||||
import 'package:flutter_unit/components/permanent/feedback_widget.dart';
|
||||
import 'package:flutter_unit/update_part/views/app_update_panel.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
|
||||
@@ -79,7 +80,7 @@ class VersionInfo extends StatelessWidget {
|
||||
onTap: () => Navigator.of(context).pushNamed(UnitRouter.about_app),
|
||||
),
|
||||
Divider(height: 1,indent: 10),
|
||||
const AppVersionChecker(),
|
||||
const AppUpdatePanel(),
|
||||
Divider(height: 1,indent: 10),
|
||||
ListTile(
|
||||
title: Text('检查数据库新版本',style: labelStyle),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/app/blocs/global/global_bloc.dart';
|
||||
import 'package:flutter_unit/app/blocs/global/global_event.dart';
|
||||
import 'package:flutter_unit/bloc_exp.dart';
|
||||
import 'package:flutter_unit/point_system/blocs/point_system_bloc.dart';
|
||||
import 'package:flutter_unit/widget_system/repositories/repositories.dart';
|
||||
import 'package:flutter_unit/user_system/bloc/authentic/bloc.dart';
|
||||
@@ -65,6 +66,7 @@ class _BlocWrapperState extends State<BlocWrapper> {
|
||||
create: (_) => SearchBloc(repository: repository)),
|
||||
|
||||
BlocProvider<PointBloc>(create: (_) => PointBloc()),
|
||||
BlocProvider<UpdateBloc>(create: (_) => UpdateBloc()),
|
||||
|
||||
BlocProvider<PointCommentBloc>(create: (_) => PointCommentBloc()),
|
||||
], child: widget.child);
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/bloc_exp.dart';
|
||||
|
||||
import 'package:flutter_unit/app/res/cons.dart';
|
||||
import 'package:flutter_unit/app/router/unit_router.dart';
|
||||
|
||||
import 'package:flutter_unit/painter_system/gallery_unit.dart';
|
||||
import 'package:flutter_unit/user_system/pages/user/user_page.dart';
|
||||
import 'package:flutter_unit/components/project/nav/unit_bottom_bar.dart';
|
||||
import 'package:flutter_unit/components/project/overlay_tool_wrapper.dart';
|
||||
import 'package:flutter_unit/painter_system/gallery_unit.dart';
|
||||
import 'package:flutter_unit/user_system/pages/user/user_page.dart';
|
||||
import 'package:flutter_unit/widget_system/blocs/widget_system_bloc.dart';
|
||||
|
||||
import 'package:flutter_unit/widget_system/views/widget_system_view.dart';
|
||||
|
||||
|
||||
import '../../blocs/color_change_bloc.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020-04-11
|
||||
/// contact me by email 1981462002@qq.com
|
||||
/// 说明: 主题结构 左右滑页 + 底部导航栏
|
||||
|
||||
|
||||
class UnitNavigation extends StatefulWidget {
|
||||
const UnitNavigation();
|
||||
|
||||
@@ -33,6 +31,12 @@ class _UnitNavigationState extends State<UnitNavigation> {
|
||||
// 禁止 PageView 滑动
|
||||
final ScrollPhysics _neverScroll = const NeverScrollableScrollPhysics();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
BlocProvider.of<UpdateBloc>(context).add(CheckUpdate(appName: 'FlutterUnit'));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose(); //释放控制器
|
||||
@@ -95,12 +99,14 @@ class _UnitNavigationState extends State<UnitNavigation> {
|
||||
);
|
||||
|
||||
// 点击底部按钮事件,切换页面
|
||||
_onTapBottomNav(int index) {
|
||||
_controller.animateToPage(index, duration: const Duration(milliseconds: 200), curve: Curves.linear);
|
||||
if(index!=0){
|
||||
void _onTapBottomNav(int index) {
|
||||
_controller.animateToPage(index,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.linear);
|
||||
if (index != 0) {
|
||||
context.read<ColorChangeCubit>().change(Theme.of(context).primaryColor);
|
||||
}else{
|
||||
Color color = Cons.tabColors[context.read<ColorChangeCubit>().state.family.index];
|
||||
} else {
|
||||
Color color =
|
||||
Cons.tabColors[context.read<ColorChangeCubit>().state.family.index];
|
||||
context.read<ColorChangeCubit>().change(color);
|
||||
}
|
||||
|
||||
|
||||
5
lib/bloc_exp.dart
Normal file
5
lib/bloc_exp.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
library bloc_exp;
|
||||
|
||||
export 'update_part/bloc/bloc.dart';
|
||||
export 'update_part/bloc/event.dart';
|
||||
export 'update_part/bloc/state.dart';
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_unit/app/res/size_unit.dart';
|
||||
import 'package:flutter_unit/app/res/toly_icon.dart';
|
||||
import 'package:flutter_unit/update_part/views/update_red_point.dart';
|
||||
import 'package:flutter_unit/components/permanent/feedback_widget.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020-04-11
|
||||
@@ -138,14 +139,22 @@ class _UnitBottomBarState extends State<UnitBottomBar> {
|
||||
child: FeedbackWidget(
|
||||
onPressed: () => _updateIndex(3),
|
||||
onLongPressed: () => _onLongPress(context, 3),
|
||||
child: Container(
|
||||
padding: paddingR,
|
||||
height: SizeUnit.bottom_nav_height,
|
||||
child: Icon(
|
||||
TolyIcon.yonghu,
|
||||
size: getIconSizeByPosition(3),
|
||||
color: getIconColorByPosition(3),
|
||||
)),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: paddingR,
|
||||
height: SizeUnit.bottom_nav_height,
|
||||
child: Icon(
|
||||
TolyIcon.yonghu,
|
||||
size: getIconSizeByPosition(3),
|
||||
color: getIconColorByPosition(3),
|
||||
)),
|
||||
Positioned(
|
||||
left: 20,
|
||||
top: 5,
|
||||
child: const UpdateRedPoint())
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -169,6 +178,7 @@ class _UnitBottomBarState extends State<UnitBottomBar> {
|
||||
|
||||
void _onLongPress(BuildContext context, int index) {
|
||||
widget.onItemLongTap?.call(context, index);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
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 {
|
||||
static Future<ResultBean<AppInfo>> getAppVersion({required String appName}) async {
|
||||
String errorMsg = "";
|
||||
var result = await HttpUtil.getInstance()
|
||||
.client
|
||||
.get(PathUnit.appInfo)
|
||||
.get(PathUnit.appInfo+"/$appName")
|
||||
.catchError((err) {
|
||||
errorMsg = err.toString();
|
||||
});
|
||||
@@ -22,6 +23,7 @@ class AppInfoApi {
|
||||
appName: result.data['data']['appName'],
|
||||
appVersion: result.data['data']['appVersion'],
|
||||
appUrl: result.data['data']['appUrl'],
|
||||
appSize: result.data['data']['appSize'],
|
||||
));
|
||||
} else {
|
||||
return ResultBean.ok<AppInfo>(null);
|
||||
@@ -31,14 +33,24 @@ class AppInfoApi {
|
||||
}
|
||||
}
|
||||
|
||||
class AppInfo{
|
||||
class AppInfo extends Equatable{
|
||||
final String appName;
|
||||
final String appVersion;
|
||||
final String appUrl;
|
||||
final int appSize;
|
||||
|
||||
AppInfo({
|
||||
required this.appName,
|
||||
required this.appVersion,
|
||||
required this.appUrl,
|
||||
required this.appSize,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [appName,appVersion,appUrl,appSize];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppInfo{appName: $appName, appVersion: $appVersion, appUrl: $appUrl, appSize: $appSize}';
|
||||
}
|
||||
}
|
||||
72
lib/update_part/bloc/bloc.dart
Normal file
72
lib/update_part/bloc/bloc.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/app/utils/http_utils/result_bean.dart';
|
||||
import 'package:flutter_unit/point_system/api/app_info.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:r_upgrade/r_upgrade.dart';
|
||||
|
||||
import 'event.dart';
|
||||
import 'state.dart';
|
||||
|
||||
class UpdateBloc extends Bloc<UpdateEvent, UpdateState> {
|
||||
UpdateBloc() : super(const NoUpdateState()) {
|
||||
on<CheckUpdate>(_onCheckUpdate);
|
||||
on<ResetNoUpdate>(_onResetNoUpdate);
|
||||
on<DownloadEvent>(_onDownloadEvent);
|
||||
on<DownloadingEvent>(_onDownloadingEvent);
|
||||
}
|
||||
|
||||
void _onCheckUpdate(CheckUpdate event, Emitter<UpdateState> emit) async {
|
||||
emit(CheckLoadingState());
|
||||
// await Future.delayed(Duration(seconds: 1));
|
||||
// 检测更新逻辑
|
||||
ResultBean<AppInfo> result =
|
||||
await AppInfoApi.getAppVersion(appName: event.appName);
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
if (result.status && result.data != null) {
|
||||
if (packageInfo.version == result.data!.appVersion) {
|
||||
emit(NoUpdateState(
|
||||
isChecked: true,
|
||||
checkTime: DateTime.now().millisecondsSinceEpoch,
|
||||
));
|
||||
} else {
|
||||
if (result.data != null) {
|
||||
emit(ShouldUpdateState(
|
||||
oldVersion: packageInfo.version, info: result.data!));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit(CheckErrorState(error: result.msg));
|
||||
}
|
||||
}
|
||||
|
||||
void _onResetNoUpdate(ResetNoUpdate event, Emitter<UpdateState> emit) {
|
||||
emit(const NoUpdateState());
|
||||
}
|
||||
|
||||
late int? id;
|
||||
late StreamSubscription<DownloadInfo>? subscription;
|
||||
|
||||
void _onDownloadEvent(DownloadEvent event, Emitter<UpdateState> emit) async{
|
||||
id = await RUpgrade.upgrade(event.appInfo.appUrl,
|
||||
fileName: '${event.appInfo.appName}.apk', isAutoRequestInstall: true);
|
||||
subscription = RUpgrade.stream.listen((DownloadInfo info) {
|
||||
double progress = (info.percent ?? 0) / 100;
|
||||
if (info.status! == DownloadStatus.STATUS_SUCCESSFUL) {
|
||||
progress = 1;
|
||||
subscription?.cancel();
|
||||
add(ResetNoUpdate());
|
||||
}
|
||||
add(DownloadingEvent(state: DownloadingState(
|
||||
appSize: event.appInfo.appSize,
|
||||
progress: progress
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
void _onDownloadingEvent(DownloadingEvent event, Emitter<UpdateState> emit) {
|
||||
emit(event.state);
|
||||
}
|
||||
}
|
||||
43
lib/update_part/bloc/event.dart
Normal file
43
lib/update_part/bloc/event.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_unit/bloc_exp.dart';
|
||||
import 'package:flutter_unit/point_system/api/app_info.dart';
|
||||
|
||||
abstract class UpdateEvent extends Equatable {
|
||||
const UpdateEvent();
|
||||
}
|
||||
|
||||
// 检查更新 ---> 校验,转换状态
|
||||
class CheckUpdate extends UpdateEvent {
|
||||
final String appName;
|
||||
|
||||
const CheckUpdate({required this.appName});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [appName];
|
||||
}
|
||||
|
||||
class DownloadEvent extends UpdateEvent {
|
||||
final AppInfo appInfo;
|
||||
|
||||
const DownloadEvent({required this.appInfo});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [appInfo];
|
||||
}
|
||||
|
||||
class DownloadingEvent extends UpdateEvent {
|
||||
final DownloadingState state;
|
||||
|
||||
const DownloadingEvent({required this.state});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [state];
|
||||
}
|
||||
|
||||
// 将状态重置为 NoUpdateState
|
||||
class ResetNoUpdate extends UpdateEvent {
|
||||
const ResetNoUpdate();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
61
lib/update_part/bloc/state.dart
Normal file
61
lib/update_part/bloc/state.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_unit/point_system/api/app_info.dart';
|
||||
|
||||
abstract class UpdateState extends Equatable {
|
||||
const UpdateState();
|
||||
}
|
||||
|
||||
class NoUpdateState extends UpdateState {
|
||||
final bool isChecked;
|
||||
final int checkTime;
|
||||
|
||||
const NoUpdateState({this.isChecked = false, this.checkTime = 0});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isChecked, checkTime];
|
||||
}
|
||||
|
||||
class CheckLoadingState extends UpdateState {
|
||||
const CheckLoadingState();
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class DownloadingState extends UpdateState {
|
||||
final double progress;
|
||||
final int appSize;
|
||||
|
||||
const DownloadingState({required this.progress, required this.appSize});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [progress, appSize];
|
||||
}
|
||||
|
||||
class CheckErrorState extends UpdateState {
|
||||
final String error;
|
||||
|
||||
const CheckErrorState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CheckErrorState{error: $error}';
|
||||
}
|
||||
}
|
||||
|
||||
class ShouldUpdateState extends UpdateState {
|
||||
final String oldVersion;
|
||||
final AppInfo info;
|
||||
|
||||
const ShouldUpdateState({required this.oldVersion, required this.info});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [oldVersion, info];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ShouldUpdateState{oldVersion: $oldVersion, info: $info}';
|
||||
}
|
||||
}
|
||||
108
lib/update_part/views/app_update_panel.dart
Normal file
108
lib/update_part/views/app_update_panel.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/app/utils/Toast.dart';
|
||||
import 'package:flutter_unit/app/utils/convert.dart';
|
||||
import 'package:flutter_unit/bloc_exp.dart';
|
||||
|
||||
class AppUpdatePanel extends StatelessWidget {
|
||||
const AppUpdatePanel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<UpdateBloc, UpdateState>(
|
||||
builder: _buildByUpdateState,
|
||||
listener: _listenerByUpdateState,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProgress(BuildContext context, double progress, int appSize) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'${(progress * 100).toStringAsFixed(2)} %',
|
||||
style: TextStyle(height: 1, fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Text(
|
||||
'${Convert.convertFileSize((appSize * progress).floor())}/${Convert.convertFileSize(appSize)}',
|
||||
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: progress,
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildByUpdateState(BuildContext context, UpdateState state) {
|
||||
String info = '检查新版本';
|
||||
Widget trail = const SizedBox.shrink();
|
||||
if (state is ShouldUpdateState) {
|
||||
info = "下载新版本";
|
||||
trail = Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'${state.oldVersion} --> ${state.info.appVersion} ',
|
||||
style: TextStyle(height: 1, fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const Icon(Icons.update, color: Colors.green)
|
||||
]);
|
||||
}
|
||||
if (state is CheckLoadingState) {
|
||||
trail = CupertinoActivityIndicator();
|
||||
}
|
||||
if (state is DownloadingState) {
|
||||
info = "新版本下载中...";
|
||||
trail = _buildProgress(context, state.progress, state.appSize);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
info,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
trailing: trail,
|
||||
onTap: () => _tapByState(state, context),
|
||||
);
|
||||
}
|
||||
|
||||
void _tapByState(UpdateState state, BuildContext context) {
|
||||
if (state is NoUpdateState) {
|
||||
BlocProvider.of<UpdateBloc>(context)
|
||||
.add(CheckUpdate(appName: 'FlutterUnit'));
|
||||
}
|
||||
if (state is ShouldUpdateState) {
|
||||
// 处理下载的事件
|
||||
BlocProvider.of<UpdateBloc>(context)
|
||||
.add(DownloadEvent(appInfo: state.info));
|
||||
}
|
||||
}
|
||||
|
||||
void _listenerByUpdateState(BuildContext context, UpdateState state) {
|
||||
if (state is NoUpdateState) {
|
||||
if (state.isChecked) {
|
||||
Toast.success(context, '当前应用已是最新版本!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
lib/update_part/views/update_red_point.dart
Normal file
26
lib/update_part/views/update_red_point.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_unit/bloc_exp.dart';
|
||||
|
||||
|
||||
class UpdateRedPoint extends StatelessWidget {
|
||||
const UpdateRedPoint({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget radPoint = Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
|
||||
);
|
||||
return BlocBuilder<UpdateBloc, UpdateState>(
|
||||
builder: (BuildContext context, UpdateState state) {
|
||||
if (state is ShouldUpdateState) {
|
||||
return radPoint;
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_unit/app/res/style/behavior/no_scroll_behavior.dart';
|
||||
import 'package:flutter_unit/app/res/toly_icon.dart';
|
||||
import 'package:flutter_unit/app/router/unit_router.dart';
|
||||
import 'package:flutter_unit/update_part/views/update_red_point.dart';
|
||||
|
||||
/// create by 张风捷特烈 on 2020-03-26
|
||||
/// contact me by email 1981462002@qq.com
|
||||
@@ -27,12 +28,19 @@ class MePageItem extends StatelessWidget {
|
||||
height: 10,
|
||||
),
|
||||
_buildItem(context, TolyIcon.icon_them, '应用设置', UnitRouter.setting),
|
||||
_buildItem(context, TolyIcon.icon_layout, '数据管理', UnitRouter.data_manage),
|
||||
_buildItem(context, TolyIcon.icon_collect, '我的收藏', UnitRouter.collect),
|
||||
_buildItem(
|
||||
context, TolyIcon.icon_layout, '数据管理', UnitRouter.data_manage),
|
||||
_buildItem(
|
||||
context, TolyIcon.icon_collect, '我的收藏', UnitRouter.collect),
|
||||
Divider(
|
||||
height: 1,
|
||||
),
|
||||
_buildItem(context, Icons.update, '版本信息', UnitRouter.version_info),
|
||||
Stack(
|
||||
children: [
|
||||
_buildItem(context, Icons.update, '版本信息', UnitRouter.version_info,),
|
||||
Positioned(left: 40, top: 10, child: UpdateRedPoint())
|
||||
],
|
||||
),
|
||||
_buildItem(context, Icons.info, '关于应用', UnitRouter.about_app),
|
||||
Divider(
|
||||
height: 1,
|
||||
@@ -51,7 +59,11 @@ class MePageItem extends StatelessWidget {
|
||||
icon,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
title: Text(title),
|
||||
title: Stack(
|
||||
children: [
|
||||
Text(title),
|
||||
],
|
||||
),
|
||||
trailing:
|
||||
Icon(Icons.chevron_right, color: Theme.of(context).primaryColor),
|
||||
onTap: () {
|
||||
|
||||
@@ -345,6 +345,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
r_upgrade:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: r_upgrade
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.7+3"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: flutter_unit
|
||||
description: A new Flutter application.
|
||||
|
||||
version: 1.6.0
|
||||
version: 1.6.1
|
||||
author: 张风捷特烈 <1981462002@qq.com>
|
||||
homepage: https://juejin.cn/user/149189281194766/posts
|
||||
|
||||
@@ -16,6 +16,7 @@ dependencies:
|
||||
stream_transform: ^2.0.0
|
||||
equatable: ^2.0.3 # 相等辅助
|
||||
package_info_plus: ^1.3.0 # 应用包信息
|
||||
r_upgrade: ^0.3.7+3
|
||||
sqflite: ^2.0.2 # 数据库
|
||||
shared_preferences: ^2.0.13 # xml 固化
|
||||
jwt_decoder: ^2.0.1 # jwt 解析
|
||||
|
||||
Reference in New Issue
Block a user