import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:android_intent/android_intent.dart'; import 'package:android_intent/flag.dart'; import 'package:app_installer/app_installer.dart'; import 'package:broadcast/broadcast.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:device_apps/device_apps.dart' as app; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_swiper_plus/flutter_swiper_plus.dart'; import 'package:get_it/get_it.dart'; import 'package:package_info/package_info.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sport/application.dart'; import 'package:sport/bean/feed_back.dart'; import 'package:sport/bean/game.dart'; import 'package:sport/bean/image.dart' as photo; import 'package:sport/bean/rank_game_info.dart'; import 'package:sport/bean/user_info.dart'; import 'package:sport/pages/game/game_guide.dart'; import 'package:sport/pages/game/rank_detail.dart'; import 'package:sport/pages/my/feedback_detail_page.dart'; import 'package:sport/pages/social/gallery_photo_view.dart'; import 'package:sport/pages/social/post_comment.dart'; import 'package:sport/pages/web/game_page.dart'; import 'package:sport/pages/web/web_page.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/provider/game_info_model.dart'; import 'package:sport/provider/game_model.dart'; import 'package:sport/provider/lib/provider_widget.dart'; import 'package:sport/provider/login_info_model.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/services/api/inject_api.dart'; import 'package:sport/services/api/resp.dart'; import 'package:sport/services/app_lifecycle_state.dart'; import 'package:sport/services/game_manager.dart'; import 'package:sport/utils/DateFormat.dart'; import 'package:sport/utils/download.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/utils/version.dart'; import 'package:sport/widgets/appbar.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/dialog/alert_dialog.dart'; import 'package:sport/widgets/dialog/bindphone_dialog.dart'; import 'package:sport/widgets/dialog/request_dialog.dart'; import 'package:sport/widgets/error.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/loading.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/popmenu_bg.dart'; import 'package:sport/widgets/text_input.dart' as TextInput; import 'package:umeng_common_sdk/umeng_common_sdk.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:video_player/video_player.dart'; import 'package:visibility_detector/visibility_detector.dart'; Future startGame(BuildContext context, GameInfoData? game, {String? invite = "", UserInfo? user, bool launch4GameCenter = false, bool skipGuide = false}) async { if (game == null) return false; Bluetooth bluetooth = GetIt.I(); if (!bluetooth.isConnected) { ToastUtil.showBottom("请先连接智能鞋"); return false; } if (bluetooth.electricityNotifier.value < 10) { ToastUtil.showBottom("鞋子电量不足,请及时充电!"); } var gameType = game.gameType ?? 0; if (gameType == 0) { ToastUtil.showBottom("无效游戏类型 ${game.name}"); return false; } var packageInfo = await PackageInfo.fromPlatform(); var versionName = packageInfo.version; var v = versionCompare(versionName, game.c_ver ?? ""); if (v < 0) { ToastUtil.showBottom("趣动App不支持该游戏 ${game.name},请升级至最新版本"); return false; } String? mac = bluetooth.deviceId; SharedPreferences prefs = await SharedPreferences.getInstance(); var userModel = Provider.of(context, listen: false); var userInfo = userModel.user; if (skipGuide != true) { // String baseKey = "guide_game_0"; bool guide = prefs.getBool(baseKey) ?? true; // if (guide == true) { // var result = await NavigatorUtil.goPage( // context, // (context) => GameGuide( // game: game, // base: true, // launch4GameCenter: launch4GameCenter, // )); // if (result is List && result[0] == true) { // prefs.setBool(baseKey, !result[1]); // } else { // return; // } // } // // // // prefs.getKeys().forEach((element) { // // if(element.startsWith("guide")){ // // prefs.setBool(element, true); // // } // // }); // if (guideActionMap.containsKey(game.id)) { String key = "guide_game_${game.id}"; // guide = prefs.getBool(key) ?? true; guide = userInfo.setting[key] ?? true; // guide = true; if (guide == true) { var result = await NavigatorUtil.goPageRemoveUntil( context, (context) => GameGuide( game: game, launch4GameCenter: launch4GameCenter, ), settings: RouteSettings(name: "game_guide"), ); if (result is List && result[0] == true) { // await prefs.setBool(key, !result[1]); userInfo.setting[key] = !result[1]; LoginInfoModel loginInfoModel = GetIt.I(); loginInfoModel.loginApi.clientConfig(json.encode(userInfo.setting)); loginInfoModel.saveUser(context, userInfo); await request(context, () async { await Future.delayed(Duration(seconds: 2)); }); if (!launch4GameCenter && (game.h5 ?? 0) == 0) { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } } else { return false; } } } } var userMap = userInfo.toJsonSimple(); String token = prefs.getString("token") ?? ""; var params = { 'token': token, 'mac': "$mac", 'game_id': "${game.id}", 'game_type': "$gameType", 'user_id': "${userInfo.id}", 'user': userMap, 'vibrate': prefs.containsKey("vibrate") ? prefs.getBool("vibrate") : true, if (invite?.isNotEmpty == true) 'invite': {"info": "$invite", "user": user?.toJsonSimple() ?? {}, "game_id": "${game.id}", "ts": DateTime.now().millisecondsSinceEpoch}, 'debug_game_mode': isDebugShoe ? prefs.containsKey("debug_game") ? prefs.getBool("debug_game") : true : false, 'debug': isDebugShoe, }; print("start game $gameType params: $params"); GetIt.I().addGame(game); if ((game.h5 ?? 0) > 0 && game.downloadUrl != null) { await NavigatorUtil.goPageRemoveUntil(context, (context) => GamePage(game: game, gameCenter: launch4GameCenter), settings: RouteSettings(name: "game")); return true; } if (Platform.isAndroid) { if (game.packageNameAndroid == null) { return false; } var sport = await app.DeviceApps.getApp(game.packageNameAndroid!); if (sport == null) { ToastUtil.showBottom("你还没安装 ${game.name} ${game.packageNameAndroid}"); return false; } Broadcast.broadcast("xie.hiyd.com.GameService"); ToastUtil.showBottom("正在打开... ${game.name} ${game.packageNameAndroid}"); // bluetooth.disconnectDevice("游戏开始,主动断开"); AndroidIntent intent = AndroidIntent(action: "android.intent.action.MAIN", package: game.packageNameAndroid, componentName: "com.ouj.shoe.sdklibrary.GameActivity", data: "oujgame://com.ouj.shoe", flags: [Flag.FLAG_ACTIVITY_NEW_TASK], arguments: params); await intent.launch(); return true; // app.DeviceApps.openApp(_data.packageNameAndroid) } else if (Platform.isIOS) { var url = "${game.packageNameIos}://${Uri.encodeComponent(json.encode(params))}"; // // launch(url); bluetooth.disconnectDevice("游戏开始,主动断开"); bool _canLaunch = await canLaunch(url); if (_canLaunch) { await launch(url); } else { if (game.appstore != null) { await launch(game.appstore!); } } // ToastUtil.show("正在打开... ${game.name} ${game.packageNameIos}"); return true; } return true; } Future isInstalled(GameInfoData game) async { if ((game.h5 ?? 0) > 0) { return 0; } if (Platform.isAndroid) { if (game.packageNameAndroid?.isNotEmpty == true && (await app.DeviceApps.isAppInstalled(game.packageNameAndroid!))) return 0; } else if (Platform.isIOS) { var url = "${game.packageNameIos}://${Uri.encodeComponent(json.encode("{}"))}"; bool _canLaunch = await canLaunch(url); if (_canLaunch) { return 0; } } return 1; } final Map GAME_RULE = {1: "qdtw", 33: "tgds", 22: "jdstb", 3: "qdpk", 10: "gfxy", 9: "tydb"}; class GameDetailsPage extends StatefulWidget { final GameInfoData details; GameDetailsPage(this.details); @override State createState() { return _GameDetailPageState(); } } class _GameDetailPageState extends State with TickerProviderStateMixin, InjectApi, InjectLoginApi { FocusNode _comment = FocusNode(); bool isChangeFullScreen = false; late CommentListModel _commentListModel; ValueNotifier _swiperIndex = ValueNotifier(0); bool defaultLike = false; bool isLike = false; late int id; final GlobalKey _videoKey = GlobalKey(); @override void initState() { super.initState(); id = widget.details.id; defaultLike = isLike = Application.gameLikes[id] ?? widget.details.isLike ?? false; _commentListModel = new CommentListModel('${widget.details.subjectId}', by: 'created_at'); } @override void dispose() { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((value) => SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top])); super.dispose(); _commentListModel.dispose(); _swiperIndex.dispose(); } Future _checkInstall() async { if (widget.details.packageNameAndroid?.isNotEmpty == true) return await app.DeviceApps.isAppInstalled(widget.details.packageNameAndroid ?? ""); return false; } fullScreen(bool change) { setState(() { isChangeFullScreen = change; }); } @override Widget build(BuildContext context) { final mediaQuery = MediaQuery.of(context); final double statusBarHeight = mediaQuery.padding.top; final double pinnedHeaderHeight = statusBarHeight + kToolbarHeight; GameInfoData game = widget.details; if (isChangeFullScreen == true) { return WillPopScope( onWillPop: () async { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); return false; }, child: Scaffold(backgroundColor: Colors.black, body: Center(child: AspectRatio(aspectRatio: 1.78, child: _VideoWidget(key: _videoKey, isFullScreen: true, data: game)))), ); } int imagesLength = widget.details.introduceImages?.length ?? 0; int swiperLength = imagesLength; if (game.introduceVideo?.isNotEmpty == true) { swiperLength += 1; } return Scaffold( backgroundColor: Colors.white, appBar: buildAppBar( context, title: "运动详情", actions: [ FutureBuilder( future: _checkInstall(), builder: (BuildContext context, AsyncSnapshot snapshot) { var list = [ if (GAME_RULE.containsKey(widget.details.id)) menuItem('游戏规则', 'linkpop_icon_record.png', '游戏规则'), menuItem('运动记录', 'linkpop_icon_motion.png', '运动记录'), // menuItem('操作指引', 'linkpop_icon_motion.png', '操作指引'), menuItem('问题反馈', 'linkpop_icon_feedback.png', '问题反馈'), ]; if (snapshot.data == true && Platform.isAndroid && widget.details.h5 == 0) { list.add(menuItem('删除运动', 'linkpop_icon_del.png', '删除运动')); } return PopupMenuTheme( data: PopupMenuThemeData(shape: PopmenuShape(borderRadius: BorderRadius.all(Radius.circular(10.0)))), child: PopupMenuButton( offset: Offset(0, kToolbarHeight / 2 + 15), icon: iconMoreGray(), onSelected: (val) { _onPopMenuSelected(val); }, itemBuilder: (context) { return divideMenus(list); }, )); }), ], ), body: ProviderWidget( model: _commentListModel, onModelReady: (model) => model.initData(), autoDispose: false, builder: (_, model, __) { return CustomScrollView( slivers: [ SliverToBoxAdapter( child: Container( child: Column( children: [ if (swiperLength > 0) AspectRatio( aspectRatio: 1.8, child: Stack( fit: StackFit.expand, children: [ Swiper( itemCount: swiperLength, itemBuilder: (BuildContext context, int index) { var padding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0); if (imagesLength != swiperLength && index == 0) { return Padding( padding: padding, child: AspectRatio(aspectRatio: 1.78, child: ClipRRect(borderRadius: BorderRadius.circular(10.0), child: _VideoWidget(key: _videoKey, isFullScreen: false, data: game))), ); } else { int _i = index - (imagesLength != swiperLength ? 1 : 0); List _images = widget.details.introduceImages ?? []; if (_images.isEmpty) return Container(); return InkWell( onTap: () { Navigator.push( context, FadeRoute( page: GalleryPhotoViewWrapper( galleryItems: _images.map((e) => photo.Image(id: "${_images.indexOf(e)}", src: e)).toList(), backgroundDecoration: const BoxDecoration( color: Colors.black, ), initialIndex: _i, scrollDirection: Axis.horizontal, ), ), ); }, child: Padding( padding: padding, child: ClipRRect( borderRadius: BorderRadius.circular(10.0), child: Container( color: Colors.black, child: CachedNetworkImage( imageUrl: _images[_i], fit: BoxFit.cover, ), ), ), ), ); } }, loop: false, onIndexChanged: (i) => _swiperIndex.value = i, ), Positioned( bottom: 20.0, right: 28.0, child: Container( padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0), decoration: BoxDecoration(borderRadius: BorderRadius.circular(100), color: Colors.black.withOpacity(.5)), child: Row( mainAxisSize: MainAxisSize.min, children: [ ValueListenableBuilder( valueListenable: _swiperIndex, builder: (context, snapshot, _) { return Text( "${snapshot + 1}", style: Theme.of(context).textTheme.subtitle2!.copyWith(color: Colors.white), ); }), Text( "/$swiperLength", style: Theme.of(context).textTheme.subtitle2!.copyWith(color: Colors.white), ), ], ), ), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( margin: EdgeInsets.only(right: 10.0), decoration: ShapeDecoration(image: DecorationImage(image: CachedNetworkImageProvider(widget.details.cover ?? ''), fit: BoxFit.cover), shape: RoundedRectangleBorder(borderRadius: BorderRadiusDirectional.circular(6))), width: 60.0, height: 60.0, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${widget.details.name}", style: Theme.of(context).textTheme.headline1!, ), const SizedBox( height: 12, ), if (game.tags != null) Wrap( spacing: 4, runSpacing: 4, children: game.tags!.map((e) => gameTag(context, e)).toList(), ), ], ), ), // InkWell( // child: Row( // children: [ // Image.asset( // isLike ? "lib/assets/img/bbs_icon_like_complete.png" : "lib/assets/img/bbs_icon_like.png", // width: 26, // height: 38, // ), // Padding( // padding: EdgeInsets.symmetric(horizontal: 5.0), // child: Text( // "${max(0, (widget.details.likeCount ?? 0) + (isLike == true ? defaultLike ? 0 : 1 : defaultLike ? -1 : 0))}", // style: Theme.of(context).textTheme.subtitle2, // ), // ) // ], // ), // onTap: throttle(() async { // var data = await request(context, () async { // RespData data; // if (!isLike) { // data = await api.postForumLike('$id', 'game_id'); // } else { // data = await api.postForumUnLike('$id', 'game_id'); // } // return data; // }); // if (data?.code == 0) { // setState(() { // isLike = !isLike; // Application.gameLikes[id] = isLike; // }); // } // }), // ) FutureProvider( initialData: null, create: (context) async { Rank? _rank = game.rank; if (_rank == null) { RespList resp = await api.getGameAll(); _rank = resp.results.firstWhere((element) => element.id == id).rank; } return _rank; }, builder: (context, child) { return InkWell( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Image.asset("lib/assets/img/rank_icon_game.png"), ), Text( "排行榜", style: Theme.of(context).textTheme.subtitle1, ), ], ), ), onTap: () => NavigatorUtil.goPage(context, (context) => RankDetailPage(id, 1)), ); }, ), ], ), Container( margin: EdgeInsets.only(top: 24.0, bottom: 10.0), child: StartButtonWidget(game, (start) async { startGame(context, widget.details); UmengCommonSdk.onEvent("game_start_detail", {'id': '${game.id}'}); })), Padding( padding: const EdgeInsets.fromLTRB(0, 24, 0, 12), child: Text("互动规则", style: Theme.of(context).textTheme.headline3), ), Html( shrinkWrap: true, data: "${game.guide}", style: {"strong": Style(fontWeight: FontWeight.normal), "li": Style(margin: const EdgeInsets.symmetric(vertical: 8), padding: EdgeInsets.zero)}, ), if (guideActionMap.containsKey(game.id)) InkWell( onTap: () { NavigatorUtil.goPage( context, (context) => GameGuide( game: game, detailGuide: true, )); }, child: Container( decoration: BoxDecoration(color: Theme.of(context).scaffoldBackgroundColor, borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 14.0), margin: const EdgeInsets.symmetric(vertical: 12.0), child: Center( child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("互动教程", style: Theme.of(context).textTheme.subtitle1), Padding( padding: const EdgeInsets.only(left: 6.0), child: arrowRight(), ), ], ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(0, 24, 0, 12), child: Text("运动介绍", style: Theme.of(context).textTheme.headline3), ), Text( "${game.introduce}", style: Theme.of(context).textTheme.bodyText2!.copyWith(height: 1.8), ), ], ), ), ], ), ), ), if (openSocial()) SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 24, 16, 12), child: Text("游戏评论", style: Theme.of(context).textTheme.headline3), ), Container( margin: const EdgeInsets.fromLTRB(16, 12, 16, 24), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14.0), decoration: card(), child: InkWell( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ Consumer( builder: (_, model, __) { return Row( children: [ CircleAvatar(backgroundColor: Colors.black26, radius: 15, backgroundImage: CachedNetworkImageProvider(model.user.avatar ?? "")), SizedBox( width: 12, ), Text(model.user.name, style: Theme.of(context).textTheme.subtitle1!.copyWith(fontWeight: FontWeight.w600)) ], ); }, ), ], ), Row( children: [ Padding( padding: EdgeInsets.only(right: 6.0), child: Text( "发表评论", style: TextStyle(color: Color.fromRGBO(51, 51, 51, 1)), ), ), arrowRight(), ], ) ], ), onTap: () async { if (await showBindPhoneDialog(context) != true) { return; } // 从下面弹出个框来 输入 评论 showModalBottomSheet( context: context, isScrollControlled: true, // !important builder: (BuildContext context) { return SingleChildScrollView( child: Container( padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), // !important child: TextInput.TextInput( '${widget.details.subjectId}', focusNode: _comment, autoFocus: true, callback: () { model.refresh(); Navigator.pop(context); }, )), ); }, ); }, ), ), ], ), ), if (openSocial() && model.isBusy) SliverToBoxAdapter( child: RequestLoadingWidget(), ), if (openSocial() && model.isIdle && model.list.isNotEmpty) SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 8), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { return PostCommentWidget(model.list[index]); }, childCount: model.list.length)), ), if (openSocial() && model.isEmpty) SliverToBoxAdapter( child: Container( child: Center( child: RequestErrorWidget( null, msg: "暂无评论~", assets: RequestErrorWidget.ASSETS_NO_COMMENT, ), ), ), ), if (model.isIdle && model.list.isNotEmpty) SliverToBoxAdapter( child: Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Text("没有更多数据了"), )), ), ], ); }, ), ); } _onPopMenuSelected(var val) async { switch (val) { case '运动记录': NavigatorUtil.goGameHistory(context, widget.details); break; case '游戏规则': NavigatorUtil.goPage(context, (context) => WebPage(url: "https://xie-web.funfet.com/index.html#/pages/Rules/index?name=${GAME_RULE[widget.details.id]}")); break; // case '操作指引': // NavigatorUtil.goPage(context, (context) => WebPage(url: "http://xie-web.hiyd.com/index.html#/game_guide?id=${widget.details.id}")); // break; case '问题反馈': FeedTypeInfoData? group; await request(context, () async { FeedTypeInfo _feedTypeInfo = await loginApi.getFeedBackTypes(); if (_feedTypeInfo.code == 0) { group = _feedTypeInfo.data?.singleWhere((element) => element.groupId == '3'); } }); if (group != null) { NavigatorUtil.goPage(context, (context) => FeedbackDetailPage(group!)); } break; case '删除运动': if (Platform.isAndroid) { var installed = await app.DeviceApps.isAppInstalled(widget.details.packageNameAndroid ?? ""); if (installed == true) { if (await showDialog( context: context, builder: (context) => CustomAlertDialog(title: '是否删除运动', ok: () => Navigator.of(context).pop(true)), ) == true) { GameManager.deleteFile(widget.details); await app.DeviceApps.uninstallApp(widget.details.packageNameAndroid ?? ""); setState(() {}); } } else {} } break; } } } // bottom Button class StartButtonWidget extends StatefulWidget { final GameInfoData data; final Function startCallBack; final double width, height; final TextStyle textStyle; StartButtonWidget(this.data, this.startCallBack, {this.width = double.infinity, this.height = 44, this.textStyle = const TextStyle( color: Colors.white, fontSize: 14.0, fontWeight: FontWeight.w600, height: 1.1, )}); @override State createState() { return _StartButtonWidgetState(); } } class _StartButtonWidgetState extends LifecycleState { DownStatus _downStatus = DownStatus.unknown; double _percent = 0.0; late StreamSubscription _subscription; @override void initState() { super.initState(); _subscription = DownLoadManage().stream.listen((event) { if (event.tag != widget.data.downloadUrl!) return; if (event.done == true) { _downStatus = DownStatus.install; ToastUtil.showBottom("下载成功,请按步骤继续安装"); _installApk(); } else if (event.failed == true) { if (event.error is DioError && event.error?.type == DioErrorType.cancel) { _downStatus = DownStatus.pause; } else { ToastUtil.showBottom("下载失败,请重试 ${event.error.toString()}"); _downStatus = DownStatus.unsupported; } } else { if (event.total != -1) { _percent = (event.count / event.total * 100); } } if (mounted) setState(() {}); }); _isReady().then((value) => setState(() {})); } @override void dispose() { if (DownLoadManage().isDowning(widget.data.downloadUrl!)) { ToastUtil.showBottom("后台下载中..."); } super.dispose(); _subscription.cancel(); // stopDownload(); } @override Future didChangeAppLifecycleState(AppLifecycleState state) async { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { _isReady().then((value) { if (mounted) setState(() {}); }); } } Future _isReady() async { _downStatus = DownStatus.unsupported; GameInfoData game = widget.data; if ((game.h5 ?? 0) > 0) { _downStatus = DownStatus.ready; return; } if (Platform.isAndroid && widget.data.packageNameAndroid?.isNotEmpty == true) { if (DownLoadManage().isDowning(widget.data.downloadUrl!)) { _downStatus = DownStatus.down; } else { var sport = await app.DeviceApps.getApp(widget.data.packageNameAndroid ?? ""); if (sport != null) { var v = versionCompare(sport.versionName ?? "", widget.data.version ?? ""); // print("111111111 $sport $v"); if (v >= 0) { _downStatus = DownStatus.ready; } else { _downStatus = DownStatus.update; } } else { int length = await _checkDownload(); // print("11111111111111 length: $length"); if (length > 0) { _downStatus = DownStatus.pause; // print("11111111111111 length: $length, total: ${game.fileSizeByte} $_percent"); _percent = (length / (game.fileSizeByte ?? 0)) * 100; if (_percent >= 100) { _downStatus = DownStatus.install; } } } } } else if (Platform.isIOS) { var url = "${game.packageNameIos}://${Uri.encodeComponent(json.encode("{}"))}"; bool _canLaunch = await canLaunch(url); if (_canLaunch) { _downStatus = DownStatus.ready; } } print("Game -- $_downStatus"); } _installApk() async { if (Platform.isAndroid) { File? savePath = await GameManager.createFile(widget.data); if (savePath == null) { ToastUtil.showBottom("创建文件失败"); return; } try { String path = savePath.path; AppInstaller.installApk(path).then((value) async { // var sport = await app.DeviceApps.getApp(widget.data.packageNameAndroid ?? ""); // if (sport != null) { // GameManager.deleteFile(widget.data); // } }); print('install apk $path'); } catch (e) { print(e); print('install apk error: $e'); } } } Future _checkDownload() async { File? savePath = await GameManager.createFile(widget.data); if (savePath == null || !await savePath.exists()) { return 0; } int total = 0; try { final List children = savePath.parent.listSync(); for (final FileSystemEntity file in children) { if (file is File) { int length = await file.length(); total += length; } } } catch (e) { // print(e); } return total; } Future _startDownload() async { Bluetooth bluetooth = GetIt.I(); if (bluetooth.isNotReady) { ToastUtil.showBottom("请先连接智能鞋"); return false; } bool storage = await Application.requestPermission(Permission.storage); if (!storage) { ToastUtil.showBottom("没有下载文件权限"); return false; } File? savePath = await GameManager.createFile(widget.data); if (savePath == null) { ToastUtil.showBottom("创建文件失败"); return false; } if (_percent == 0) { await GameManager.deleteFile(widget.data); if (connectivityResult == ConnectivityResult.none) { ToastUtil.showBottom("请打开手机网络"); return false; } var ok = await showDialog( context: context, builder: (context) => CustomAlertDialog( title: '下载${widget.data.name}到本地', child: Padding( padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 8.0), child: Align( alignment: Alignment.centerLeft, child: Text( "当前使用${connectivityResult == ConnectivityResult.wifi ? 'WIFI' : '移动'}网络\n下载运动需要${widget.data.fileSize}M", style: Theme.of(context).textTheme.subtitle1?.copyWith(height: 1.8), ))), ok: () => Navigator.of(context).pop(true)), ); if (ok != true) { return false; } } print("Game -- down: ${widget.data.downloadUrl}"); DownLoadManage().download(widget.data.downloadUrl, savePath.path); return true; } stopDownload() { DownLoadManage().stop(widget.data.downloadUrl ?? ""); } Widget _buildGameDownloadProgress() { return Container( height: widget.height, child: Stack( children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20.0)), border: new Border.all(width: 1, color: Theme.of(context).colorScheme.secondary), ), child: ClipRRect( borderRadius: BorderRadius.circular(22.0), child: SizedBox( height: widget.height, child: LinearProgressIndicator( // backgroundColor: Color.fromRGBO(220, 220, 220, 1), backgroundColor: Colors.white, valueColor: AlwaysStoppedAnimation(Theme.of(context).colorScheme.secondary.withOpacity(0.3)), value: _percent / 100, ), ), ), ), Container( alignment: Alignment.center, child: Text( _percent >= 100.0 ? "正在校验" : "${_percent.toStringAsFixed(1)}%", style: widget.textStyle.copyWith(color: Theme.of(context).colorScheme.secondary), ), ) ], ), ); } _click() async { switch (_downStatus) { case DownStatus.ready: widget.startCallBack(1); break; case DownStatus.unknown: break; case DownStatus.update: case DownStatus.unsupported: case DownStatus.pause: if (Platform.isIOS) { launch(widget.data.appstore ?? ""); } else if (Platform.isAndroid) { if (await _startDownload()) { _downStatus = DownStatus.down; } } break; case DownStatus.unauthorized: // TODO: Handle this case. break; case DownStatus.down: stopDownload(); _downStatus = DownStatus.pause; break; case DownStatus.install: await _installApk(); break; } setState(() {}); } @override Widget build(BuildContext context) { int viewIndex = _downStatus.index; double height = widget.height; return GestureDetector( onTap: _click, child: IndexedStack(index: viewIndex, alignment: Alignment.center, children: [ CircularProgressIndicator(), _ButtonStyle( child: Text( "开始运动", style: widget.textStyle, ), height: height), _ButtonStyle( child: Platform.isAndroid ? Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/btn_icon_download.png", width: 16, ), SizedBox( width: 5, ), Text( "开始下载", style: widget.textStyle, ), // SizedBox( // width: 30, // ), ], ) : Text( "去下载", style: widget.textStyle, ), height: height), _ButtonStyle( child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/btn_icon_download.png", width: 16, ), SizedBox( width: 5, ), Text( "开始更新", style: widget.textStyle, ), // SizedBox( // width: 30, // ), ], ), height: height), _buildGameDownloadProgress(), _ButtonStyle( child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/btn_icon_download.png", width: 16, ), SizedBox( width: 5, ), Text( "继续下载", style: widget.textStyle, ), // SizedBox( // width: 30, // ), ], ), height: height), _ButtonStyle( child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( "lib/assets/img/btn_icon_download.png", width: 16, ), SizedBox( width: 5, ), Text( "安装运动", style: widget.textStyle, ), // SizedBox( // width: 30, // ), ], ), height: height), Container(), ])); } } enum DownStatus { unknown, ready, unsupported, update, down, pause, install, unauthorized, } class _ButtonStyle extends StatelessWidget { final Widget child; final double height; const _ButtonStyle({Key? key, required this.child, required this.height}) : super(key: key); @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, height: this.height, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50.0)), // border: new Border.all( // width: 1, color: Theme.of(context).accentColor), // color: Theme.of(context).accentColor, boxShadow: [BoxShadow(offset: Offset(0.0, 3), blurRadius: 3, spreadRadius: 0, color: Color(0x82FF9100))], gradient: new LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color.fromRGBO(255, 196, 0, 1), Color.fromRGBO(255, 170, 0, 1), ]), ), // "开始下载(${widget.data.fileSize}M)" child: child); } } class _VideoWidget extends StatefulWidget { final bool isFullScreen; final GameInfoData data; _VideoWidget({Key? key, required this.isFullScreen, required this.data}) : super(key: key); @override State createState() { return __VideoWidgetState(); } } class __VideoWidgetState extends State<_VideoWidget> { Timer? timer; var opacity = 1.0; late VideoPlayerController controller; // 播放的controller @override void initState() { super.initState(); controller = VideoPlayerController.network(widget.data.introduceVideo ?? ""); controller.setLooping(true); if (controller.value.isPlaying == true) { overlay(); } } @override void dispose() { super.dispose(); timer?.cancel(); controller.dispose(); } void overlay() { if (timer != null) { timer!.cancel(); } setState(() { opacity = 1; }); timer = Timer(new Duration(seconds: 3), () { setState(() { if (mounted) { if (controller.value.isPlaying == true) opacity = 0; } }); }); } void _handleVisibilityChanged(VisibilityInfo info) { if (!mounted) return; if (controller.value.isInitialized != true) return; if (info.visibleFraction == 0) { controller.pause(); setState(() { opacity = 1; }); } else { // if (controller.value.position.inSeconds > 0) controller.play(); } } @override Widget build(BuildContext context) { return Container( width: double.infinity, height: double.infinity, child: VisibilityDetector( key: ValueKey(widget.data.introduceVideo), onVisibilityChanged: _handleVisibilityChanged, child: Stack( fit: StackFit.expand, children: [ VideoPlayer(controller), if (!widget.isFullScreen) Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration(color: const Color(0xFF040404).withOpacity(.05)), ), ValueListenableBuilder( valueListenable: controller, builder: (context, v, __) { if (v.isInitialized) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (v.isPlaying) { if (opacity >= 1.0) { SystemChrome.setPreferredOrientations(Platform.isIOS ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]).then((value) => SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [])).then((value) { context.findAncestorStateOfType<_GameDetailPageState>()?.fullScreen(true); }); } else { overlay(); } } }, child: AnimatedOpacity( duration: new Duration(seconds: 1), opacity: opacity, child: Container( width: double.infinity, height: double.infinity, child: Stack( fit: StackFit.expand, children: [ Center( child: InkWell( child: Image.asset( v.isPlaying ? "lib/assets/img/game_icon_suspend.png" : "lib/assets/img/game_icon_play.png", height: 50.0, ), onTap: () { if(controller.value.isInitialized) { if (v.isPlaying) { controller.pause(); } else { if (controller.value.position.compareTo(controller.value.duration) == 0) { controller.seekTo(Duration.zero); } controller.play(); } }else{ controller.initialize().then((value) => controller.play()); } overlay(); }, ), ), if (controller.value.position.inSeconds > 0) Positioned( left: 12, right: widget.isFullScreen ? 12 : 60, bottom: 16, child: Row( // mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 5.0), child: Text(DateFormat.toVideoTime(controller.value.position.inSeconds), style: Theme.of(context).textTheme.subtitle1?.copyWith(fontSize: 10.0)), ), Expanded( child: SizedBox( height: 3, child: VideoProgressIndicator(controller, allowScrubbing: true, // 是否可以拖动 padding: const EdgeInsets.all(0), colors: VideoProgressColors(playedColor: Theme.of(context).colorScheme.secondary))), ), Padding( padding: EdgeInsets.symmetric(horizontal: 5.0), child: Text(DateFormat.toVideoTime(controller.value.duration.inSeconds), style: Theme.of(context).textTheme.subtitle1?.copyWith(fontSize: 10.0)), ), ], )), ], ), ), ), ); }else if(v.isBuffering) { return Center(child: CircularProgressIndicator()); } else { return Container( width: double.infinity, height: double.infinity, child: Stack( fit: StackFit.expand, children: [ CachedNetworkImage(imageUrl: '${widget.data.introduceVideoCover}', fit: BoxFit.fitWidth,), Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration(color: const Color(0xFF040404).withOpacity(.05)), ), Center( child: InkWell( child: Image.asset( "lib/assets/img/game_icon_play.png", height: 50.0, ), onTap: () { controller.initialize().then((value) => controller.play()); overlay(); }, ), ), ], ), ); } }), if (widget.isFullScreen) Positioned( right: 12, top: 12, child: InkWell( child: Container( padding: const EdgeInsets.all(10), decoration: const ShapeDecoration(shape: CircleBorder(), color: Colors.black45), child: Center( child: Image.asset( "lib/assets/img/btn_close_white.png", height: 22, width: 22, ), ), ), onTap: () { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((value) => SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top])).then((value) { context.findAncestorStateOfType<_GameDetailPageState>()?.fullScreen(false); }); })), ], ), )); } }