import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:android_intent/android_intent.dart'; import 'package:android_intent/flag.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:device_apps/device_apps.dart' as app; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/material.dart' hide NestedScrollView; import 'package:flutter/services.dart'; import 'package:install_plugin/install_plugin.dart'; import 'package:orientation/orientation.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/game.dart'; import 'package:sport/bean/rank_game_info.dart'; import 'package:sport/provider/bluetooth.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/router/navigator_util.dart'; import 'package:sport/services/Converter.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/click.dart'; import 'package:sport/utils/download.dart'; import 'package:sport/utils/toast.dart'; import 'package:sport/widgets/button_primary.dart'; import 'package:sport/widgets/decoration.dart'; import 'package:sport/widgets/dialog/request_dialog.dart'; import 'package:sport/widgets/image.dart'; import 'package:sport/widgets/misc.dart'; import 'package:sport/widgets/persistent_header.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:video_player/video_player.dart'; import 'detail_bottom.dart'; import 'game_video.dart'; class GameDetailsPage extends StatefulWidget { final GameInfoData details; GameDetailsPage(this.details); @override State createState() { return _GameDetailPageState(); } } class _GameDetailPageState extends State with TickerProviderStateMixin, InjectApi, RouteAware { bool _isFullScreen; // 是否全屏 VideoPlayerController _controller; // 播放的controller bool isLoading = true; // 是否loading Future _initializeVideoPlayerFuture; // 播放的回调 TabController _tabController; // 中间的tab栏 FocusNode _comment = FocusNode(); bool isChange = false; Timer timer; DeviceOrientation _lastEvent; StreamSubscription _streamSubscription; bool _isPlaying; @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)); } @override void didPopNext() { _registerListener(); if(_isPlaying == true){ _controller?.play(); } } @override void didPushNext() { _streamSubscription?.cancel(); _isPlaying = _controller?.value?.isPlaying; _controller?.pause(); } @override void initState() { super.initState(); _isFullScreen = false; // 重置一手 // OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp); if (Platform.isAndroid) { //设置Android头部的导航栏透明 SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent)); } _controller = VideoPlayerController.network(widget.details.introduceVideo); // 初始化第一个? _initializeVideoPlayerFuture = _controller.initialize().then((value) { if (mounted) setState(() { isLoading = false; }); }); _tabController = new TabController(length: 2, vsync: this); _registerListener(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); } // Function throttle({Function f, int delay}) { // var date = DateTime.now().microsecondsSinceEpoch; // print('$date-----------------------------------prepreprepre'); // return () { // var now = DateTime.now().microsecondsSinceEpoch; // print("$now-----------------------------------nownownownow"); // if (now - date >= delay) { // print("执行——————————————————————————————————————————————"); // f(); // } // }(); // } @override void dispose() { routeObserver.unsubscribe(this); _streamSubscription?.cancel(); super.dispose(); _initializeVideoPlayerFuture = null; _controller?.pause(); _controller?.dispose(); _tabController?.dispose(); } void _registerListener() { // 20200927 说不需要了! // _streamSubscription = OrientationPlugin.onOrientationChange.listen( // (event) { // print("$mounted $_lastEvent $event"); // if (!mounted) return; // if (_lastEvent == event) { // return; // } // _lastEvent = event; // // if (event == DeviceOrientation.portraitUp) { // setState(() { // _isFullScreen = false; // SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // }); // } else if (event == DeviceOrientation.landscapeRight) { // List _orientation = Platform.isAndroid ? [DeviceOrientation.landscapeLeft] : [DeviceOrientation.landscapeRight]; // setState(() { // _isFullScreen = true; // SystemChrome.setPreferredOrientations(_orientation); // }); // } else if (event == DeviceOrientation.landscapeLeft) { // List _orientation = Platform.isAndroid ? [DeviceOrientation.landscapeRight] : [DeviceOrientation.landscapeLeft]; // setState(() { // _isFullScreen = true; // SystemChrome.setPreferredOrientations(_orientation); // }); // } // }, // ); } void changeIsFullScreen() { if (_isFullScreen) { setState(() { _isFullScreen = false; SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); }); } else { setState(() { _isFullScreen = true; SystemChrome.setPreferredOrientations(Platform.isAndroid ? [DeviceOrientation.landscapeLeft] : [DeviceOrientation.landscapeRight]); }); } } void autoFocus() { FocusScope.of(context).requestFocus(_comment); } @override Widget build(BuildContext context) { // 全屏隐藏 状态栏 和 底部 虚拟键 GameInfoData _data = widget.details; if (_isFullScreen) { SystemChrome.setEnabledSystemUIOverlays([]); } else { SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]); } final double tabHeader = 50; return AnnotatedRegion( value: SystemUiOverlayStyle.light, child: Material( child: _isFullScreen ? Scaffold( backgroundColor: Colors.black, body: WillPopScope( onWillPop: () async { setState(() { _isFullScreen = false; OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp); }); return false; }, child: Center( child: GameDetailsVideo(changeIsFullScreen, _isFullScreen, _controller, isLoading, _data), ), )) : Scaffold( backgroundColor: Colors.black, body: SafeArea( child: Container( color: Colors.white, child: Column( children: [ Expanded( child: NestedScrollView( pinnedHeaderSliverHeightBuilder: () { return tabHeader; }, innerScrollPositionKeyBuilder: () { TabController tabController = _tabController; String index = 'Tab${tabController.index}'; return Key(index); }, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ // headerVideo SliverToBoxAdapter( child: GameDetailsVideo(changeIsFullScreen, _isFullScreen, _controller, isLoading, _data), ), SliverToBoxAdapter( child: DetailContent( _data.name, _data.introduce, _data.likeCount, _data.cover, widget.details.id, _data.isLike, _data.rank), ), SliverToBoxAdapter( child: Divider(), ), SliverPersistentHeader( delegate: PersistentHeader( min: tabHeader, max: tabHeader, child: Container( color: Colors.white, height: tabHeader, padding: EdgeInsets.symmetric(vertical: 8.0), child: TabBar( controller: _tabController, isScrollable: true, indicatorPadding: EdgeInsets.symmetric(horizontal: 6), indicatorWeight: 3, tabs: [ Tab(text: "详情"), Tab(text: "评论"), ], ), )), pinned: true, ), ]; }, body: TabBarView( controller: _tabController, children: [ NestedScrollViewInnerScrollPositionKeyWidget(const Key('Tab0'), TabDetail(_data.introduceImages, _data.tags, _data.fileSize, _data.publishDate, _data.developCompany)), NestedScrollViewInnerScrollPositionKeyWidget(const Key('Tab1'), Center(child: TabComment(_data.subjectId))), ], ), ), ), Platform.isAndroid ? PositionedBottom(_data, (start) async { // request(context, () async { // await api // .postAddGame( // widget.details.id, // Random().nextDouble() * 100, // (Random().nextDouble() * 40).toInt(), // Random().nextInt(300), // start, // (Random().nextDouble() * 100).toInt(), // (Random().nextDouble() * 100).toInt(), // (Random().nextDouble() * 100).toInt(), // (Random().nextDouble() * 200).toInt()) // .catchError((err) {}); // ToastUtil.show("提交信息成功"); // }); if (Platform.isAndroid) { SharedPreferences prefs = await SharedPreferences.getInstance(); String token = prefs.getString("token"); // AndroidIntent intent = AndroidIntent( // action: "android.intent.action.MAIN", // package: _data.packageNameAndroid, // componentName: "${_data.packageNameAndroid}.sdk.UnityPlayerActivity", // data: "oujgame://${_data.packageNameAndroid}", // flags: [ // Flag.FLAG_ACTIVITY_NEW_TASK // ], // arguments: { // 'token': token, // 'mac': Provider.of(context, listen: false)?.device?.id?.toString() ?? '', // 'game_id': "${_data.id}", // 'user_id': Provider.of(context, listen: false)?.user?.id?.toString() ?? '', // 'user': Provider.of(context, listen: false)?.user?.toJsonSimple() ?? '' // }); AndroidIntent intent = AndroidIntent( action: "android.intent.action.MAIN", package: _data.packageNameAndroid, componentName: "com.ouj.shoe.sdklibrary.GameActivity", data: "oujgame://com.ouj.shoe", flags: [ Flag.FLAG_ACTIVITY_NEW_TASK ], arguments: { 'token': token, 'mac': Provider.of(context, listen: false)?.device?.id?.toString() ?? '', 'game_id': "${_data.id}", 'user_id': Provider.of(context, listen: false)?.user?.id?.toString() ?? '', 'user': json.encode(Provider.of(context, listen: false)?.user?.toJsonSimple() ?? '{}') }); intent.launch(); // app.DeviceApps.openApp(_data.packageNameAndroid) } ToastUtil.show("正在打开... ${_data.name} ${_data.packageNameAndroid}"); }) : Container( width: double.infinity, height: 50.0, padding: EdgeInsets.symmetric(vertical: 5), decoration: shadowTop(), child: InkWell( child: Container( alignment: Alignment.center, margin: EdgeInsets.symmetric(horizontal: 12.0), height: 40.0, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20.0)), border: new Border.all(width: 1, color: Theme.of(context).accentColor), color: Theme.of(context).accentColor, gradient: new LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color.fromRGBO(255, 196, 0, 1), Color.fromRGBO(255, 170, 0, 1), ]), ), child: Text( "go app store", style: TextStyle( color: Colors.white, fontSize: 14.0, fontWeight: FontWeight.w400, ), )), onTap: () { launch("https://itunes.apple.com/cn/app/hi-yun-dong-nin-jian-shen/id1093054311?l=zh&ls=1&mt=8"); }, )) ], )), ), ))); } } class DetailContent extends StatefulWidget { String _title; String _introduce; int _likeCount; String _imgUrl; int id; bool isLike; Rank rank; DetailContent(this._title, this._introduce, this._likeCount, this._imgUrl, this.id, this.isLike, this.rank); createState() => _DetailContentState(); } class _DetailContentState extends State with InjectApi { bool defaultLike; bool isLike; Rank _rank; Future _getRankFuture; initState() { defaultLike = isLike = Application.gameLikes[widget.id] ?? widget.isLike ?? false; _rank = widget.rank; _getRankFuture = _getRank(); super.initState(); } @override void dispose() { super.dispose(); } Future _getRank() async { if (_rank == null) { RespList resp = await api.getGameAll(); _rank = resp.results.firstWhere((element) => element.id == widget.id).rank; } return _rank; } Widget _rankWidget(Rank rank) { return Row( children: [ Container( margin: EdgeInsets.only(right: 10.0), decoration: ShapeDecoration( image: DecorationImage(image: CachedNetworkImageProvider(rank.logo ?? ''), fit: BoxFit.cover), shape: RoundedRectangleBorder(borderRadius: BorderRadiusDirectional.circular(6))), width: 50.0, height: 50.0, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "${rank.name}", style: Theme.of(context).textTheme.headline3, ), Padding( padding: EdgeInsets.only(top: 5.0), child: Text("${rank.introduce}", style: TextStyle(fontSize: 12.0, color: Color.fromRGBO(153, 153, 153, 1))), ), ], ), ), arrowRight7() ], ); } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 12.0, bottom: 0.0), child: Column( children: [ // content header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${widget._title}", style: Theme.of(context).textTheme.headline1, ), 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.only(left: 5.0), child: Text("${max(0, widget._likeCount + (isLike == true ? defaultLike ? 0 : 1 : defaultLike ? -1 : 0))}"), ) ], ), onTap: throttle(() async { var data = await request(context, () async { RespData data; if (!isLike) { data = await api.postForumLike('${widget.id}', 'game_id'); } else { data = await api.postForumUnLike('${widget.id}', 'game_id'); } return data; }); if (data?.code == 0) { setState(() { isLike = !isLike; Application.gameLikes[widget.id] = isLike; }); } }), ) ], ), SizedBox( height: 5, ), Container( alignment: Alignment.centerLeft, child: Text("${widget._introduce}", style: TextStyle(height: 1.5, fontSize: 14.0, color: Color.fromRGBO(153, 153, 153, 1))), ), SizedBox( height: 5, ), InkWell( child: Container( height: 75, padding: EdgeInsets.symmetric(vertical: 10.0), child: widget.rank == null ? FutureBuilder( future: _getRankFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { return _rankWidget(snapshot.data); } return Container(); }, ) : _rankWidget(widget.rank), ), onTap: () { NavigatorUtil.goRankDetails(context, widget.id, 1); }, ), ], ), ); } } // bottom Button class PositionedBottom extends StatefulWidget { final GameInfoData data; final Function startCallBack; PositionedBottom(this.data, this.startCallBack); @override State createState() { return _PositionedBottomState(); } } class _PositionedBottomState extends LifecycleState { int _viewIndex = 3; double _percent = 0.0; bool _nonDownloading = false; // 是否在下载 bool _exits = false; // 是否下载完成 @override void initState() { super.initState(); _isReady(); } @override void dispose() { super.dispose(); stopDownload(); } @override Future didChangeAppLifecycleState(AppLifecycleState state) async { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.resumed) { _isReady(); } } _version(String versionName) { if (versionName?.isEmpty == true) return [0, 0, 0]; var arr = versionName.split("."); if (arr.length < 3) arr.add("0"); return arr.map((e) => Converter.toInt(e)).toList(); } /** * v1: local * v2: net */ _versionCompare(String v1, String v2) { if (v1 == v2) return 0; var clientVersion = _version(v1); var baseVersion = _version(v2); print("$clientVersion $baseVersion"); int client = (clientVersion[0] << 20) | (clientVersion[1] << 10) | clientVersion[2]; int base = (baseVersion[0] << 20) | (baseVersion[1] << 10) | baseVersion[2]; if (client > base) { return 1; } else if (client == base) { return 0; } else { return -1; } } _isReady() async { if (Platform.isAndroid) { 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) { setState(() { _viewIndex = 0; }); return; } } int length = await _checkDownload(); if (length > 0) { print("length: $length, total: ${widget.data.fileSizeByte} $_percent"); _percent = (length / widget.data.fileSizeByte) * 100; if (_percent >= 100) _exits = true; } } setState(() { _viewIndex = 1; }); } void _installApk(String path) async { if (path.isEmpty) { print('make sure the apk file is set'); return; } InstallPlugin.installApk(path, 'xie.hiyd.com').then((result) { print('install apk $result'); }).catchError((error) { print('install apk error: $error'); }); } Future _checkDownload() async { File savePath = await GameManager.createFile(widget.data); if (savePath == null || !await savePath.exists()) { return 0; } return savePath.lengthSync(); } _startDownload() async { bool storage = await Application.requestPermission(Permission.storage); if (!storage) { ToastUtil.show("没有下载文件权限"); return; } File savePath = await GameManager.createFile(widget.data); if (savePath == null) { ToastUtil.show("创建文件失败"); return; } _nonDownloading = true; setState(() { _viewIndex = 2; }); var result = await DownLoadManage().download(widget.data.downloadUrl, savePath.path, onReceiveProgress: (received, total) { if (total != -1) { // print("下载1已接收:" + // received.toString() + // "总共:" + // total.toString() + // "进度:+${(received / total * 100).floor()}%"); if (mounted) setState(() { _viewIndex = 2; if (mounted) _percent = (received / total * 100); }); } }, done: () async { setState(() { _exits = true; _viewIndex = 1; }); // setState(() { // }); print("下载1完成"); if (Platform.isAndroid) { _installApk(savePath.path); } }, failed: (e) { print("下载1失败:" + e.toString()); setState(() { _exits = false; _viewIndex = 1; }); }); _nonDownloading = false; print("下载停止"); return result; } stopDownload() { DownLoadManage().stop(widget.data.downloadUrl); _nonDownloading = false; } Widget _buildGameDownloadProgress() { final double PROGRESSITEM = (MediaQuery.of(context).size.width - 24) / 100; // 分成100份 return Container( margin: EdgeInsets.symmetric(horizontal: 12.0), height: 40.0, child: InkWell( child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(22.0), child: SizedBox( height: 40.0, child: LinearProgressIndicator( backgroundColor: Color.fromRGBO(220, 220, 220, 1), valueColor: AlwaysStoppedAnimation(Theme.of(context).accentColor), value: _percent / 100, ), ), ), Container( alignment: Alignment.center, child: Text( _percent >= 100.0 ? "正在校验安装包" : "正在下载${_percent.toStringAsFixed(1)}%", style: textStyle, strutStyle: fixedLine, ), ) ], ), onTap: () async { if (_percent != 100.0) { if (_nonDownloading) { stopDownload(); setState(() { _viewIndex = 1; }); } } }, ), ); } Widget _buildGameDownload() { return InkWell( child: !_exits && _percent > 0 ? Container( alignment: Alignment.center, margin: EdgeInsets.symmetric(horizontal: 12.0), height: 40.0, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(22.0), child: SizedBox( height: 40.0, child: LinearProgressIndicator( backgroundColor: Color.fromRGBO(220, 220, 220, 1), valueColor: AlwaysStoppedAnimation(Theme.of(context).accentColor), value: _percent / 100, ), ), ), Container( alignment: Alignment.center, child: Text( "继续下载", style: textStyle, strutStyle: fixedLine, ), ) ], )) : Container( alignment: Alignment.center, margin: EdgeInsets.symmetric(horizontal: 12.0), height: 40.0, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(20.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), ]), ), child: Text( _exits ? "安装应用" : _percent > 0 ? "继续下载${_percent.toStringAsFixed(1)}%" : "开始下载(${widget.data.fileSize}M)", style: textStyle, strutStyle: fixedLine, )), onTap: () async { if (!_nonDownloading) { _startDownload(); } }, ); } Widget _buildGameStart() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: PrimaryButton( callback: () => widget.startCallBack(1), content: "开始运动", height: 40, bold: true, ), ); } var textStyle = TextStyle( color: Colors.white, fontSize: 14.0, fontWeight: FontWeight.w600, ); @override Widget build(BuildContext context) { return Container( width: double.infinity, height: 50.0, decoration: shadowTop(), child: IndexedStack( index: _viewIndex, alignment: Alignment.center, children: [_buildGameStart(), _buildGameDownload(), _buildGameDownloadProgress(), CircularProgressIndicator()])); } }