game_run.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import 'dart:io';
  2. import 'package:flutter/material.dart';
  3. import 'package:health_kit_reporter/health_kit_reporter.dart';
  4. import 'package:health_kit_reporter/model/type/quantity_type.dart';
  5. import 'package:health_kit_reporter/model/type/workout_type.dart';
  6. import 'package:permission_handler/permission_handler.dart';
  7. import 'package:provider/provider.dart';
  8. import 'package:shared_preferences/shared_preferences.dart';
  9. import 'package:sport/pages/home/step_realtime_page.dart';
  10. import 'package:sport/pages/run/run_start.dart';
  11. import 'package:sport/pages/run/setting_healthkit_page.dart';
  12. import 'package:sport/pages/run/setting_permission_page.dart';
  13. import 'package:sport/provider/user_model.dart';
  14. import 'package:sport/router/navigator_util.dart';
  15. import 'package:sport/utils/toast.dart';
  16. import 'package:sport/widgets/button_primary.dart';
  17. import 'package:sport/widgets/dialog/alert_dialog.dart';
  18. connectHealthKit() async {
  19. try {
  20. final readTypes = <String>[
  21. QuantityType.stepCount.identifier,
  22. QuantityType.distanceWalkingRunning.identifier,
  23. ];
  24. final writeTypes = <String>[
  25. QuantityType.activeEnergyBurned.identifier,
  26. QuantityType.distanceWalkingRunning.identifier,
  27. WorkoutType.workoutType.identifier,
  28. ];
  29. final isRequested = await HealthKitReporter.requestAuthorization(readTypes, writeTypes);
  30. if (isRequested) {}
  31. } catch (e) {
  32. print(e);
  33. }
  34. }
  35. Future<bool> requestBackground(BuildContext context) async {
  36. var preferences = await SharedPreferences.getInstance();
  37. bool _background = preferences.getBool("RUN_BACKGROUND") ?? false;
  38. if (!_background) {
  39. var result = await showDialog(
  40. context: context,
  41. builder: (context) => CustomAlertDialog(
  42. title: "提示",
  43. child: Padding(
  44. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  45. child: Text(
  46. "由于您当前的操作系统版本会管控后台设置,可能会在运动记录过程中误杀趣动进程,为避免运动记录异常、距离不准确、轨迹点漂移等问题,建议您将趣动后台配置更改为【无限制】,以便我们能为您更准确地记录运动数据",
  47. style: TextStyle(fontSize: 16.0, color: Color(0xff333333), height: 1.8),
  48. ),
  49. ),
  50. textCancel: "稍后",
  51. textOk: "立即设置",
  52. ok: () {
  53. Navigator.pop(context, true);
  54. },
  55. ));
  56. if (result == true) {
  57. preferences.setBool("RUN_BACKGROUND", true);
  58. if (!await jumpBackgroundPermission()) {
  59. await NavigatorUtil.goPage(context, (context) => SettingPermissionPage());
  60. }
  61. _background = true;
  62. }
  63. }
  64. return _background;
  65. // bool checkPermissions = await SystemAlertWindow.checkPermissions;
  66. // if (!checkPermissions) {
  67. // var result = await showDialog(
  68. // context: context,
  69. // builder: (context) => CustomAlertDialog(
  70. // title: "提示",
  71. // child: Padding(
  72. // padding: const EdgeInsets.symmetric(horizontal: 20.0),
  73. // child: Text(
  74. // "由于您当前的操作系统版本会管控后台设置,可能会在运动记录过程中误杀趣动进程,请您开启悬浮窗权限以保证正常运动。",
  75. // style: TextStyle(fontSize: 16.0, color: Color(0xff333333)),
  76. // ),
  77. // ),
  78. // textCancel: "稍后",
  79. // textOk: "立即设置",
  80. // ok: () {
  81. // Navigator.pop(context, true);
  82. // },
  83. // ));
  84. // if (result == true) {
  85. // await SystemAlertWindow.requestPermissions;
  86. // }
  87. // return false;
  88. // }
  89. // return true;
  90. }
  91. Future<bool> runCheck(BuildContext context) async {
  92. PermissionStatus status = await Permission.locationWhenInUse.request();
  93. if (status.isGranted != true) {
  94. ToastUtil.show("请授权位置信息后使用该功能!");
  95. openAppSettings();
  96. return false;
  97. }
  98. var preferences = await SharedPreferences.getInstance();
  99. if (Platform.isAndroid) {
  100. //1111 status PermissionStatus.denied false PermissionStatus.granted false false
  101. // 1111 status false
  102. if ((await Permission.locationAlways.status).isGranted != true) {
  103. if ((preferences.getBool("locationAlways_isPermanentlyDenied") ?? false) != true) {
  104. var result = await showDialog(
  105. context: context,
  106. builder: (context) => CustomAlertDialog(
  107. title: "地理位置权限申请",
  108. child: Padding(
  109. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  110. child: Text(
  111. "运动中的轨迹记录相关功能需要您授权地理位置权限。建议开户【始终允许】地理位置权限,否则锁屏后可能无法记录位置。",
  112. style: TextStyle(fontSize: 16.0, color: Color(0xff333333), height: 1.8),
  113. ),
  114. ),
  115. textOk: "立即设置",
  116. cancelable: false,
  117. ok: () {
  118. Navigator.pop(context, true);
  119. },
  120. ));
  121. if (result) {
  122. PermissionStatus status = await Permission.locationAlways.request();
  123. if (status.isGranted == true) {
  124. } else if (status.isPermanentlyDenied == true) {
  125. preferences.setBool("locationAlways_isPermanentlyDenied", true);
  126. } else {
  127. ToastUtil.show("请授权位置信息【始终允许】后再尝试");
  128. return false;
  129. }
  130. } else {
  131. return false;
  132. }
  133. }
  134. }
  135. await Permission.activityRecognition.request();
  136. await requestBackground(context);
  137. } else if (Platform.isIOS) {
  138. await Permission.sensors.request();
  139. if (!preferences.containsKey("HEALTH_KIT")) {
  140. await NavigatorUtil.goPage(context, (context) => SettingHealthKitPage());
  141. preferences.setBool("HEALTH_KIT", true);
  142. }
  143. }
  144. return true;
  145. }
  146. Future startRun(BuildContext context) async {
  147. bool result = await runCheck(context);
  148. if (result == true) await NavigatorUtil.goPage(context, (context) => RunStartPage());
  149. // await NavigatorUtil.goPage(context, (context) => RunPage());
  150. //
  151. // var package = "com.ouj.hiyd";
  152. // var componentName = "com.ouj.hiyd.SplashActivity_";
  153. //
  154. // if (Platform.isAndroid) {
  155. // var sport = await app.DeviceApps.getApp(package);
  156. // if (sport == null) {
  157. // ToastUtil.show("你还没安装 Hi运动 app");
  158. // return;
  159. // }
  160. // AndroidIntent intent = AndroidIntent(
  161. // action: "android.intent.action.MAIN",
  162. // package: package,
  163. // componentName: componentName,
  164. // flags: [Flag.FLAG_ACTIVITY_NEW_TASK],
  165. // arguments: {});
  166. // intent.launch();
  167. // }
  168. }
  169. class GameRun extends StatelessWidget {
  170. @override
  171. Widget build(BuildContext context) {
  172. return Row(
  173. children: <Widget>[
  174. Container(
  175. width: 50.0,
  176. height: 50.0,
  177. margin: const EdgeInsets.all(12.0),
  178. child: Image.asset("lib/assets/img/home_game_run.png"),
  179. // child: Image.asset("lib/assets/img/game_icon_function.png")
  180. ),
  181. Expanded(
  182. child: Column(
  183. crossAxisAlignment: CrossAxisAlignment.start,
  184. children: <Widget>[
  185. Text(
  186. "跑步训练",
  187. style: Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 16.0, fontWeight: FontWeight.w600),
  188. ),
  189. SizedBox(
  190. height: 3,
  191. ),
  192. ValueListenableBuilder(
  193. valueListenable: Provider.of<UserModel>(context, listen: false).runUserCount,
  194. builder: (BuildContext context, int value, Widget? child) => Text("$value人在练", style: Theme.of(context).textTheme.bodyText1!, maxLines: 1),
  195. ),
  196. SizedBox(
  197. height: 6,
  198. )
  199. ],
  200. ),
  201. ),
  202. Padding(
  203. padding: const EdgeInsets.all(12.0),
  204. child: PrimaryButton(
  205. width: 93,
  206. height: 35,
  207. content: "开始跑步",
  208. callback: () {
  209. startRun(context);
  210. },
  211. ),
  212. ),
  213. // Text(
  214. // "小功能合集",
  215. // style: Theme.of(context)
  216. // .textTheme
  217. // .subtitle1
  218. // .copyWith(fontSize: 16.0, fontWeight: FontWeight.w600),
  219. // ),
  220. // Expanded(
  221. // child: Padding(
  222. // padding: EdgeInsets.only(right: 12.0),
  223. // child: Row(
  224. // mainAxisAlignment: MainAxisAlignment.end,
  225. // children: <Widget>[arrowRight5()],
  226. // ),
  227. // )),
  228. ],
  229. );
  230. }
  231. }
  232. class FunctionSet extends StatelessWidget {
  233. List map = [
  234. {
  235. "name": "户外跑步",
  236. "url": "function_icon_run.png",
  237. "callBack": (context) {
  238. startRun(context);
  239. },
  240. },
  241. {
  242. "name": "实时计步",
  243. "url": "function_icon_steps.png",
  244. "callBack": (context) {
  245. NavigatorUtil.goPage(context, (context) => StepRealTimePage());
  246. },
  247. },
  248. // {
  249. // "name": "体重填写",
  250. // "url": "function_icon_weight.png",
  251. // "callBack": (context) {
  252. // NavigatorUtil.goPage(context, (context) => WeightPage(update: true,));
  253. // },
  254. // }
  255. ];
  256. @override
  257. Widget build(BuildContext context) {
  258. return Expanded(
  259. child: Padding(
  260. padding: EdgeInsets.fromLTRB(15.0, 0, 15.0, 0),
  261. child: GridView(
  262. shrinkWrap: true,
  263. physics: NeverScrollableScrollPhysics(),
  264. padding: EdgeInsets.zero,
  265. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  266. crossAxisCount: 3,
  267. crossAxisSpacing: 12.0,
  268. ),
  269. children: map
  270. .map((e) => GestureDetector(
  271. onTap: () {
  272. e['callBack'](context);
  273. },
  274. child: Container(
  275. decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5.0))),
  276. child: Column(
  277. children: <Widget>[
  278. Padding(
  279. padding: EdgeInsets.fromLTRB(22.0, 12.0, 22.0, 8.0),
  280. child: Image.asset(
  281. "lib/assets/img/${e["url"]}",
  282. width: 56.0,
  283. height: 56.0,
  284. ),
  285. ),
  286. Text(
  287. "${e['name']}",
  288. style: TextStyle(color: Color(0xff000000)),
  289. )
  290. ],
  291. ),
  292. ),
  293. ))
  294. .toList(),
  295. // itemBuilder: (context, index) {
  296. // return Container(
  297. // decoration: BoxDecoration(
  298. // color: Colors.white,
  299. // borderRadius: BorderRadius.all(Radius.circular(5.0))),
  300. // height: 116,
  301. // child: Column(
  302. // children: <Widget>[
  303. // Padding(
  304. // padding: EdgeInsets.fromLTRB(22.0, 12.0, 22.0, 8.0),
  305. // child: Image.asset(
  306. // "lib/assets/img/function_icon_run.png",
  307. // width: 56.0,
  308. // height: 56.0,
  309. // ),
  310. // ),
  311. // Text(
  312. // "户外跑步",
  313. // style: TextStyle(color: Color(0xff000000)),
  314. // )
  315. // ],
  316. // ),
  317. // );
  318. // },
  319. // itemCount: 3,
  320. ),
  321. ),
  322. );
  323. }
  324. }