import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screen_recording/flutter_screen_recording.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sport/pages/run/location.dart'; import 'package:sport/pages/run/map.dart'; import 'package:sport/pages/run/run_page.dart'; import 'package:sport/provider/user_model.dart'; import 'package:sport/utils/DateFormat.dart'; import 'package:sport/utils/click.dart'; import 'package:sport/utils/sport_utils.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'; class MapReplayPage extends StatefulWidget { final double distance; final int duration; final int kcal; final int runMapType; final bool runMapKm; final List points; final Map pointsKm; final int begin; final int showType; const MapReplayPage({Key? key, this.showType = 0, this.distance = 0, this.duration = 0, this.kcal = 0, this.runMapType = 0, this.runMapKm = true, required this.points, required this.pointsKm, this.begin = 0}) : super(key: key); @override State createState() => MapReplayState(); } class MapReplayState extends State { final GlobalKey mapKey = GlobalKey(); ValueNotifier _notifierMapNotification = ValueNotifier(MapNotification(0, 0, 0)); double weight = 60; bool _finish = false; List points = []; String? path; final int second = 4; @override void initState() { super.initState(); SystemChrome.setEnabledSystemUIOverlays([]); _loadWeight(); SchedulerBinding.instance?.addPostFrameCallback((timeStamp) { requestPermissions().then((value) { if (value) { } else { ToastUtil.show("录制失败!"); Navigator.pop(context); } }); }); } _loadWeight() async { var preferences = await SharedPreferences.getInstance(); weight = preferences.getDouble("weight") ?? 0.0; } Future requestPermissions() async { // Map statuses = await [ // Permission.photos, // Permission.storage, // ].request(); // if (statuses.values.any((element) => element.isGranted != true)) { // ToastUtil.show("请授权后使用该功能!"); // Navigator.maybeOf(context); // return; // } // setState(() { this.points = widget.points; }); await Future.delayed(Duration(seconds: 1)); bool start = await FlutterScreenRecording.startRecordScreen("趣动户外运动_${DateTime.now()}"); if (start) { await Future.delayed(Duration(seconds: 2)); mapKey.currentState?.initAnimation(); } return start; } _finishRecord() async { await Future.delayed(const Duration(milliseconds: 2000)); path = await FlutterScreenRecording.stopRecordScreen; setState(() { _finish = true; }); } @override void dispose() { super.dispose(); SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]); FlutterScreenRecording.stopRecordScreen.then((value) { if (value.isNotEmpty == true) { File(value).deleteSync(); } }); if (path != null) { File(path!).deleteSync(); } } @override Widget build(BuildContext context) { var map = points.isNotEmpty == true ? NotificationListener( onNotification: (map) { _notifierMapNotification.value = map; if (map.finish == true) { if (_finish == false) { _finishRecord(); } } return false; }, child: MapWidget( key: mapKey, distance: widget.distance, runMapType: widget.runMapType, runMapKm: widget.runMapKm, points: points, pointsKm: widget.pointsKm, record: true, anim: true, showType: widget.showType, ), ) : Container( color: Colors.black, ); return AnnotatedRegion( value: SystemUiOverlayStyle.light, child: Scaffold( backgroundColor: Colors.black, body: Stack( children: [ map, Container( height: 250.0, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(.62), Colors.black.withOpacity(.0), ], )), child: Column( children: [ Container( height: 70, child: _finish ? Padding( padding: const EdgeInsets.fromLTRB(16.0, 24.0, 16.0, 0), child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () { Navigator.maybePop(context); }, child: Container(width: 40, height: 40, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(50.0)), child: arrowBack()), ), Row( children: [ GestureDetector( onTap: throttle(() async { if (path?.isNotEmpty == true) { var result = await request(context, () async { await Future.delayed(Duration(seconds: 1)); final result = await ImageGallerySaver.saveFile(path!); return result != null && result["isSuccess"] == true; }); if (result == true) { showDialog( context: context, barrierDismissible: true, builder: (BuildContext context) => Dialog( child: Container( padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 25.0), decoration: circular(), child: Column( mainAxisSize: MainAxisSize.min, children: [ Center( child: Text( "视频已保存至相册", style: Theme.of(context).textTheme.headline1, ), ), Padding( padding: const EdgeInsets.only(top: 20.0), child: PrimaryButton( width: 120, callback: () { Navigator.maybePop(context); }, content: "知道了"), ), ], ), ), )); } } }), child: Container( height: 40, padding: EdgeInsets.symmetric(horizontal: 30.0), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(50.0)), child: Row( children: [ Image.asset( "lib/assets/img/topbar_icon_trajectory_save.png", width: 16, height: 16, ), const SizedBox( width: 7, ), Text( "保存(${widget.showType == 1 ? "20秒" : "${widget.distance ~/ 1000 * second + 4}秒"})", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 14), ) ], )), ), const SizedBox( width: 12.0, ), GestureDetector( onTap: throttle(() async { if (path?.isNotEmpty == true) { ToastUtil.show("正在分享,请稍候..."); Share.shareFiles([path!], subject: "我的户外跑轨迹"); } // showModalBottomSheet( // context: context, // backgroundColor: Colors.white, // elevation: 10, // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), // builder: (context) => MenuShareBottomContent( // "Video", // hasDownload: false, // file: path, // app: false, // ), // ); }), child: Container( height: 40, padding: EdgeInsets.symmetric(horizontal: 30.0), decoration: BoxDecoration(color: Color(0xffFFDD00), borderRadius: BorderRadius.circular(50.0)), child: Row( children: [ Image.asset( "lib/assets/img/bbs_icon_share.png", width: 16, height: 16, ), const SizedBox( width: 7, ), Text( "分享", style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 14), ) ], )), ), ], ), ]), ) : Container(), ), Padding( padding: const EdgeInsets.all(30.0), child: Row( children: [ Consumer( builder: (_, model, __) => Row( children: [ Container( margin: const EdgeInsets.fromLTRB(0.0, 0, 12, 0), padding: const EdgeInsets.all(2), decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), child: CircleAvatar( backgroundColor: Colors.black26, backgroundImage: userAvatarProvider(model.user.avatar), radius: 20.0, ), ), Text(model.user.name, style: Theme.of(context).textTheme.headline4!), ], )), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Image.asset( "lib/assets/img/logo_img_white_30.png", width: 30.0, height: 30.0, ), const SizedBox(width: 7), Text("趣动", style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.white)), ], ) ], mainAxisAlignment: MainAxisAlignment.spaceBetween, ), ) ], ), ), IgnorePointer( child: Align( alignment: Alignment.bottomCenter, child: Container( height: 250.0, alignment: Alignment.bottomCenter, padding: const EdgeInsets.all(30.0), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(.0), Colors.black, ], )), child: ValueListenableBuilder( valueListenable: _notifierMapNotification, builder: (context, data, __) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Container( constraints: BoxConstraints(minWidth: 72.0), child: Text( "${formatNum((data.distance) / 1000.0, 2)}", style: Theme.of(context).textTheme.headline4!.copyWith(fontSize: 50.0, fontFamily: "DIN"), strutStyle: fixedLine, ), ), Text( "公里", style: Theme.of(context).textTheme.bodyText2!.copyWith(color: Colors.white), ), Expanded(child: Container()), Text( "${SportUtils.toDateTimeFull(DateTime.fromMillisecondsSinceEpoch(widget.begin + (data.duration * 1000)))}", style: Theme.of(context).textTheme.subtitle2!.copyWith(color: Colors.white), ), ], crossAxisAlignment: CrossAxisAlignment.end, ), const Divider( color: Colors.white, height: 40, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( constraints: BoxConstraints(minWidth: 88.0), child: Row( children: [ Image.asset("lib/assets/img/trajectory_icon_1.png"), const SizedBox( width: 7, ), Text( "${DateFormat.toTime(min(widget.duration, data.duration))}", style: Theme.of(context).textTheme.headline4!.copyWith(fontFamily: "DIN", fontSize: 22.0), ) ], )), Container( constraints: BoxConstraints(minWidth: 88.0), child: Row( children: [ Image.asset("lib/assets/img/trajectory_icon_2.png"), const SizedBox( width: 7, ), Text( "${SportUtils.pace(SportUtils.calPace(min(widget.duration, data.duration), (min(widget.distance, data.distance)) / 1000))}", style: Theme.of(context).textTheme.headline4!.copyWith(fontFamily: "DIN", fontSize: 22.0), ), ], ), ), Row( children: [ Image.asset("lib/assets/img/trajectory_icon_3.png"), const SizedBox( width: 7, ), Text( "${widget.kcal}", style: Theme.of(context).textTheme.headline4!.copyWith(fontFamily: "DIN", fontSize: 22.0), ), Padding( padding: const EdgeInsets.only(top: 4.0), child: Text( "kcal", style: Theme.of(context).textTheme.bodyText1!.copyWith(color: Colors.white), ), ) ], ), ], ), ], ); }), ), ), ), ], ), )); } }