x1ongzhu 6 年之前
父節點
當前提交
90da9d2bbf

+ 16 - 3
android/app/src/main/AndroidManifest.xml

@@ -1,4 +1,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.izouma.wanna_battle">
 
     <!-- io.flutter.app.FlutterApplication is an android.app.Application that
@@ -6,6 +7,10 @@
          In most cases you can leave this as-is, but you if you want to provide
          additional functionality it is fine to subclass or reimplement
          FlutterApplication and put your custom class here. -->
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
     <application
         android:name="com.izouma.screen_stream_plugin.MyApplication"
         android:icon="@mipmap/ic_launcher"
@@ -37,8 +42,16 @@
             </intent-filter>
         </activity>
 
-        <meta-data
-            android:name="android.support.FILE_PROVIDER_PATHS"
-            android:resource="@xml/file_paths" />
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.izouma.wanna_battle.fileProvider"
+            android:exported="false"
+            android:grantUriPermissions="true"
+            tools:node="replace">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/filepaths"
+                tools:replace="android:resource" />
+        </provider>
     </application>
 </manifest>

+ 0 - 6
android/app/src/main/res/xml/file_paths.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<paths xmlns:android="http://schemas.android.com/apk/res/android">
-    <external-path
-        name="external_files"
-        path="tessdata" />
-</paths>

+ 1 - 13
android/app/src/main/res/xml/filepaths.xml

@@ -2,17 +2,5 @@
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
     <external-path
         name="external_files"
-        path="tessdata" />
-    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
-    <external-path
-        name="beta_external_path"
-        path="Download/" />
-    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
-    <external-path
-        name="beta_external_files_path"
-        path="Android/data/" />
-
-    <external-path
-        name="sdcard"
-        path="/" />
+        path="." />
 </paths>

二進制
images/2x/icon_rank.png


二進制
images/3x/icon_rank.png


二進制
images/icon_rank.png


+ 26 - 5
lib/main.dart

@@ -6,6 +6,8 @@ import 'package:redux/redux.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_redux/flutter_redux.dart';
 import 'package:shared_preferences/shared_preferences.dart';
+import 'package:flutter_bugly/flutter_bugly.dart';
+import 'package:flutter/foundation.dart';
 import './Localizations.dart';
 import 'model/UserInfo.dart';
 import 'net/HttpManager.dart';
@@ -71,17 +73,36 @@ Future<void> main() async {
   );
   jpush.applyPushAuthority(NotificationSettingsIOS(sound: true, alert: true, badge: true));
   final prefs = await SharedPreferences.getInstance();
-  print(prefs.getString('token'));
   HttpManager.token = prefs.getString('token') ?? '';
+
+  if (debugMode()) {
+    HttpManager.token = ''
+        'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwOTkyZDBkMC0xN2Y3LTQ2'
+        'NzktOTY4My01YWFlY2VlZmU1YzgiLCJpc3MiOiJhZG1pbiIsImlh'
+        'dCI6MTU2MDk5NTYwNCwic3ViIjoiODQ3MDMiLCJleHAiOjE1NjM1'
+        'ODc2MDR9.WBB9F2KBb_xOv3rXnyxCVXYTC7_BiqhS1IFhMcnKvT8';
+  }
+
   Result result = await HttpManager.get('userInfo/getUserInfo');
   AppState state = AppState.empty();
   if (result.success && result.data != null) {
     UserInfo userInfo = UserInfo.fromJson(result.data);
     state.userInfo = userInfo;
-    try {
-      // jpush.setAlias(userInfo.id.toString()).then((map) {});
-    } catch (e) {}
+    jpush.setAlias(userInfo.id.toString()).then((map) {});
   }
+
   Store<AppState> store = Store<AppState>(appReducer, initialState: state);
-  runApp(MobileCyberGamesApp(store));
+  FlutterBugly.init(androidAppId: '5f31e941fc', iOSAppId: '');
+  FlutterBugly.postCatchedException(() {
+    runApp(MobileCyberGamesApp(store));
+  });
+}
+
+bool debugMode() {
+  if (kReleaseMode) {
+    print('release mode');
+  } else {
+    print('debug mode');
+  }
+  return !kReleaseMode;
 }

+ 3 - 1
lib/model/ParticipatingInfo.dart

@@ -1,11 +1,12 @@
 import 'package:json_annotation/json_annotation.dart';
+import 'package:wanna_battle/model/UserInfo.dart';
 part 'ParticipatingInfo.g.dart';
 
 @JsonSerializable()
 class ParticipatingInfo {
   ParticipatingInfo.empty();
 
-  ParticipatingInfo(this.userId, this.competitionId, this.points, this.bonus, this.received, this.rank, this.currentRank);
+  ParticipatingInfo(this.userId, this.competitionId, this.points, this.bonus, this.received, this.rank, this.currentRank, this.userInfo);
 
   factory ParticipatingInfo.fromJson(Map<String, dynamic> json) => _$ParticipatingInfoFromJson(json);
 
@@ -16,6 +17,7 @@ class ParticipatingInfo {
   int received;
   int rank;
   int currentRank;
+  UserInfo userInfo;
 
   Map<String, dynamic> toJson() => _$ParticipatingInfoToJson(this);
 

+ 6 - 2
lib/model/ParticipatingInfo.g.dart

@@ -14,7 +14,10 @@ ParticipatingInfo _$ParticipatingInfoFromJson(Map<String, dynamic> json) {
       json['bonus'] as int,
       json['received'] as int,
       json['rank'] as int,
-      json['currentRank'] as int);
+      json['currentRank'] as int,
+      json['userInfo'] == null
+          ? null
+          : UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>));
 }
 
 Map<String, dynamic> _$ParticipatingInfoToJson(ParticipatingInfo instance) =>
@@ -25,5 +28,6 @@ Map<String, dynamic> _$ParticipatingInfoToJson(ParticipatingInfo instance) =>
       'bonus': instance.bonus,
       'received': instance.received,
       'rank': instance.rank,
-      'currentRank': instance.currentRank
+      'currentRank': instance.currentRank,
+      'userInfo': instance.userInfo
     };

+ 0 - 1
lib/net/HttpManager.dart

@@ -20,7 +20,6 @@ class HttpManager {
     try {
       FormData formData = FormData.from(data ?? {});
       Response response = await _createDio().post(url, data: formData);
-      print(response);
       if (response.statusCode != 200) {
         result.success = false;
         result.error = 'httpCode' + response.statusCode.toString();

+ 268 - 0
lib/pages/CompetitionRank.dart

@@ -0,0 +1,268 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import 'package:wanna_battle/model/ParticipatingInfo.dart';
+import 'package:wanna_battle/redux/AppState.dart';
+import '../styles/colors.dart';
+import '../net/HttpManager.dart';
+
+class CompetitionRank extends StatefulWidget {
+  final int competitionId;
+
+  const CompetitionRank({Key key, this.competitionId}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() {
+    return CompetitionRankState();
+  }
+}
+
+class CompetitionRankState extends State<CompetitionRank> {
+  List<ParticipatingInfo> data = [];
+  ParticipatingInfo myParticipatingInfo;
+
+  Future<void> refresh() async {
+    final res = await HttpManager.get('participatingInfo/rank', data: {'competitionId': widget.competitionId});
+    ParticipatingInfo myParticipatingInfo;
+    if (res.success) {
+      List<ParticipatingInfo> list = [];
+      for (var item in res.data) {
+        var p = ParticipatingInfo.fromJson(item);
+        list.add(p);
+        if (StoreProvider.of<AppState>(context).state.userInfo.id == p.userId) {
+          myParticipatingInfo = p;
+        }
+      }
+      setState(() {
+        data = list;
+        this.myParticipatingInfo = myParticipatingInfo;
+      });
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    Future.delayed(Duration.zero, () {
+      refresh();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        centerTitle: true,
+        elevation: 0,
+        title: Text('排行榜'),
+        actions: <Widget>[
+          CupertinoButton(
+            child: Text(
+              '奖金规则',
+              style: TextStyle(fontSize: 13, color: Color(0x80FFFFFF)),
+            ),
+            onPressed: () {},
+          )
+        ],
+      ),
+      body: Stack(
+        children: <Widget>[
+          RefreshIndicator(
+            onRefresh: () async {
+              await refresh();
+              return;
+            },
+            child: Builder(
+              builder: (context) {
+                List<Widget> list = [
+                  Container(
+                    padding: EdgeInsets.only(bottom: 10),
+                    color: SUB_COLOR,
+                    child: Row(
+                      children: <Widget>[
+                        topRank(data.length > 1 ? data[1] : null),
+                        topRank(data.length > 0 ? data[0] : null),
+                        topRank(data.length > 2 ? data[2] : null),
+                      ],
+                    ),
+                  )
+                ];
+                if (data.length > 3) {
+                  for (int i = 2; i < data.length; i++) {
+                    list.add(rankItem(data[i]));
+                  }
+                }
+                return ListView(
+                  padding: EdgeInsets.only(bottom: 48),
+                  children: list,
+                );
+              },
+            ),
+          ),
+          Align(
+            alignment: Alignment.bottomCenter,
+            child: rankItem(myParticipatingInfo, mine: true),
+          )
+        ],
+      ),
+    );
+  }
+
+  Widget topRank(ParticipatingInfo participatingInfo) {
+    if (participatingInfo == null) {
+      return Expanded(child: Container());
+    }
+    double size = participatingInfo.rank == 1 ? 80 : 70;
+    List<Color> colors = [Color(0xFFD48E00), Color(0xFFFECF01)];
+    String icon = 'images/icon_paihangbang_01.png';
+    if (participatingInfo.rank == 2) {
+      colors = [Color(0xFFC5C5C5), Color(0xFFE3E3E3)];
+      icon = 'images/icon_paihangbang_02.png';
+    } else if (participatingInfo.rank == 3) {
+      colors = [Color(0xFFE77023), Color(0xFFF89E58)];
+      icon = 'images/icon_paihangbang_03.png';
+    }
+    return Expanded(
+      child: Container(
+        margin: EdgeInsets.only(top: participatingInfo.rank == 1 ? 4 : 38),
+        child: Column(
+          children: <Widget>[
+            Stack(
+              children: <Widget>[
+                Align(
+                  alignment: Alignment.topCenter,
+                  child: Container(
+                    width: size,
+                    height: size,
+                    margin: EdgeInsets.only(top: 23),
+                    padding: EdgeInsets.all(5),
+                    decoration: BoxDecoration(
+                      borderRadius: BorderRadius.all(Radius.circular(size / 2)),
+                      gradient: LinearGradient(
+                        colors: colors,
+                        begin: Alignment.centerRight,
+                        end: Alignment.centerLeft,
+                      ),
+                    ),
+                    child: ClipOval(
+                      child: CachedNetworkImage(
+                        imageUrl: participatingInfo?.userInfo?.icon ?? '',
+                        fit: BoxFit.cover,
+                      ),
+                    ),
+                  ),
+                ),
+                Align(
+                  alignment: Alignment.topCenter,
+                  child: Image.asset(icon),
+                ),
+              ],
+            ),
+            Container(
+              margin: EdgeInsets.only(top: 6),
+              child: Text(
+                participatingInfo?.userInfo?.nickname ?? '',
+                style: TextStyle(color: PRIMARY_COLOR, fontSize: 14, fontWeight: FontWeight.bold),
+              ),
+            ),
+            Row(
+              mainAxisSize: MainAxisSize.min,
+              children: <Widget>[
+                Image.asset('images/icon_jifen_da.png', width: 20),
+                Container(
+                  margin: EdgeInsets.only(left: 2),
+                  child: Text(
+                    (participatingInfo?.points ?? 0).toString(),
+                    style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
+                  ),
+                )
+              ],
+            )
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget rankItem(ParticipatingInfo participatingInfo, {bool mine = false}) {
+    if (participatingInfo == null) {
+      if (mine) {
+        return Container();
+      }
+      return Container();
+    }
+    return Container(
+      height: mine ? 48 : 66,
+      decoration: BoxDecoration(
+        border: Border(
+          bottom: BorderSide(
+            width: mine ? 0 : 1,
+            color: Color(0x2E000000),
+          ),
+        ),
+        color: mine ? SUB_COLOR : Colors.transparent,
+      ),
+      padding: EdgeInsets.only(left: 15),
+      child: Row(
+        children: <Widget>[
+          Container(
+            width: 30,
+            height: 30,
+            child: Stack(
+              children: <Widget>[
+                Image.asset('images/icon_rank.png'),
+                Align(
+                  alignment: Alignment.topCenter,
+                  child: Container(
+                    margin: EdgeInsets.only(top: 11),
+                    child: Text(
+                      (participatingInfo?.rank ?? 0).toString(),
+                      style: TextStyle(color: Color(0xFF15151D), fontSize: 12, fontWeight: FontWeight.bold),
+                    ),
+                  ),
+                )
+              ],
+            ),
+          ),
+          Container(
+            width: mine ? 30 : 46,
+            height: mine ? 30 : 46,
+            margin: EdgeInsets.only(left: 10),
+            child: ClipOval(
+              child: CachedNetworkImage(
+                imageUrl: participatingInfo?.userInfo?.icon ?? '',
+                fit: BoxFit.cover,
+              ),
+            ),
+          ),
+          Expanded(
+            child: Container(
+              margin: EdgeInsets.only(left: 19),
+              child: Text(
+                participatingInfo?.userInfo?.nickname ?? '',
+                style: TextStyle(color: mine ? Colors.white : PRIMARY_COLOR, fontSize: 14),
+              ),
+            ),
+          ),
+          Container(
+            width: 113,
+            child: Row(
+              children: <Widget>[
+                Image.asset('images/icon_jifen_da.png', width: 20),
+                Container(
+                  margin: EdgeInsets.only(left: 2),
+                  child: Text(
+                    (participatingInfo?.points ?? 0).toString(),
+                    style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
+                  ),
+                )
+              ],
+            ),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 4 - 1
lib/pages/CompetitionRooms.dart

@@ -13,6 +13,7 @@ import '../net/Result.dart';
 import './RoomInfo.dart';
 import '../redux/AppState.dart';
 import '../plugins/ScreenStramPlugin.dart';
+import './CompetitionRank.dart';
 
 class CompetitionRooms extends StatefulWidget {
   CompetitionRooms(this.competitionInfo);
@@ -47,7 +48,9 @@ class _CompetitionRoomsState extends State<CompetitionRooms> {
         elevation: 0,
         actions: <Widget>[
           GestureDetector(
-            onTap: () {},
+            onTap: () {
+              Navigator.push(context, CupertinoPageRoute(builder: (context) => CompetitionRank(competitionId: widget.competitionInfo.id)));
+            },
             child: Image.asset('images/icon_paihangbang.png'),
           )
         ],

+ 1 - 2
lib/pages/Competitions.dart

@@ -18,7 +18,6 @@ class Competitions extends StatefulWidget {
 
 class _CompetitionState extends State<Competitions> {
   List<CompetitionInfo> competitionInfoList = [];
-
   @override
   void initState() {
     super.initState();
@@ -148,7 +147,7 @@ class _CompetitionState extends State<Competitions> {
                                           ],
                                         ),
                                       ),
-                                      onTap: () {
+                                      onTap: () async {
                                         showCheckinDialog(context);
                                       },
                                     ),

+ 6 - 1
lib/pages/Home.dart

@@ -4,6 +4,7 @@ import './Competitions.dart';
 import '../widget/HomeDrawer.dart';
 import './TipList.dart';
 import './Shop.dart';
+import '../widget/CheckUpgrade.dart';
 
 class HomePage extends StatefulWidget {
   const HomePage({Key key}) : super(key: key);
@@ -17,12 +18,16 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
     Tab(text: 'RIGHT'),
   ];
 
+  final GlobalKey<UpgradeDialogState> _dialogKey = GlobalKey();
   TabController _tabController;
-
+  
   @override
   void initState() {
     super.initState();
     _tabController = TabController(vsync: this, length: myTabs.length);
+    Future.delayed(Duration.zero, () {
+      checkUpgrade(context, _dialogKey);
+    });
   }
 
   @override

+ 9 - 8
lib/pages/UserChange.dart

@@ -87,8 +87,7 @@ class UserChangeState extends State<UserChange> {
                                   userInfo.nickname,
                                   placeholder: '请填写昵称',
                                   onTap: () {
-                                    Navigator.push(
-                                        context, CupertinoPageRoute(builder: (context) => ChangeUserInfo(title: '昵称', val: userInfo.nickname)));
+                                    Navigator.push(context, CupertinoPageRoute(builder: (context) => ChangeUserInfo(title: '昵称', val: userInfo.nickname)));
                                   },
                                 ),
                                 _cell(
@@ -129,7 +128,7 @@ class UserChangeState extends State<UserChange> {
                           highlightColor: Color(0xFF763434),
                           child: Text(
                             '退出登录',
-                            style: TextStyle(fontSize: 16, fontWeight:  FontWeight.normal),
+                            style: TextStyle(fontSize: 16, fontWeight: FontWeight.normal),
                           ),
                           onPressed: () async {
                             Toast.show(context, '退出成功', 1500, 'success');
@@ -228,6 +227,7 @@ class UserChangeState extends State<UserChange> {
                       top: 0,
                       bottom: 0,
                       child: CupertinoButton(
+                        padding: EdgeInsets.fromLTRB(15, 0, 0, 0),
                         child: Text(
                           '取消',
                           style: TextStyle(color: PRIMARY_COLOR),
@@ -242,6 +242,7 @@ class UserChangeState extends State<UserChange> {
                       top: 0,
                       bottom: 0,
                       child: CupertinoButton(
+                        padding: EdgeInsets.fromLTRB(0, 0, 15, 0),
                         child: Text(
                           '确定',
                           style: TextStyle(color: PRIMARY_COLOR),
@@ -341,11 +342,11 @@ class UserChangeState extends State<UserChange> {
     } else {
       secondChild = child;
     }
-    return Container(
-      height: 60,
-      padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
-      child: GestureDetector(
-        onTap: onTap,
+    return GestureDetector(
+      onTap: onTap,
+      child: Container(
+        height: 60,
+        padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
         child: Container(
           decoration: BoxDecoration(
             border: Border(

+ 1 - 5
lib/redux/UserRedux.dart

@@ -19,11 +19,7 @@ UserInfo _updateLoaded(UserInfo userInfo, action) {
   final JPush jpush = JPush();
   if (action.userInfo != null) {
     if (userInfo == null || (userInfo.id != action.userInfo.id)) {
-      try {
-        jpush.setAlias(action.userInfo.id.toString()).then((map) {}).catchError((error) => {});
-      } catch (e) {
-        print(e.toString());
-      }
+      jpush.setAlias(action.userInfo.id.toString());
     }
   } else {
     jpush.deleteAlias();

+ 293 - 0
lib/widget/CheckUpgrade.dart

@@ -0,0 +1,293 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_bugly/flutter_bugly.dart';
+import 'package:intl/intl.dart';
+import 'package:dio/dio.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:open_file/open_file.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+Future checkUpgrade(context, key) async {
+  final upgradeInfo = await FlutterBugly.getUpgradeInfo();
+  if (upgradeInfo == null) {
+    return false;
+  }
+  return await showUpgradeDailog(context, upgradeInfo, key);
+}
+
+Future showUpgradeDailog(context, upgradeInfo, key) async {
+  return await showGeneralDialog(
+    context: context,
+    barrierDismissible: false,
+    pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
+      return WillPopScope(
+        onWillPop: () {
+          return Future.value(false);
+        },
+        child: Center(
+          child: Material(
+            color: Colors.transparent,
+            child: UpgradeDialog(upgradeInfo, key: key),
+          ),
+        ),
+      );
+    },
+    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    barrierColor: Colors.black26,
+    transitionDuration: const Duration(milliseconds: 300),
+  );
+}
+
+Future downloadApk(context, url) async {
+  return await showGeneralDialog(
+    context: context,
+    barrierDismissible: false,
+    pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
+      return Center(
+        child: Material(
+          color: Colors.transparent,
+          child: DownloadDialog(url),
+        ),
+      );
+    },
+    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    barrierColor: Colors.black26,
+    transitionDuration: const Duration(milliseconds: 300),
+  );
+}
+
+class UpgradeDialog extends StatefulWidget {
+  final UpgradeInfo upgradeInfo;
+
+  UpgradeDialog(this.upgradeInfo, {Key key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() {
+    return UpgradeDialogState();
+  }
+}
+
+class UpgradeDialogState extends State<UpgradeDialog> {
+  final Color primaryColor = Color(0xFF1990F8);
+  final Color bgColor = Color(0xE6293559);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: 280,
+      padding: EdgeInsets.only(left: 20, right: 20),
+      decoration: BoxDecoration(color: bgColor, border: Border.all(width: 1, color: primaryColor)),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        mainAxisSize: MainAxisSize.min,
+        children: <Widget>[
+          Container(
+            margin: EdgeInsets.only(top: 20),
+            child: Text(
+              widget.upgradeInfo.title,
+              style: TextStyle(color: primaryColor, fontSize: 16, fontWeight: FontWeight.bold),
+              textAlign: TextAlign.center,
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 15),
+            child: Text(
+              '版本:' + widget.upgradeInfo.versionName,
+              style: TextStyle(color: Colors.white),
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 10),
+            child: Text(
+              '包大小:' + (widget.upgradeInfo.fileSize / 1024 / 1024).toStringAsFixed(0) + 'MB',
+              style: TextStyle(color: Colors.white),
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 10),
+            child: Text(
+              '更新时间:' + DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMicrosecondsSinceEpoch(widget.upgradeInfo.publishTime * 1000)),
+              style: TextStyle(color: Colors.white),
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 10),
+            child: Text(
+              '更新说明:',
+              style: TextStyle(color: Colors.white),
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 6),
+            child: Text(
+              widget.upgradeInfo.newFeature,
+              style: TextStyle(color: Colors.white),
+            ),
+          ),
+          Container(
+            margin: EdgeInsets.only(bottom: 20, top: 20),
+            child: Row(
+              children: <Widget>[
+                widget.upgradeInfo.upgradeType == 2
+                    ? Container()
+                    : Expanded(
+                        child: MaterialButton(
+                          elevation: 0,
+                          highlightElevation: 0,
+                          minWidth: 100,
+                          height: 36,
+                          color: Color(0xFF4F5C87),
+                          child: Text(
+                            '下次再说',
+                            style: TextStyle(color: Colors.white),
+                          ),
+                          onPressed: () {
+                            Navigator.of(context).pop();
+                          },
+                        ),
+                      ),
+                widget.upgradeInfo.upgradeType == 2 ? Container() : Container(width: 20),
+                Expanded(
+                  child: MaterialButton(
+                    elevation: 0,
+                    highlightElevation: 0,
+                    minWidth: 100,
+                    height: 36,
+                    color: primaryColor,
+                    child: Text(
+                      '立即更新',
+                      style: TextStyle(color: Colors.white),
+                    ),
+                    onPressed: () async {
+                      PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
+                      if (permission == PermissionStatus.denied) {
+                        Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler().requestPermissions([PermissionGroup.storage]);
+                        if (permissions[PermissionGroup.storage] == PermissionStatus.denied) {
+                          showDialog(
+                              context: context,
+                              barrierDismissible: false,
+                              builder: (context) {
+                                return AlertDialog(
+                                  content: Text(
+                                    '需要获取外部存储权限来下载安装包',
+                                    style: TextStyle(color: Colors.black),
+                                  ),
+                                  actions: <Widget>[
+                                    FlatButton(
+                                      child: Text('打开设置'),
+                                      onPressed: () {
+                                        Navigator.of(context).pop();
+                                        PermissionHandler().openAppSettings();
+                                      },
+                                    ),
+                                  ],
+                                );
+                              });
+                          return;
+                        }
+                      }
+                      Navigator.of(context).pop();
+                      await downloadApk(context, widget.upgradeInfo.apkUrl);
+                    },
+                  ),
+                )
+              ],
+            ),
+          )
+        ],
+      ),
+    );
+  }
+}
+
+class DownloadDialog extends StatefulWidget {
+  final String url;
+
+  DownloadDialog(this.url, {Key key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() {
+    return DownloadDialogState();
+  }
+}
+
+class DownloadDialogState extends State<DownloadDialog> {
+  final Color primaryColor = Color(0xFF1990F8);
+  final Color bgColor = Color(0xE6293559);
+  Dio dio;
+  CancelToken token;
+  double progress = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    Future.delayed(Duration.zero, () async {
+      final Directory tempDir = await getExternalStorageDirectory();
+      final String tempPath = tempDir.path;
+      final String savePath = '$tempPath/update.apk';
+      dio = Dio();
+      token = CancelToken();
+      final res = await dio.download(
+        widget.url,
+        savePath,
+        onReceiveProgress: (int count, int total) {
+          setState(() {
+            progress = count / total;
+          });
+        },
+        options: Options(receiveTimeout: 10 * 60 * 1000),
+        cancelToken: token,
+      ).catchError((_error) {
+        return _error;
+      });
+      if (res != null && res.statusCode == 200) {
+        OpenFile.open(savePath);
+      }
+      Navigator.of(context).pop();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: 300,
+      height: 60,
+      padding: EdgeInsets.fromLTRB(15, 10, 15, 10),
+      decoration: BoxDecoration(color: bgColor, border: Border.all(width: 1, color: primaryColor)),
+      child: Column(
+        children: <Widget>[
+          Row(
+            children: <Widget>[
+              Expanded(
+                child: Text(
+                  '正在下载更新',
+                  style: TextStyle(color: primaryColor, fontSize: 14),
+                ),
+              ),
+              Text(
+                (progress * 100).toStringAsFixed(0) + '%',
+                style: TextStyle(color: Colors.white.withAlpha(128)),
+              )
+            ],
+          ),
+          Container(
+            margin: EdgeInsets.only(top: 10),
+            child: Align(
+              alignment: Alignment.centerLeft,
+              child: FractionallySizedBox(
+                widthFactor: progress,
+                child: Container(
+                  height: 2,
+                  color: primaryColor,
+                ),
+              ),
+            ),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 8 - 7
lib/widget/HomeDrawer.dart

@@ -1,3 +1,4 @@
+import 'package:cached_network_image/cached_network_image.dart';
 import 'package:flutter/material.dart';
 import 'package:redux/redux.dart';
 import 'package:flutter_redux/flutter_redux.dart';
@@ -52,8 +53,8 @@ class HomeDrawerState extends State<HomeDrawer> {
                     children: <Widget>[
                       ClipOval(
                         child: InkWell(
-                          child: Image.network(
-                            userInfo.icon,
+                          child: CachedNetworkImage(
+                            imageUrl: userInfo.icon,
                             width: 86,
                             height: 86,
                             fit: BoxFit.cover,
@@ -170,11 +171,11 @@ class DrawerMenu extends StatelessWidget {
   DrawerMenu(this.icon, this.title, {this.onTap});
   @override
   Widget build(BuildContext context) {
-    return Container(
-      height: 60,
-      padding: EdgeInsets.fromLTRB(30, 0, 28, 0),
-      child: GestureDetector(
-        onTap: onTap,
+    return GestureDetector(
+      onTap: onTap,
+      child: Container(
+        height: 60,
+        padding: EdgeInsets.fromLTRB(30, 0, 28, 0),
         child: Row(
           children: <Widget>[
             Image.asset(icon),

+ 74 - 0
lib/widget/UpdateDialog.dart

@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+
+class UpdateDialog extends StatefulWidget {
+  final key;
+  final version;
+  final Function onClickWhenDownload;
+  final Function onClickWhenNotDownload;
+
+  UpdateDialog(
+    this.key,
+    this.version,
+    this.onClickWhenDownload,
+    this.onClickWhenNotDownload,
+  );
+
+  @override
+  State<StatefulWidget> createState() => UpdateDialogState();
+}
+
+class UpdateDialogState extends State<UpdateDialog> {
+  var _downloadProgress = 0.0;
+
+  @override
+  Widget build(BuildContext context) {
+    var _textStyle = TextStyle(color: Theme.of(context).textTheme.body1.color);
+
+    return AlertDialog(
+      title: Text(
+        "有新的更新",
+        style: _textStyle,
+      ),
+      content: _downloadProgress == 0.0
+          ? Text(
+              "版本${widget.version}",
+              style: _textStyle,
+            )
+          : LinearProgressIndicator(
+              value: _downloadProgress,
+            ),
+      actions: <Widget>[
+        FlatButton(
+          child: Text(
+            '更新',
+            style: _textStyle,
+          ),
+          onPressed: () {
+            if (_downloadProgress != 0.0) {
+              widget.onClickWhenDownload("正在更新中");
+              return;
+            }
+            widget.onClickWhenNotDownload();
+//            Navigator.of(context).pop();
+          },
+        ),
+        FlatButton(
+          child: new Text('取消'),
+          onPressed: () {
+            Navigator.of(context).pop();
+          },
+        ),
+      ],
+    );
+  }
+
+  set progress(_progress) {
+    setState(() {
+      _downloadProgress = _progress;
+      if (_downloadProgress == 1) {
+        Navigator.of(context).pop();
+        _downloadProgress = 0.0;
+      }
+    });
+  }
+}

+ 28 - 0
pubspec.lock

@@ -181,6 +181,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_bugly:
+    dependency: "direct main"
+    description:
+      name: flutter_bugly
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.2.2"
   flutter_cache_manager:
     dependency: transitive
     description:
@@ -303,6 +310,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.2"
+  install_plugin:
+    dependency: "direct main"
+    description:
+      name: install_plugin
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
   intl:
     dependency: "direct main"
     description:
@@ -380,6 +394,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.9.6+3"
+  open_file:
+    dependency: "direct main"
+    description:
+      name: open_file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
   package_config:
     dependency: transitive
     description:
@@ -422,6 +443,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.5.0"
+  permission_handler:
+    dependency: "direct main"
+    description:
+      name: permission_handler
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.0"
   platform:
     dependency: transitive
     description:

+ 4 - 0
pubspec.yaml

@@ -40,6 +40,10 @@ dependencies:
   gradient_text: ^1.0.2
   sprintf: ^4.0.2
   flutter_html: ^0.10.1+hotfix.1
+  flutter_bugly: ^0.2.2
+  install_plugin: ^2.0.1
+  open_file: ^2.0.3
+  permission_handler: ^3.1.0
 
 dev_dependencies:
   build_runner: ^1.1.1