game_detail.dart 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:math';
  5. import 'package:android_intent/android_intent.dart';
  6. import 'package:android_intent/flag.dart';
  7. import 'package:cached_network_image/cached_network_image.dart';
  8. import 'package:device_apps/device_apps.dart' as app;
  9. import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
  10. import 'package:flutter/material.dart' hide NestedScrollView;
  11. import 'package:flutter/services.dart';
  12. import 'package:install_plugin/install_plugin.dart';
  13. import 'package:orientation/orientation.dart';
  14. import 'package:permission_handler/permission_handler.dart';
  15. import 'package:provider/provider.dart';
  16. import 'package:shared_preferences/shared_preferences.dart';
  17. import 'package:sport/application.dart';
  18. import 'package:sport/bean/game.dart';
  19. import 'package:sport/bean/rank_game_info.dart';
  20. import 'package:sport/provider/bluetooth.dart';
  21. import 'package:sport/provider/user_model.dart';
  22. import 'package:sport/router/navigator_util.dart';
  23. import 'package:sport/services/Converter.dart';
  24. import 'package:sport/services/api/inject_api.dart';
  25. import 'package:sport/services/api/resp.dart';
  26. import 'package:sport/services/app_lifecycle_state.dart';
  27. import 'package:sport/services/game_manager.dart';
  28. import 'package:sport/utils/click.dart';
  29. import 'package:sport/utils/download.dart';
  30. import 'package:sport/utils/toast.dart';
  31. import 'package:sport/widgets/button_primary.dart';
  32. import 'package:sport/widgets/decoration.dart';
  33. import 'package:sport/widgets/dialog/request_dialog.dart';
  34. import 'package:sport/widgets/image.dart';
  35. import 'package:sport/widgets/misc.dart';
  36. import 'package:sport/widgets/persistent_header.dart';
  37. import 'package:url_launcher/url_launcher.dart';
  38. import 'package:video_player/video_player.dart';
  39. import 'detail_bottom.dart';
  40. import 'game_video.dart';
  41. class GameDetailsPage extends StatefulWidget {
  42. final GameInfoData details;
  43. GameDetailsPage(this.details);
  44. @override
  45. State<StatefulWidget> createState() {
  46. return _GameDetailPageState();
  47. }
  48. }
  49. class _GameDetailPageState extends State<GameDetailsPage> with TickerProviderStateMixin, InjectApi, RouteAware {
  50. bool _isFullScreen; // 是否全屏
  51. VideoPlayerController _controller; // 播放的controller
  52. bool isLoading = true; // 是否loading
  53. Future<void> _initializeVideoPlayerFuture; // 播放的回调
  54. TabController _tabController; // 中间的tab栏
  55. FocusNode _comment = FocusNode();
  56. bool isChange = false;
  57. Timer timer;
  58. DeviceOrientation _lastEvent;
  59. StreamSubscription _streamSubscription;
  60. bool _isPlaying;
  61. @override
  62. void didChangeDependencies() {
  63. super.didChangeDependencies();
  64. routeObserver.subscribe(this, ModalRoute.of(context));
  65. }
  66. @override
  67. void didPopNext() {
  68. _registerListener();
  69. if(_isPlaying == true){
  70. _controller?.play();
  71. }
  72. }
  73. @override
  74. void didPushNext() {
  75. _streamSubscription?.cancel();
  76. _isPlaying = _controller?.value?.isPlaying;
  77. _controller?.pause();
  78. }
  79. @override
  80. void initState() {
  81. super.initState();
  82. _isFullScreen = false;
  83. // 重置一手
  84. // OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);
  85. if (Platform.isAndroid) {
  86. //设置Android头部的导航栏透明
  87. SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));
  88. }
  89. _controller = VideoPlayerController.network(widget.details.introduceVideo); // 初始化第一个?
  90. _initializeVideoPlayerFuture = _controller.initialize().then((value) {
  91. if (mounted)
  92. setState(() {
  93. isLoading = false;
  94. });
  95. });
  96. _tabController = new TabController(length: 2, vsync: this);
  97. _registerListener();
  98. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  99. }
  100. // Function throttle({Function f, int delay}) {
  101. // var date = DateTime.now().microsecondsSinceEpoch;
  102. // print('$date-----------------------------------prepreprepre');
  103. // return () {
  104. // var now = DateTime.now().microsecondsSinceEpoch;
  105. // print("$now-----------------------------------nownownownow");
  106. // if (now - date >= delay) {
  107. // print("执行——————————————————————————————————————————————");
  108. // f();
  109. // }
  110. // }();
  111. // }
  112. @override
  113. void dispose() {
  114. routeObserver.unsubscribe(this);
  115. _streamSubscription?.cancel();
  116. super.dispose();
  117. _initializeVideoPlayerFuture = null;
  118. _controller?.pause();
  119. _controller?.dispose();
  120. _tabController?.dispose();
  121. }
  122. void _registerListener() {
  123. // 20200927 说不需要了!
  124. // _streamSubscription = OrientationPlugin.onOrientationChange.listen(
  125. // (event) {
  126. // print("$mounted $_lastEvent $event");
  127. // if (!mounted) return;
  128. // if (_lastEvent == event) {
  129. // return;
  130. // }
  131. // _lastEvent = event;
  132. //
  133. // if (event == DeviceOrientation.portraitUp) {
  134. // setState(() {
  135. // _isFullScreen = false;
  136. // SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  137. // });
  138. // } else if (event == DeviceOrientation.landscapeRight) {
  139. // List<DeviceOrientation> _orientation = Platform.isAndroid ? [DeviceOrientation.landscapeLeft] : [DeviceOrientation.landscapeRight];
  140. // setState(() {
  141. // _isFullScreen = true;
  142. // SystemChrome.setPreferredOrientations(_orientation);
  143. // });
  144. // } else if (event == DeviceOrientation.landscapeLeft) {
  145. // List<DeviceOrientation> _orientation = Platform.isAndroid ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft];
  146. // setState(() {
  147. // _isFullScreen = true;
  148. // SystemChrome.setPreferredOrientations(_orientation);
  149. // });
  150. // }
  151. // },
  152. // );
  153. }
  154. void changeIsFullScreen() {
  155. if (_isFullScreen) {
  156. setState(() {
  157. _isFullScreen = false;
  158. SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  159. });
  160. } else {
  161. setState(() {
  162. _isFullScreen = true;
  163. SystemChrome.setPreferredOrientations(Platform.isAndroid ? [DeviceOrientation.landscapeLeft] : [DeviceOrientation.landscapeRight]);
  164. });
  165. }
  166. }
  167. void autoFocus() {
  168. FocusScope.of(context).requestFocus(_comment);
  169. }
  170. @override
  171. Widget build(BuildContext context) {
  172. // 全屏隐藏 状态栏 和 底部 虚拟键
  173. GameInfoData _data = widget.details;
  174. if (_isFullScreen) {
  175. SystemChrome.setEnabledSystemUIOverlays([]);
  176. } else {
  177. SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]);
  178. }
  179. final double tabHeader = 50;
  180. return AnnotatedRegion<SystemUiOverlayStyle>(
  181. value: SystemUiOverlayStyle.light,
  182. child: Material(
  183. child: _isFullScreen
  184. ? Scaffold(
  185. backgroundColor: Colors.black,
  186. body: WillPopScope(
  187. onWillPop: () async {
  188. setState(() {
  189. _isFullScreen = false;
  190. OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);
  191. });
  192. return false;
  193. },
  194. child: Center(
  195. child: GameDetailsVideo(changeIsFullScreen, _isFullScreen, _controller, isLoading, _data),
  196. ),
  197. ))
  198. : Scaffold(
  199. backgroundColor: Colors.black,
  200. body: SafeArea(
  201. child: Container(
  202. color: Colors.white,
  203. child: Column(
  204. children: <Widget>[
  205. Expanded(
  206. child: NestedScrollView(
  207. pinnedHeaderSliverHeightBuilder: () {
  208. return tabHeader;
  209. },
  210. innerScrollPositionKeyBuilder: () {
  211. TabController tabController = _tabController;
  212. String index = 'Tab${tabController.index}';
  213. return Key(index);
  214. },
  215. headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  216. return [
  217. // headerVideo
  218. SliverToBoxAdapter(
  219. child: GameDetailsVideo(changeIsFullScreen, _isFullScreen, _controller, isLoading, _data),
  220. ),
  221. SliverToBoxAdapter(
  222. child: DetailContent(
  223. _data.name, _data.introduce, _data.likeCount, _data.cover, widget.details.id, _data.isLike, _data.rank),
  224. ),
  225. SliverToBoxAdapter(
  226. child: Divider(),
  227. ),
  228. SliverPersistentHeader(
  229. delegate: PersistentHeader(
  230. min: tabHeader,
  231. max: tabHeader,
  232. child: Container(
  233. color: Colors.white,
  234. height: tabHeader,
  235. padding: EdgeInsets.symmetric(vertical: 8.0),
  236. child: TabBar(
  237. controller: _tabController,
  238. isScrollable: true,
  239. indicatorPadding: EdgeInsets.symmetric(horizontal: 6),
  240. indicatorWeight: 3,
  241. tabs: <Widget>[
  242. Tab(text: "详情"),
  243. Tab(text: "评论"),
  244. ],
  245. ),
  246. )),
  247. pinned: true,
  248. ),
  249. ];
  250. },
  251. body: TabBarView(
  252. controller: _tabController,
  253. children: <Widget>[
  254. NestedScrollViewInnerScrollPositionKeyWidget(const Key('Tab0'),
  255. TabDetail(_data.introduceImages, _data.tags, _data.fileSize, _data.publishDate, _data.developCompany)),
  256. NestedScrollViewInnerScrollPositionKeyWidget(const Key('Tab1'), Center(child: TabComment(_data.subjectId))),
  257. ],
  258. ),
  259. ),
  260. ),
  261. Platform.isAndroid
  262. ? PositionedBottom(_data, (start) async {
  263. // request(context, () async {
  264. // await api
  265. // .postAddGame(
  266. // widget.details.id,
  267. // Random().nextDouble() * 100,
  268. // (Random().nextDouble() * 40).toInt(),
  269. // Random().nextInt(300),
  270. // start,
  271. // (Random().nextDouble() * 100).toInt(),
  272. // (Random().nextDouble() * 100).toInt(),
  273. // (Random().nextDouble() * 100).toInt(),
  274. // (Random().nextDouble() * 200).toInt())
  275. // .catchError((err) {});
  276. // ToastUtil.show("提交信息成功");
  277. // });
  278. if (Platform.isAndroid) {
  279. SharedPreferences prefs = await SharedPreferences.getInstance();
  280. String token = prefs.getString("token");
  281. // AndroidIntent intent = AndroidIntent(
  282. // action: "android.intent.action.MAIN",
  283. // package: _data.packageNameAndroid,
  284. // componentName: "${_data.packageNameAndroid}.sdk.UnityPlayerActivity",
  285. // data: "oujgame://${_data.packageNameAndroid}",
  286. // flags: [
  287. // Flag.FLAG_ACTIVITY_NEW_TASK
  288. // ],
  289. // arguments: {
  290. // 'token': token,
  291. // 'mac': Provider.of<Bluetooth>(context, listen: false)?.device?.id?.toString() ?? '',
  292. // 'game_id': "${_data.id}",
  293. // 'user_id': Provider.of<UserModel>(context, listen: false)?.user?.id?.toString() ?? '',
  294. // 'user': Provider.of<UserModel>(context, listen: false)?.user?.toJsonSimple() ?? ''
  295. // });
  296. AndroidIntent intent = AndroidIntent(
  297. action: "android.intent.action.MAIN",
  298. package: _data.packageNameAndroid,
  299. componentName: "com.ouj.shoe.sdklibrary.GameActivity",
  300. data: "oujgame://com.ouj.shoe",
  301. flags: [
  302. Flag.FLAG_ACTIVITY_NEW_TASK
  303. ],
  304. arguments: {
  305. 'token': token,
  306. 'mac': Provider.of<Bluetooth>(context, listen: false)?.device?.id?.toString() ?? '',
  307. 'game_id': "${_data.id}",
  308. 'user_id': Provider.of<UserModel>(context, listen: false)?.user?.id?.toString() ?? '',
  309. 'user': json.encode(Provider.of<UserModel>(context, listen: false)?.user?.toJsonSimple() ?? '{}')
  310. });
  311. intent.launch();
  312. // app.DeviceApps.openApp(_data.packageNameAndroid)
  313. }
  314. ToastUtil.show("正在打开... ${_data.name} ${_data.packageNameAndroid}");
  315. })
  316. : Container(
  317. width: double.infinity,
  318. height: 50.0,
  319. padding: EdgeInsets.symmetric(vertical: 5),
  320. decoration: shadowTop(),
  321. child: InkWell(
  322. child: Container(
  323. alignment: Alignment.center,
  324. margin: EdgeInsets.symmetric(horizontal: 12.0),
  325. height: 40.0,
  326. decoration: BoxDecoration(
  327. borderRadius: BorderRadius.all(Radius.circular(20.0)),
  328. border: new Border.all(width: 1, color: Theme.of(context).accentColor),
  329. color: Theme.of(context).accentColor,
  330. gradient: new LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [
  331. Color.fromRGBO(255, 196, 0, 1),
  332. Color.fromRGBO(255, 170, 0, 1),
  333. ]),
  334. ),
  335. child: Text(
  336. "go app store",
  337. style: TextStyle(
  338. color: Colors.white,
  339. fontSize: 14.0,
  340. fontWeight: FontWeight.w400,
  341. ),
  342. )),
  343. onTap: () {
  344. launch("https://itunes.apple.com/cn/app/hi-yun-dong-nin-jian-shen/id1093054311?l=zh&ls=1&mt=8");
  345. },
  346. ))
  347. ],
  348. )),
  349. ),
  350. )));
  351. }
  352. }
  353. class DetailContent extends StatefulWidget {
  354. String _title;
  355. String _introduce;
  356. int _likeCount;
  357. String _imgUrl;
  358. int id;
  359. bool isLike;
  360. Rank rank;
  361. DetailContent(this._title, this._introduce, this._likeCount, this._imgUrl, this.id, this.isLike, this.rank);
  362. createState() => _DetailContentState();
  363. }
  364. class _DetailContentState extends State<DetailContent> with InjectApi {
  365. bool defaultLike;
  366. bool isLike;
  367. Rank _rank;
  368. Future _getRankFuture;
  369. initState() {
  370. defaultLike = isLike = Application.gameLikes[widget.id] ?? widget.isLike ?? false;
  371. _rank = widget.rank;
  372. _getRankFuture = _getRank();
  373. super.initState();
  374. }
  375. @override
  376. void dispose() {
  377. super.dispose();
  378. }
  379. Future<Rank> _getRank() async {
  380. if (_rank == null) {
  381. RespList<GameInfoData> resp = await api.getGameAll();
  382. _rank = resp.results.firstWhere((element) => element.id == widget.id).rank;
  383. }
  384. return _rank;
  385. }
  386. Widget _rankWidget(Rank rank) {
  387. return Row(
  388. children: <Widget>[
  389. Container(
  390. margin: EdgeInsets.only(right: 10.0),
  391. decoration: ShapeDecoration(
  392. image: DecorationImage(image: CachedNetworkImageProvider(rank.logo ?? ''), fit: BoxFit.cover),
  393. shape: RoundedRectangleBorder(borderRadius: BorderRadiusDirectional.circular(6))),
  394. width: 50.0,
  395. height: 50.0,
  396. ),
  397. Expanded(
  398. child: Column(
  399. crossAxisAlignment: CrossAxisAlignment.start,
  400. mainAxisSize: MainAxisSize.min,
  401. children: <Widget>[
  402. Text(
  403. "${rank.name}",
  404. style: Theme.of(context).textTheme.headline3,
  405. ),
  406. Padding(
  407. padding: EdgeInsets.only(top: 5.0),
  408. child: Text("${rank.introduce}", style: TextStyle(fontSize: 12.0, color: Color.fromRGBO(153, 153, 153, 1))),
  409. ),
  410. ],
  411. ),
  412. ),
  413. arrowRight7()
  414. ],
  415. );
  416. }
  417. @override
  418. Widget build(BuildContext context) {
  419. return Container(
  420. padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0, bottom: 0.0),
  421. child: Column(
  422. children: <Widget>[
  423. // content header
  424. Row(
  425. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  426. children: <Widget>[
  427. Text(
  428. "${widget._title}",
  429. style: Theme.of(context).textTheme.headline1,
  430. ),
  431. InkWell(
  432. child: Row(
  433. children: <Widget>[
  434. Image.asset(
  435. isLike ? "lib/assets/img/bbs_icon_like_complete.png" : "lib/assets/img/bbs_icon_like.png",
  436. width: 26,
  437. height: 38,
  438. ),
  439. Padding(
  440. padding: EdgeInsets.only(left: 5.0),
  441. child: Text("${max(0, widget._likeCount + (isLike == true ? defaultLike ? 0 : 1 : defaultLike ? -1 : 0))}"),
  442. )
  443. ],
  444. ),
  445. onTap: throttle(() async {
  446. var data = await request(context, () async {
  447. RespData<String> data;
  448. if (!isLike) {
  449. data = await api.postForumLike('${widget.id}', 'game_id');
  450. } else {
  451. data = await api.postForumUnLike('${widget.id}', 'game_id');
  452. }
  453. return data;
  454. });
  455. if (data?.code == 0) {
  456. setState(() {
  457. isLike = !isLike;
  458. Application.gameLikes[widget.id] = isLike;
  459. });
  460. }
  461. }),
  462. )
  463. ],
  464. ),
  465. SizedBox(
  466. height: 5,
  467. ),
  468. Container(
  469. alignment: Alignment.centerLeft,
  470. child: Text("${widget._introduce}", style: TextStyle(height: 1.5, fontSize: 14.0, color: Color.fromRGBO(153, 153, 153, 1))),
  471. ),
  472. SizedBox(
  473. height: 5,
  474. ),
  475. InkWell(
  476. child: Container(
  477. height: 75,
  478. padding: EdgeInsets.symmetric(vertical: 10.0),
  479. child: widget.rank == null
  480. ? FutureBuilder<Rank>(
  481. future: _getRankFuture,
  482. builder: (BuildContext context, AsyncSnapshot<Rank> snapshot) {
  483. if (snapshot.connectionState == ConnectionState.done) {
  484. return _rankWidget(snapshot.data);
  485. }
  486. return Container();
  487. },
  488. )
  489. : _rankWidget(widget.rank),
  490. ),
  491. onTap: () {
  492. NavigatorUtil.goRankDetails(context, widget.id, 1);
  493. },
  494. ),
  495. ],
  496. ),
  497. );
  498. }
  499. }
  500. // bottom Button
  501. class PositionedBottom extends StatefulWidget {
  502. final GameInfoData data;
  503. final Function startCallBack;
  504. PositionedBottom(this.data, this.startCallBack);
  505. @override
  506. State<StatefulWidget> createState() {
  507. return _PositionedBottomState();
  508. }
  509. }
  510. class _PositionedBottomState extends LifecycleState<PositionedBottom> {
  511. int _viewIndex = 3;
  512. double _percent = 0.0;
  513. bool _nonDownloading = false; // 是否在下载
  514. bool _exits = false; // 是否下载完成
  515. @override
  516. void initState() {
  517. super.initState();
  518. _isReady();
  519. }
  520. @override
  521. void dispose() {
  522. super.dispose();
  523. stopDownload();
  524. }
  525. @override
  526. Future didChangeAppLifecycleState(AppLifecycleState state) async {
  527. super.didChangeAppLifecycleState(state);
  528. if (state == AppLifecycleState.resumed) {
  529. _isReady();
  530. }
  531. }
  532. _version(String versionName) {
  533. if (versionName?.isEmpty == true) return [0, 0, 0];
  534. var arr = versionName.split(".");
  535. if (arr.length < 3) arr.add("0");
  536. return arr.map((e) => Converter.toInt(e)).toList();
  537. }
  538. /**
  539. * v1: local
  540. * v2: net
  541. */
  542. _versionCompare(String v1, String v2) {
  543. if (v1 == v2) return 0;
  544. var clientVersion = _version(v1);
  545. var baseVersion = _version(v2);
  546. print("$clientVersion $baseVersion");
  547. int client = (clientVersion[0] << 20) | (clientVersion[1] << 10) | clientVersion[2];
  548. int base = (baseVersion[0] << 20) | (baseVersion[1] << 10) | baseVersion[2];
  549. if (client > base) {
  550. return 1;
  551. } else if (client == base) {
  552. return 0;
  553. } else {
  554. return -1;
  555. }
  556. }
  557. _isReady() async {
  558. if (Platform.isAndroid) {
  559. var sport = await app.DeviceApps.getApp(widget.data.packageNameAndroid);
  560. if (sport != null) {
  561. var v = _versionCompare(sport.versionName, widget.data.version);
  562. print("111111111 $sport $v");
  563. if (v >= 0) {
  564. setState(() {
  565. _viewIndex = 0;
  566. });
  567. return;
  568. }
  569. }
  570. int length = await _checkDownload();
  571. if (length > 0) {
  572. print("length: $length, total: ${widget.data.fileSizeByte} $_percent");
  573. _percent = (length / widget.data.fileSizeByte) * 100;
  574. if (_percent >= 100) _exits = true;
  575. }
  576. }
  577. setState(() {
  578. _viewIndex = 1;
  579. });
  580. }
  581. void _installApk(String path) async {
  582. if (path.isEmpty) {
  583. print('make sure the apk file is set');
  584. return;
  585. }
  586. InstallPlugin.installApk(path, 'xie.hiyd.com').then((result) {
  587. print('install apk $result');
  588. }).catchError((error) {
  589. print('install apk error: $error');
  590. });
  591. }
  592. Future<int> _checkDownload() async {
  593. File savePath = await GameManager.createFile(widget.data);
  594. if (savePath == null || !await savePath.exists()) {
  595. return 0;
  596. }
  597. return savePath.lengthSync();
  598. }
  599. _startDownload() async {
  600. bool storage = await Application.requestPermission(Permission.storage);
  601. if (!storage) {
  602. ToastUtil.show("没有下载文件权限");
  603. return;
  604. }
  605. File savePath = await GameManager.createFile(widget.data);
  606. if (savePath == null) {
  607. ToastUtil.show("创建文件失败");
  608. return;
  609. }
  610. _nonDownloading = true;
  611. setState(() {
  612. _viewIndex = 2;
  613. });
  614. var result = await DownLoadManage().download(widget.data.downloadUrl, savePath.path, onReceiveProgress: (received, total) {
  615. if (total != -1) {
  616. // print("下载1已接收:" +
  617. // received.toString() +
  618. // "总共:" +
  619. // total.toString() +
  620. // "进度:+${(received / total * 100).floor()}%");
  621. if (mounted)
  622. setState(() {
  623. _viewIndex = 2;
  624. if (mounted) _percent = (received / total * 100);
  625. });
  626. }
  627. }, done: () async {
  628. setState(() {
  629. _exits = true;
  630. _viewIndex = 1;
  631. });
  632. // setState(() {
  633. // });
  634. print("下载1完成");
  635. if (Platform.isAndroid) {
  636. _installApk(savePath.path);
  637. }
  638. }, failed: (e) {
  639. print("下载1失败:" + e.toString());
  640. setState(() {
  641. _exits = false;
  642. _viewIndex = 1;
  643. });
  644. });
  645. _nonDownloading = false;
  646. print("下载停止");
  647. return result;
  648. }
  649. stopDownload() {
  650. DownLoadManage().stop(widget.data.downloadUrl);
  651. _nonDownloading = false;
  652. }
  653. Widget _buildGameDownloadProgress() {
  654. final double PROGRESSITEM = (MediaQuery.of(context).size.width - 24) / 100; // 分成100份
  655. return Container(
  656. margin: EdgeInsets.symmetric(horizontal: 12.0),
  657. height: 40.0,
  658. child: InkWell(
  659. child: Stack(
  660. children: <Widget>[
  661. ClipRRect(
  662. borderRadius: BorderRadius.circular(22.0),
  663. child: SizedBox(
  664. height: 40.0,
  665. child: LinearProgressIndicator(
  666. backgroundColor: Color.fromRGBO(220, 220, 220, 1),
  667. valueColor: AlwaysStoppedAnimation(Theme.of(context).accentColor),
  668. value: _percent / 100,
  669. ),
  670. ),
  671. ),
  672. Container(
  673. alignment: Alignment.center,
  674. child: Text(
  675. _percent >= 100.0 ? "正在校验安装包" : "正在下载${_percent.toStringAsFixed(1)}%",
  676. style: textStyle,
  677. strutStyle: fixedLine,
  678. ),
  679. )
  680. ],
  681. ),
  682. onTap: () async {
  683. if (_percent != 100.0) {
  684. if (_nonDownloading) {
  685. stopDownload();
  686. setState(() {
  687. _viewIndex = 1;
  688. });
  689. }
  690. }
  691. },
  692. ),
  693. );
  694. }
  695. Widget _buildGameDownload() {
  696. return InkWell(
  697. child: !_exits && _percent > 0
  698. ? Container(
  699. alignment: Alignment.center,
  700. margin: EdgeInsets.symmetric(horizontal: 12.0),
  701. height: 40.0,
  702. child: Stack(
  703. children: <Widget>[
  704. ClipRRect(
  705. borderRadius: BorderRadius.circular(22.0),
  706. child: SizedBox(
  707. height: 40.0,
  708. child: LinearProgressIndicator(
  709. backgroundColor: Color.fromRGBO(220, 220, 220, 1),
  710. valueColor: AlwaysStoppedAnimation(Theme.of(context).accentColor),
  711. value: _percent / 100,
  712. ),
  713. ),
  714. ),
  715. Container(
  716. alignment: Alignment.center,
  717. child: Text(
  718. "继续下载",
  719. style: textStyle,
  720. strutStyle: fixedLine,
  721. ),
  722. )
  723. ],
  724. ))
  725. : Container(
  726. alignment: Alignment.center,
  727. margin: EdgeInsets.symmetric(horizontal: 12.0),
  728. height: 40.0,
  729. decoration: BoxDecoration(
  730. borderRadius: BorderRadius.all(Radius.circular(20.0)),
  731. border: new Border.all(width: 1, color: Theme.of(context).accentColor),
  732. color: Theme.of(context).accentColor,
  733. boxShadow: [BoxShadow(offset: Offset(0.0, 3), blurRadius: 3, spreadRadius: 0, color: Color(0x82FF9100))],
  734. gradient: new LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [
  735. Color.fromRGBO(255, 196, 0, 1),
  736. Color.fromRGBO(255, 170, 0, 1),
  737. ]),
  738. ),
  739. child: Text(
  740. _exits ? "安装应用" : _percent > 0 ? "继续下载${_percent.toStringAsFixed(1)}%" : "开始下载(${widget.data.fileSize}M)",
  741. style: textStyle,
  742. strutStyle: fixedLine,
  743. )),
  744. onTap: () async {
  745. if (!_nonDownloading) {
  746. _startDownload();
  747. }
  748. },
  749. );
  750. }
  751. Widget _buildGameStart() {
  752. return Padding(
  753. padding: const EdgeInsets.symmetric(horizontal: 12.0),
  754. child: PrimaryButton(
  755. callback: () => widget.startCallBack(1),
  756. content: "开始运动",
  757. height: 40,
  758. bold: true,
  759. ),
  760. );
  761. }
  762. var textStyle = TextStyle(
  763. color: Colors.white,
  764. fontSize: 14.0,
  765. fontWeight: FontWeight.w600,
  766. );
  767. @override
  768. Widget build(BuildContext context) {
  769. return Container(
  770. width: double.infinity,
  771. height: 50.0,
  772. decoration: shadowTop(),
  773. child: IndexedStack(
  774. index: _viewIndex,
  775. alignment: Alignment.center,
  776. children: <Widget>[_buildGameStart(), _buildGameDownload(), _buildGameDownloadProgress(), CircularProgressIndicator()]));
  777. }
  778. }