game_detail.dart 34 KB

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