run_target_page.dart 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
  4. import 'package:shared_preferences/shared_preferences.dart';
  5. import 'package:sport/pages/run/run_target_custom_page.dart';
  6. import 'package:sport/pages/run/setting_page.dart';
  7. import 'package:sport/router/navigator_util.dart';
  8. import 'package:sport/services/app_lifecycle_state.dart';
  9. import 'package:sport/widgets/appbar.dart';
  10. import 'package:sport/widgets/button_primary.dart';
  11. import 'package:sport/widgets/dialog/alert_dialog.dart';
  12. import 'package:sport/widgets/dialog/request_dialog.dart';
  13. import 'package:sport/widgets/misc.dart';
  14. class RunTargetPage extends StatefulWidget {
  15. final double runTargetKm;
  16. final double runTargetDuration;
  17. RunTargetPage(this.runTargetKm, this.runTargetDuration);
  18. @override
  19. State<StatefulWidget> createState() => _PageState();
  20. }
  21. class _PageState extends LifecycleState<RunTargetPage> with SingleTickerProviderStateMixin {
  22. late TabController _controller;
  23. @override
  24. void initState() {
  25. super.initState();
  26. _controller = new TabController(length: 2, vsync: this, initialIndex: widget.runTargetDuration > 0 ? 1 : 0)
  27. ..addListener(() {
  28. setState(() {});
  29. });
  30. }
  31. @override
  32. void dispose() {
  33. super.dispose();
  34. _controller.dispose();
  35. }
  36. @override
  37. Widget build(BuildContext context) {
  38. return Scaffold(
  39. backgroundColor: Colors.white,
  40. appBar: buildAppBar(context, title: "设定目标"),
  41. body: Padding(
  42. padding: const EdgeInsets.all(12.0),
  43. child: Column(
  44. children: <Widget>[
  45. Center(
  46. child: TabBar(
  47. controller: _controller,
  48. isScrollable: true,
  49. indicatorPadding: EdgeInsets.symmetric(horizontal: 10.0),
  50. indicatorWeight: 3,
  51. unselectedLabelColor: Color(0xff999999),
  52. labelStyle: TextStyle(fontSize: 16.0),
  53. unselectedLabelStyle: TextStyle(fontSize: 16.0),
  54. labelPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 30.0),
  55. // indicator: const BoxDecoration(),
  56. // labelPadding: EdgeInsets.all(20.0),
  57. tabs: <Widget>[
  58. Tab(
  59. icon: Image.asset("lib/assets/img/setgoals_icon_kilometre_${_controller.index == 0 ? "press" : "normal"}.png"),
  60. text: "公里数",
  61. iconMargin: EdgeInsets.only(bottom: 3.0),
  62. ),
  63. Tab(
  64. icon: Image.asset("lib/assets/img/setgoals_icon_duration_${_controller.index == 1 ? "press" : "normal"}.png"),
  65. text: "运动时长",
  66. iconMargin: EdgeInsets.only(bottom: 3.0),
  67. )
  68. ],
  69. ),
  70. ),
  71. Divider(),
  72. Expanded(
  73. child: Container(
  74. padding: const EdgeInsets.only(top: 30.0),
  75. child: TabBarView(
  76. controller: _controller,
  77. physics: NeverScrollableScrollPhysics(),
  78. children: <Widget>[
  79. _Form(
  80. type: 0, unit: "公里", items: [0.4, 0.8, 1, 3, 5, 10, 21.09, 42.19],
  81. labels: ["400米","800米", "1公里", "3公里", "5公里", "10公里", "半马", "全马"],
  82. target: widget.runTargetKm),
  83. _Form(
  84. type: 1,
  85. unit: "分钟",
  86. items: [10, 20, 30, 40, 60, 90],
  87. labels: ["10分钟", "20分钟", "30分钟", "40分钟", "60分钟", "90分钟"],
  88. target: widget.runTargetDuration,
  89. ),
  90. ],
  91. ),
  92. ),
  93. )
  94. ],
  95. ),
  96. ),
  97. );
  98. }
  99. }
  100. class _Form extends StatefulWidget {
  101. final double target;
  102. final int type;
  103. final String unit;
  104. final List<double> items;
  105. final List<String> labels;
  106. const _Form({Key? key,required this.type,required this.target, required this.unit,required this.items,required this.labels}) : super(key: key);
  107. @override
  108. State<StatefulWidget> createState() => _FormState();
  109. }
  110. class _FormState extends State<_Form> with RunSetting {
  111. ValueNotifier<double> _valueTotal = ValueNotifier(0);
  112. ValueNotifier<int> _valueIndex = ValueNotifier(0);
  113. @override
  114. void initState() {
  115. super.initState();
  116. loadSetting().then((value) {
  117. if (widget.type == 0) {
  118. double target = widget.target > 0 ? widget.target : runTargetKm;
  119. _valueTotal.value = target > 0 ? target / 1000 : widget.items[0];
  120. } else {
  121. double target = widget.target > 0 ? widget.target : runTargetDuration;
  122. _valueTotal.value = target > 0 ? target / 60 : widget.items[0];
  123. }
  124. _valueIndex.value = widget.items.indexOf(_valueTotal.value);
  125. });
  126. }
  127. @override
  128. bool autoLoadSetting() => false;
  129. @override
  130. void dispose() {
  131. super.dispose();
  132. _valueIndex.dispose();
  133. }
  134. @override
  135. Widget build(BuildContext context) {
  136. return SingleChildScrollView(
  137. child: Column(
  138. children: <Widget>[
  139. Row(
  140. mainAxisSize: MainAxisSize.min,
  141. crossAxisAlignment: CrossAxisAlignment.baseline,
  142. textBaseline: TextBaseline.alphabetic,
  143. children: <Widget>[
  144. ValueListenableBuilder<double>(
  145. valueListenable: _valueTotal,
  146. builder: (_, value, ___) => Text(
  147. widget.type == 0 ? "${value.toStringAsFixed(2)}" : "${value.toInt()}",
  148. style: Theme.of(context).textTheme.headline1!.copyWith(fontSize: 40.0, fontFamily: "DIN"),
  149. ),
  150. ),
  151. Padding(
  152. padding: const EdgeInsets.all(6.0),
  153. child: Text(
  154. widget.unit,
  155. style: Theme.of(context).textTheme.subtitle2,
  156. ),
  157. ),
  158. ],
  159. ),
  160. GestureDetector(
  161. onTap: () async {
  162. var result = await NavigatorUtil.goPage(
  163. context,
  164. (context) => RunTargetCustomPage(
  165. target: _valueTotal.value,
  166. type: widget.type,
  167. ));
  168. if (result != null) {
  169. var val = result.trim();
  170. if (val.isEmpty == true) return;
  171. _valueTotal.value = double.parse(val);
  172. _valueIndex.value = -1;
  173. }
  174. },
  175. child: Container(
  176. width: 80.0,
  177. height: 30.0,
  178. margin: const EdgeInsets.all(8.0),
  179. decoration: BoxDecoration(
  180. borderRadius: BorderRadius.circular(20),
  181. border: Border.all(
  182. color: Theme.of(context).accentColor,
  183. width: .5,
  184. ),
  185. ),
  186. child: Center(
  187. child: Text(
  188. "自定义",
  189. style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Theme.of(context).accentColor),
  190. strutStyle: fixedLine,
  191. )),
  192. ),
  193. ),
  194. const SizedBox(
  195. height: 15.0,
  196. ),
  197. Padding(
  198. padding: const EdgeInsets.symmetric(vertical: 20.0),
  199. child: ValueListenableBuilder(
  200. valueListenable: _valueIndex,
  201. builder: (_, value, __) => AlignedGridView.count(
  202. padding: EdgeInsets.zero,
  203. shrinkWrap: true,
  204. physics: NeverScrollableScrollPhysics(),
  205. crossAxisCount: 3,
  206. itemCount: widget.items.length,
  207. itemBuilder: (BuildContext context, int index) => GestureDetector(
  208. onTap: () {
  209. _valueIndex.value = index;
  210. _valueTotal.value = widget.items[index];
  211. },
  212. child: Center(
  213. child: Container(
  214. height: 66.0,
  215. decoration: BoxDecoration(
  216. image: value == index
  217. ? DecorationImage(image: AssetImage("lib/assets/img/control_img_selected.png"), alignment: Alignment.bottomRight)
  218. : null,
  219. borderRadius: BorderRadius.circular(10),
  220. border: Border.all(
  221. color: value == index ? Theme.of(context).accentColor : const Color(0xffCECECE),
  222. width: .5,
  223. ),
  224. ),
  225. child: Center(
  226. child: Text("${widget.labels[index]}",
  227. style: value == index
  228. ? Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 16.0, color: Theme.of(context).accentColor)
  229. : Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 16.0))),
  230. )),
  231. ),
  232. crossAxisSpacing: 12.0,
  233. mainAxisSpacing: 12.0,
  234. ),
  235. ),
  236. ),
  237. const SizedBox(height: 12,),
  238. PrimaryButton(
  239. height: 44.0,
  240. content: "确定目标",
  241. callback: () async {
  242. double runTargetDuration = 0;
  243. double runTargetKm = 0;
  244. if (widget.type == 0) {
  245. this.runTargetKm = runTargetKm = _valueTotal.value * 1000;
  246. // runTargetDuration = 0;
  247. } else {
  248. // runTargetKm = 0;
  249. this.runTargetDuration = runTargetDuration = _valueTotal.value * 60;
  250. }
  251. updateSetting();
  252. Navigator.pop(context, [runTargetKm, runTargetDuration]);
  253. },
  254. )
  255. ],
  256. ),
  257. );
  258. }
  259. }