ExerciseInfoRecordingPage.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_habit/common/I18N.dart';
  3. import 'package:flutter_habit/common/components/PopMenus.dart';
  4. import 'package:flutter_habit/provider/DataProvider.dart';
  5. import 'package:flutter_habit/provider/UserProvider.dart';
  6. import 'package:flutter_habit/common/utils/ConvertUtils.dart';
  7. import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
  8. import 'package:flutter_habit/database/entity/SportInfo.dart';
  9. import 'package:flutter_habit/database/mapper/ExerciseInfoMapper.dart';
  10. import 'package:flutter_habit/database/mapper/SportInfoMapper.dart';
  11. import 'package:flutter_habit/network/Repository.dart';
  12. import 'package:provider/provider.dart';
  13. class ExerciseInfoRecordingPage extends StatefulWidget {
  14. @override
  15. _ExerciseInfoRecordingPageState createState() =>
  16. _ExerciseInfoRecordingPageState();
  17. }
  18. class _ExerciseInfoRecordingPageState extends State<ExerciseInfoRecordingPage> {
  19. List<SportInfo>? spots;
  20. @override
  21. void initState() {
  22. super.initState();
  23. spots = [];
  24. loadData();
  25. }
  26. Future<void> loadData() async {
  27. spots = await SportInfoMapper()
  28. .selectAll(orderBy: "sportTimes desc,name asc, hkCalorie asc");
  29. setState(() {});
  30. }
  31. Future<void> onSelected(SportInfo sportInfo) async {
  32. TextEditingController intakeController = TextEditingController();
  33. List? res = await PopMenus.baseMenu<List>(
  34. context: context,
  35. title: Text(sportInfo.getName()!),
  36. children: <Widget>[
  37. Divider(),
  38. Text("${I18N.of("运动次数")}: ${sportInfo.getSportTimes()}"),
  39. Text("${I18N.of("消耗热量")}: ${sportInfo.getHkCalorie()} (h/kcal)"),
  40. TextFormField(
  41. keyboardType: TextInputType.number,
  42. controller: intakeController,
  43. decoration: InputDecoration(
  44. icon: Icon(Icons.timer),
  45. labelText: "${I18N.of("运动时长")} (min)",
  46. hintText: I18N.of("请输入运动时长"),
  47. ),
  48. ),
  49. TextButton(
  50. child: Text(I18N.of("确定")),
  51. onPressed: () async {
  52. if (intakeController.text.trim().isNotEmpty &&
  53. double.tryParse(intakeController.text.trim()) != null) {
  54. Navigator.of(context).pop([
  55. sportInfo.getId(),
  56. double.parse(intakeController.text.trim()) / 60,
  57. sportInfo.getSportTimes(),
  58. ]);
  59. } else {
  60. await PopMenus.attention(
  61. context: context, content: Text(I18N.of("输入有误")));
  62. }
  63. },
  64. ),
  65. ],
  66. contentPadding: EdgeInsets.all(16),
  67. );
  68. if (res != null) {
  69. // 记录
  70. // 更新次数
  71. SportInfo sportInfo = SportInfo();
  72. sportInfo.setId(res[0]);
  73. sportInfo.setSportTimes(res[2] + 1);
  74. await SportInfoMapper().updateByFirstKeySelective(sportInfo);
  75. // 记录数据
  76. DateTime now = DateTime.now();
  77. ExerciseInfo exerciseInfo = ExerciseInfo();
  78. exerciseInfo.setSportId(res[0]);
  79. exerciseInfo.setExerciseQuantity(res[1]);
  80. exerciseInfo.setExerciseTime(now.millisecondsSinceEpoch);
  81. // 今日首次运动?
  82. List<ExerciseInfo> localExerciseInfo = (await ExerciseInfoMapper().selectWhere(
  83. "exerciseTime > ${ConvertUtils.dateOfDateTime(now).millisecondsSinceEpoch}"))!;
  84. if (localExerciseInfo.isEmpty){
  85. // 今日首次运动
  86. // 增加金币
  87. UserProvider userProvider =
  88. Provider.of<UserProvider>(context, listen: false);
  89. if (userProvider.token != null) {
  90. int? increasedCoin = await Repository.getInstance()!
  91. .increaseCoin(context, userProvider.uid, userProvider.token);
  92. if (increasedCoin != null) {
  93. await PopMenus.coinAdd(
  94. context: context, addedCoins: increasedCoin);
  95. userProvider.coins += increasedCoin;
  96. userProvider.refresh();
  97. }
  98. }
  99. }
  100. await ExerciseInfoMapper().insert(exerciseInfo);
  101. await Provider.of<DataProvider>(context,listen: false).loadExerciseInfoData();
  102. Navigator.of(context).pop();
  103. }
  104. }
  105. @override
  106. Widget build(BuildContext context) {
  107. return Scaffold(
  108. appBar: AppBar(
  109. title: Text(I18N.of("体育锻炼记录")),
  110. actions: <Widget>[
  111. IconButton(
  112. icon: Icon(Icons.search),
  113. onPressed: () async {
  114. String? name = await showSearch<String?>(
  115. context: context,
  116. delegate: _SportSearchDelegate(spots),
  117. );
  118. await loadData();
  119. List<SportInfo> list =
  120. spots!.where((test) => test.getName() == name).toList();
  121. if (list.isNotEmpty) {
  122. await onSelected(list.last);
  123. }
  124. },
  125. ),
  126. ],
  127. ),
  128. body: Padding(
  129. padding: EdgeInsets.all(16),
  130. child: Column(
  131. children: <Widget>[
  132. ListTile(
  133. leading: Icon(Icons.directions_run),
  134. title: Text(I18N.of("添加运动")),
  135. trailing: Icon(Icons.add),
  136. onTap: () async {
  137. TextEditingController nameController =
  138. TextEditingController();
  139. TextEditingController gkCalorieController =
  140. TextEditingController();
  141. await PopMenus.baseMenu<String>(
  142. context: context,
  143. title: Text(I18N.of("添加运动")),
  144. children: <Widget>[
  145. TextFormField(
  146. controller: nameController,
  147. decoration: InputDecoration(
  148. icon: Icon(Icons.fitness_center),
  149. labelText: I18N.of("运动名称"),
  150. hintText: I18N.of("请输入运动名称"),
  151. ),
  152. ),
  153. TextFormField(
  154. keyboardType: TextInputType.number,
  155. controller: gkCalorieController,
  156. decoration: InputDecoration(
  157. icon: Icon(Icons.whatshot),
  158. labelText: "${I18N.of("消耗热量")} (h/kcal)",
  159. hintText: I18N.of("请输入消耗热量"),
  160. ),
  161. ),
  162. TextButton(
  163. child: Text(I18N.of("确定")),
  164. onPressed: () async {
  165. if (nameController.text.trim().isNotEmpty &&
  166. gkCalorieController.text.trim().isNotEmpty &&
  167. double.tryParse(gkCalorieController.text.trim()) !=
  168. null) {
  169. SportInfo sportInfo = SportInfo();
  170. sportInfo.setName(nameController.text.trim());
  171. sportInfo.setHkCalorie(
  172. double.parse(gkCalorieController.text.trim()));
  173. sportInfo.setSportTimes(0);
  174. bool isSuccess =
  175. await SportInfoMapper().insert(sportInfo);
  176. if (isSuccess) {
  177. await loadData();
  178. Navigator.of(context).pop();
  179. } else {
  180. await PopMenus.attention(
  181. context: context,
  182. content: Text(I18N.of("该运动已存在")));
  183. }
  184. } else {
  185. await PopMenus.attention(
  186. context: context, content: Text(I18N.of("输入有误")));
  187. }
  188. },
  189. ),
  190. ],
  191. contentPadding: EdgeInsets.all(16),
  192. );
  193. },
  194. ),
  195. Divider(),
  196. Text(
  197. I18N.of("长按可删除运动"),
  198. style: Theme.of(context).textTheme.bodySmall,
  199. ),
  200. Expanded(
  201. child: ListView(
  202. children: spots!.map((i) {
  203. return ListTile(
  204. title: Text(i.getName()!),
  205. subtitle: Text(
  206. "${I18N.of("消耗热量")}: ${i.getHkCalorie()} (h/kcal)"),
  207. trailing: Text("${I18N.of("运动次数")}: ${i.getSportTimes()}"),
  208. onTap: () {
  209. onSelected(i);
  210. },
  211. onLongPress: () async {
  212. await PopMenus.sliderConfirm(
  213. context: context,
  214. content: Text(I18N.of("滑动来删除该条数据")),
  215. function: () async {
  216. if (i.getSportTimes() == 0) {
  217. await SportInfoMapper().delete(i);
  218. await loadData();
  219. await PopMenus.attention(
  220. context: context,
  221. content: Text(I18N.of("删除成功")));
  222. } else {
  223. await PopMenus.attention(
  224. context: context,
  225. content: Text(I18N.of("您不能删除记录过的运动")));
  226. }
  227. },
  228. );
  229. },
  230. );
  231. }).toList(),
  232. ),
  233. ),
  234. ],
  235. ),
  236. ),
  237. );
  238. }
  239. }
  240. class _SportSearchDelegate extends SearchDelegate<String?> {
  241. List<SportInfo>? spots;
  242. _SportSearchDelegate(this.spots);
  243. @override
  244. Widget buildLeading(BuildContext context) {
  245. return IconButton(
  246. icon: Icon(Icons.arrow_back),
  247. onPressed: () {
  248. this.close(context, null);
  249. },
  250. );
  251. }
  252. @override
  253. List<Widget> buildActions(BuildContext context) {
  254. return [
  255. IconButton(
  256. icon: Icon(Icons.clear),
  257. onPressed: () {
  258. query = "";
  259. showSuggestions(context);
  260. },
  261. ),
  262. ];
  263. }
  264. @override
  265. Widget buildResults(BuildContext context) {
  266. return Padding(
  267. padding: EdgeInsets.all(16),
  268. child: ListView(
  269. children:
  270. spots!.where((test) => test.getName()!.contains(query)).map((i) {
  271. return ListTile(
  272. title: Text(i.getName()!),
  273. subtitle: Text("${I18N.of("消耗热量")}: ${i.getHkCalorie()} (h/kcal)"),
  274. trailing: Text("${I18N.of("运动次数")}: ${i.getSportTimes()}"),
  275. onTap: () {
  276. this.close(context, i.getName());
  277. },
  278. onLongPress: () async {
  279. await PopMenus.sliderConfirm(
  280. context: context,
  281. content: Text(I18N.of("滑动来删除该条数据")),
  282. function: () async {
  283. if (i.getSportTimes() == 0) {
  284. await SportInfoMapper().delete(i);
  285. await PopMenus.attention(
  286. context: context, content: Text(I18N.of("删除成功")));
  287. this.close(context, null);
  288. } else {
  289. await PopMenus.attention(
  290. context: context,
  291. content: Text(I18N.of("您不能删除记录过的运动")));
  292. }
  293. },
  294. );
  295. },
  296. );
  297. }).toList(),
  298. ),
  299. );
  300. }
  301. @override
  302. Widget buildSuggestions(BuildContext context) {
  303. return buildResults(context);
  304. }
  305. @override
  306. ThemeData appBarTheme(BuildContext context) {
  307. return Theme.of(context);
  308. }
  309. @override
  310. String get searchFieldLabel => I18N.of("搜索");
  311. }