game_info.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import 'dart:io';
  2. import 'dart:ui';
  3. import 'package:cached_network_image/cached_network_image.dart';
  4. import 'package:device_apps/device_apps.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter_rating_bar/flutter_rating_bar.dart';
  7. import 'package:sport/bean/game.dart';
  8. import 'package:sport/constant/ui.dart';
  9. import 'package:sport/constant/ui.dart' show ui_padding, ui_margin_list;
  10. import 'package:sport/router/navigator_util.dart';
  11. import 'package:sport/router/routes.dart';
  12. import 'package:sport/services/api/inject_api.dart';
  13. import 'package:sport/widgets/button_primary.dart';
  14. import 'package:sport/widgets/decoration.dart';
  15. import 'package:sport/widgets/dialog/share_popup.dart';
  16. import 'package:sport/widgets/game_run.dart';
  17. import 'package:sport/widgets/image.dart';
  18. import 'package:sport/widgets/loading.dart';
  19. import 'package:sport/widgets/misc.dart';
  20. import 'package:sport/widgets/space.dart';
  21. class GameInfoView extends StatefulWidget {
  22. @override
  23. State<StatefulWidget> createState() {
  24. return _GameInfoViewState();
  25. }
  26. }
  27. class _GameInfoViewState extends State<GameInfoView> with InjectApi, AutomaticKeepAliveClientMixin {
  28. // 私有属性忘了下划线...
  29. // bool _flag = true;
  30. List<GameInfoData> _data;
  31. void initState() {
  32. initData();
  33. super.initState();
  34. }
  35. initData() async {
  36. final data = await api.getGameAll();
  37. // 为什么我还是选择了用setState vmodel那种是在理解不了???
  38. _data = data.results;
  39. setState(() {});
  40. }
  41. @override
  42. void dispose() {
  43. super.dispose();
  44. }
  45. Future<List<GameInfoData>> checkIsLocal() async {
  46. List<GameInfoData> _myGames = [];
  47. for (GameInfoData data in _data) {
  48. if (Platform.isAndroid) {
  49. data.isLocal = await DeviceApps.isAppInstalled(data.packageNameAndroid);
  50. if (data.isLocal == true) {
  51. _myGames.add(data);
  52. }
  53. }
  54. }
  55. return _myGames;
  56. // setState(() {});
  57. }
  58. Widget _buildLabelWidget(String title) {
  59. return buildLabelWidget(context, title);
  60. }
  61. @override
  62. Widget build(BuildContext context) {
  63. super.build(context);
  64. return Scaffold(
  65. backgroundColor: Colors.white,
  66. body: _data != null
  67. ? CustomScrollView(
  68. slivers: <Widget>[
  69. // SliverToBoxAdapter(
  70. // child: InkWell(
  71. // onTap: () async {
  72. // },
  73. // child: AspectRatio(
  74. // aspectRatio: 375 / 190.0,
  75. // child: Container(
  76. // decoration: BoxDecoration(
  77. // image: DecorationImage(
  78. // image: AssetImage("lib/assets/img/sport_banner.png"),
  79. // fit: BoxFit.cover,
  80. // //设置四周边框
  81. // ),
  82. // ),
  83. // ),
  84. // )),
  85. // ),
  86. // // SliverToBoxAdapter(
  87. // // child: SharePopupDialog(),
  88. // // ),
  89. // SliverToBoxAdapter(
  90. // child: Column(
  91. // crossAxisAlignment: CrossAxisAlignment.start,
  92. // children: <Widget>[
  93. // SizedBox(
  94. // height: 15,
  95. // ),
  96. // _buildLabelWidget("运动项目"),
  97. // ],
  98. // ),
  99. // ),
  100. SliverToBoxAdapter(
  101. child: Container(margin: const EdgeInsets.fromLTRB(ui_padding, ui_padding, ui_padding, 6.0), decoration: card(), child: GameRun())),
  102. SliverList(
  103. delegate: SliverChildBuilderDelegate((content, index) {
  104. var item = _data[index];
  105. return SportItem(item);
  106. }, childCount: _data.length),
  107. ),
  108. // SliverToBoxAdapter(
  109. // child: FutureBuilder<List<GameInfoData>>(
  110. // future: checkIsLocal(),
  111. // builder: (_, snapshot) {
  112. // return snapshot.connectionState != ConnectionState.done
  113. // ? Container()
  114. // : snapshot.data.isEmpty
  115. // ? Container()
  116. // : Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
  117. // _buildLabelWidget("我的运动"),
  118. // Column(
  119. // children: snapshot.data
  120. // .map((item) => GameItem(
  121. // type: 1,
  122. // imgUrl: item.cover,
  123. // name: item.name,
  124. // time: item.sum.lastPlayAt,
  125. // id: item.id,
  126. // duration: item.sum.durationTotal,
  127. // data: item,
  128. // ))
  129. // .toList()),
  130. // ]);
  131. // },
  132. // ),
  133. // ),
  134. SliverToBoxAdapter(
  135. child: SizedBox(
  136. height: 50,
  137. ),
  138. )
  139. ],
  140. )
  141. : RequestLoadingWidget());
  142. }
  143. @override
  144. bool get wantKeepAlive => true;
  145. }
  146. // 中间的那个游戏列表
  147. class SportItem extends StatelessWidget {
  148. final GameInfoData item;
  149. SportItem(this.item);
  150. @override
  151. Widget build(BuildContext context) {
  152. return InkWell(
  153. child: AspectRatio(
  154. aspectRatio: 351 / 120.0,
  155. child: Container(
  156. margin: const EdgeInsets.fromLTRB(ui_padding, 6.0, ui_padding, 6.0),
  157. // 约束宽高的
  158. decoration: BoxDecoration(
  159. image: DecorationImage(
  160. image: CachedNetworkImageProvider(item.coverHorizontal), // NetWorkImage 返回的是Image
  161. fit: BoxFit.cover,
  162. //设置四周边框
  163. ),
  164. borderRadius: const BorderRadius.all(Radius.circular(10.0)),
  165. ),
  166. child: Align(
  167. alignment: Alignment.bottomCenter,
  168. child: Container(
  169. width: double.infinity,
  170. // margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
  171. // alignment: Alignment.bottomCenter,
  172. decoration: const BoxDecoration(
  173. gradient: const LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [
  174. const Color(0x00000000),
  175. // Color(0x00000000),
  176. const Color(0xAA000000)
  177. ]),
  178. borderRadius: const BorderRadius.vertical(bottom: Radius.circular(10.0)),
  179. ),
  180. child: Container(
  181. padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0),
  182. child: Column(
  183. mainAxisSize: MainAxisSize.min,
  184. mainAxisAlignment: MainAxisAlignment.end,
  185. crossAxisAlignment: CrossAxisAlignment.start,
  186. children: <Widget>[
  187. Text(
  188. "${item.name}",
  189. style: Theme.of(context).textTheme.headline4.copyWith(fontSize: 20.0),
  190. ),
  191. const SizedBox(height: 5.0,),
  192. Row(
  193. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  194. children: <Widget>[
  195. Text("难度:", style: Theme.of(context).textTheme.bodyText1.copyWith(color: Colors.white)),
  196. Expanded(
  197. child: RatingBarIndicator(
  198. rating: item.difficulty / 20.0,
  199. itemBuilder: (context, index) => Image.asset("lib/assets/img/con_icon_difficulty_normal.png", color: const Color(0xffFFC400),),
  200. itemCount: 5,
  201. itemSize: 14.0,
  202. unratedColor: const Color(0xffCECECE),
  203. direction: Axis.horizontal,
  204. ),
  205. ),
  206. Text("${item.userCount}人在练", style: Theme.of(context).textTheme.bodyText1.copyWith(color: Colors.white))
  207. ],
  208. )
  209. ],
  210. ),
  211. ),
  212. ),
  213. ),
  214. ),
  215. ),
  216. onTap: () {
  217. NavigatorUtil.goGameDetails(context, details: item).then((value) {
  218. });
  219. },
  220. );
  221. }
  222. }
  223. // 最下面的那个
  224. // @type = 1 继续按钮
  225. // @type = 2 右箭头 →
  226. //
  227. class GameItem extends StatelessWidget {
  228. int type;
  229. String name;
  230. String desc;
  231. String imgUrl;
  232. String time;
  233. int id;
  234. int duration;
  235. GameInfoData data;
  236. bool bold;
  237. GameItem({@required this.type, this.name, this.imgUrl, this.time, this.id, this.duration, this.desc, this.data, this.bold = false});
  238. @override
  239. Widget build(BuildContext context) {
  240. return Container(
  241. margin: EdgeInsets.fromLTRB(ui_padding, 6.0, ui_padding, 6.0),
  242. padding: EdgeInsets.all(ui_padding),
  243. decoration: card(),
  244. child: GestureDetector(
  245. behavior: HitTestBehavior.opaque,
  246. onTap: () {
  247. print(type);
  248. if (type == 2) {
  249. print(id);
  250. NavigatorUtil.goRankDetails(context, id, 1);
  251. } else {
  252. NavigatorUtil.goGameDetails(context, details: this.data);
  253. }
  254. },
  255. child: Row(
  256. mainAxisAlignment: MainAxisAlignment.start,
  257. crossAxisAlignment: CrossAxisAlignment.center,
  258. children: <Widget>[
  259. Container(
  260. width: 70.0,
  261. height: 70.0,
  262. margin: EdgeInsets.only(right: 12.0),
  263. child: ClipRRect(
  264. child: CachedNetworkImage(
  265. imageUrl: imgUrl,
  266. fit: BoxFit.cover,
  267. ),
  268. // 也可控件一边圆角大小
  269. borderRadius: new BorderRadius.all(Radius.circular(6.0)),
  270. ),
  271. ),
  272. Expanded(
  273. child: Column(
  274. crossAxisAlignment: CrossAxisAlignment.start,
  275. children: <Widget>[
  276. Text(
  277. "$name",
  278. style: bold
  279. ? Theme.of(context).textTheme.subtitle1.copyWith(fontSize: 16.0, fontWeight: FontWeight.w600)
  280. : Theme.of(context).textTheme.subtitle1.copyWith(fontSize: 16.0),
  281. ),
  282. SizedBox(
  283. height: 3,
  284. ),
  285. type == 1
  286. ? Text(
  287. "总时长:${duration ~/ 60 != 0 ? "${duration ~/ 60}时" : ""}${duration % 60}分钟",
  288. style: Theme.of(context).textTheme.bodyText1,
  289. )
  290. : Text("$desc", style: Theme.of(context).textTheme.bodyText1, maxLines: 1),
  291. SizedBox(
  292. height: 6,
  293. ),
  294. type == 1
  295. ? Text(
  296. "最近打开: ${time ?? '未进行运动'}",
  297. style: Theme.of(context).textTheme.bodyText1,
  298. maxLines: 1,
  299. )
  300. : Text(
  301. "${time}",
  302. style: Theme.of(context).textTheme.bodyText1,
  303. maxLines: 1,
  304. ),
  305. ],
  306. ),
  307. ),
  308. type == 1
  309. ? PrimaryButton(
  310. width: 72,
  311. height: 35,
  312. content: "继续",
  313. callback: () {
  314. NavigatorUtil.goGameDetails(context, details: this.data);
  315. },
  316. )
  317. : arrowRight7()
  318. ],
  319. ),
  320. ),
  321. );
  322. }
  323. }