utils.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import 'dart:core';
  2. import 'dart:typed_data';
  3. import 'package:fixnum/fixnum.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:get/get.dart';
  6. import 'package:grpc/grpc.dart';
  7. import 'package:trackoffical_app/exception/exception.dart';
  8. import 'package:trackoffical_app/logger.dart';
  9. import 'package:trackoffical_app/route.dart' as r;
  10. import 'package:sensor/sensor.dart';
  11. import 'generated/google/protobuf/timestamp.pb.dart' as pb;
  12. import 'generated/google/protobuf/duration.pb.dart' as pb;
  13. import 'package:geolocator/geolocator.dart' as geo;
  14. import 'model/distance.dart';
  15. export 'model/distance.dart';
  16. extension PbDurationExtension on pb.Duration {
  17. Duration toDuration() {
  18. final microseconds = hasNanos() ? nanos / 1000 : 0;
  19. return Duration(
  20. seconds: seconds.toInt(), microseconds: microseconds.toInt());
  21. }
  22. }
  23. extension DurationExtension on Duration {
  24. pb.Duration toPb() {
  25. final nanos =
  26. (inMicroseconds - inSeconds * Duration.microsecondsPerSecond) * 1000;
  27. return pb.Duration()..seconds= Int64(inSeconds) ..nanos= nanos;
  28. }
  29. String toMinSecondString(){
  30. final minute = inMinutes;
  31. var second = inSeconds;
  32. second = second - minute * Duration.secondsPerMinute;
  33. return '$minute\'$second\'\'';
  34. }
  35. String toAppString() {
  36. final countDown = this;
  37. var s = countDown.inSeconds;
  38. final h = (s / 3600).floor();
  39. final hStr = h.twoDigits();
  40. final m = ((s - h * 3600) / 60).floor();
  41. final mStr = m.twoDigits();
  42. final sStr = (s - h * 3600 - m * 60).twoDigits();
  43. return '$hStr:$mStr:$sStr';
  44. }
  45. Color paceColor(){
  46. final duration = inMilliseconds.abs().toDouble() / 60000;
  47. const maxPace = 5.0;
  48. const minPace = 15.0;
  49. var v = duration - maxPace;
  50. var p = v / (minPace - maxPace);
  51. if(p > 1){
  52. p = 1;
  53. }
  54. if(p < 0){
  55. p = 0;
  56. }
  57. var r = 0;
  58. var g = 255;
  59. if(p <= 0.5){
  60. r = (255.0 * (p / 0.5)).round();
  61. }else{
  62. r = 255;
  63. }
  64. if(p > 0.5){
  65. g = g - (255.0 * ((p-0.5) / 0.5)).round();
  66. }else{
  67. g = 255;
  68. }
  69. return Color.fromARGB(255, r, g, 34);
  70. }
  71. }
  72. extension DateTimeExtension on DateTime {
  73. String toAppString() {
  74. String y =
  75. (year >= -9999 && year <= 9999) ? _fourDigits(year) : _sixDigits(year);
  76. String m = _twoDigits(month);
  77. String d = _twoDigits(day);
  78. String h = _twoDigits(hour);
  79. String min = _twoDigits(minute);
  80. String sec = _twoDigits(second);
  81. return "$y-$m-$d $h:$min:$sec";
  82. }
  83. String toAppStringNoDate() {
  84. String h = _twoDigits(hour);
  85. String min = _twoDigits(minute);
  86. String sec = _twoDigits(second);
  87. return "$h:$min:$sec";
  88. }
  89. }
  90. extension Uint8ListExtension on Uint8List {
  91. String toHexString({String separator = ''}) {
  92. return isEmpty
  93. ? ""
  94. : map((e) {
  95. var s = e.toRadixString(16).toUpperCase();
  96. if (e < 10) {
  97. s = '0$s';
  98. }
  99. return s;
  100. }).join(separator);
  101. }
  102. }
  103. const hrPColors = <Color>[
  104. Color(0xff028FE1),
  105. Color(0xFF4D28ED),
  106. Color(0xFF0AB105),
  107. Color(0xFFFFB308),
  108. Color(0xFFFF6200),
  109. Color(0xFFD11122)
  110. ];
  111. const userMarkColors = <Color>[
  112. Color(0xFFD112B7),
  113. Color(0xFF5312D1),
  114. Color(0xFF1272D1),
  115. Color(0xFF21AEAE),
  116. Color(0xFF612D61),
  117. Color(0xFFF6DE00),
  118. Color(0xFFFF7904),
  119. Color(0xFFFF8E97),
  120. Color(0xFF003C71),
  121. Color(0xFF850000),
  122. ];
  123. extension IntExtension on int {
  124. String twoDigits() {
  125. if (this >= 10) return "$this";
  126. return "0$this";
  127. }
  128. Color toHRPColor() {
  129. final hrp = this;
  130. var color = const Color(0xff028FE1);
  131. if (hrp >= 40 && hrp <= 54) {
  132. color = const Color(0xFF4D28ED);
  133. }
  134. if (hrp >= 55 && hrp <= 69) {
  135. color = const Color(0xFF0AB105);
  136. }
  137. if (hrp >= 70 && hrp <= 79) {
  138. color = const Color(0xFFFFB308);
  139. }
  140. if (hrp >= 80 && hrp <= 89) {
  141. color = const Color(0xFFFF6200);
  142. }
  143. if (hrp >= 90 && hrp <= 9999999) {
  144. color = const Color(0xFFD11122);
  145. }
  146. return color;
  147. }
  148. Color toUserMarkColor() {
  149. final userid = this;
  150. return userMarkColors[userid % 10];
  151. }
  152. }
  153. String _twoDigits(int n) {
  154. if (n >= 10) return "$n";
  155. return "0$n";
  156. }
  157. String _fourDigits(int n) {
  158. int absN = n.abs();
  159. String sign = n < 0 ? "-" : "";
  160. if (absN >= 1000) return "$n";
  161. if (absN >= 100) return "${sign}0$absN";
  162. if (absN >= 10) return "${sign}00$absN";
  163. return "${sign}000$absN";
  164. }
  165. String _sixDigits(int n) {
  166. assert(n < -9999 || n > 9999);
  167. int absN = n.abs();
  168. String sign = n < 0 ? "-" : "+";
  169. if (absN >= 100000) return "$sign$absN";
  170. return "${sign}0$absN";
  171. }
  172. extension PbDateTimeExt on pb.Timestamp {
  173. DateTime? toDateTimeNullable() {
  174. if (nanos == 0 && seconds == 0) {
  175. return null;
  176. }
  177. return toDateTime(toLocal: true);
  178. }
  179. DateTime toModel() {
  180. return toDateTime(toLocal: true);
  181. }
  182. }
  183. extension DateTimeExt on DateTime? {
  184. pb.Timestamp toPb() {
  185. if (this != null) {
  186. return pb.Timestamp.fromDateTime(this!.toUtc());
  187. } else {
  188. return pb.Timestamp();
  189. }
  190. }
  191. String toHHmm() {
  192. final t = this;
  193. if (t == null) {
  194. return "00:00";
  195. }
  196. final hStr = t.hour.twoDigits();
  197. final mStr = t.minute.twoDigits();
  198. return '$hStr:$mStr';
  199. }
  200. }
  201. extension DoubleExtension on double {
  202. String kmToStr({int fixed=1}) {
  203. if(this < 1){
  204. return '${(this*1000).round()} m';
  205. }else{
  206. return '${toStringAsFixed(fixed)} km';
  207. }
  208. }
  209. Color paceColor(){
  210. var v = this - 5;
  211. var p = v / 4;
  212. if(p > 1){
  213. p = 1;
  214. }
  215. if(p < 0){
  216. p = 0;
  217. }
  218. var r = 0;
  219. var g = 255;
  220. if(p <= 0.5){
  221. r = (255.0 * (p / 0.5)).round();
  222. }else{
  223. r = 255;
  224. }
  225. if(p > 0.5){
  226. g = g - (255.0 * ((p-0.5) / 0.5)).round();
  227. }else{
  228. g = 255;
  229. }
  230. return Color.fromARGB(255, r, g, 34);
  231. }
  232. }
  233. class _AskLocationServiceState extends State<AskLocationServiceDialog> with WidgetsBindingObserver {
  234. @override
  235. void initState() {
  236. super.initState();
  237. //2.页面初始化的时候,添加一个状态的监听者
  238. WidgetsBinding.instance.addObserver(this);
  239. }
  240. @override
  241. void dispose() {
  242. super.dispose();
  243. //3. 页面销毁时,移出监听者
  244. WidgetsBinding.instance.removeObserver(this);
  245. }
  246. var isActive=true;
  247. //监听程序进入前后台的状态改变的方法
  248. @override
  249. void didChangeAppLifecycleState(AppLifecycleState state) {
  250. super.didChangeAppLifecycleState(state);
  251. switch (state) {
  252. //进入应用时候不会触发该状态 应用程序处于可见状态,并且可以响应用户的输入事件。它相当于 Android 中Activity的onResume
  253. case AppLifecycleState.resumed:
  254. isActive=true;
  255. break;
  256. //应用状态处于闲置状态,并且没有用户的输入事件,
  257. // 注意:这个状态切换到 前后台 会触发,所以流程应该是先冻结窗口,然后停止UI
  258. case AppLifecycleState.inactive:
  259. break;
  260. //当前页面即将退出
  261. case AppLifecycleState.detached:
  262. break;
  263. // 应用程序处于不可见状态
  264. case AppLifecycleState.paused:
  265. break;
  266. }
  267. }
  268. @override
  269. Widget build(BuildContext context) {
  270. return AlertDialog(
  271. icon: const Icon(Icons.location_on),
  272. title: const Text('需要打开系统定位'),
  273. content: const Text('用于展示附近商家和辅助定向'),
  274. actions: [
  275. TextButton(onPressed: ()=>Get.back(), child: const Text('暂不开启')),
  276. FilledButton(onPressed: ()async{
  277. await geo.Geolocator.openLocationSettings();
  278. isActive=false;
  279. while(!isActive){
  280. await 10.milliseconds.delay();
  281. }
  282. if(mounted){
  283. Get.back();
  284. }
  285. }, child: const Text('去设置'))
  286. ],
  287. );
  288. }
  289. }
  290. class AskLocationServiceDialog extends StatefulWidget{
  291. const AskLocationServiceDialog({super.key});
  292. static Future<void> show(){
  293. return Get.dialog(const AskLocationServiceDialog());
  294. }
  295. @override
  296. State<StatefulWidget> createState() {
  297. return _AskLocationServiceState();
  298. }
  299. }
  300. Future<bool> isLocationServiceEnabled() {
  301. return Sensor.isLocationServiceOpen();
  302. }
  303. Duration pacePerKm(Distance distance, Duration d){
  304. if(distance.km==0){
  305. return Duration.zero;
  306. }
  307. final m = d.inMilliseconds.toDouble();
  308. return (m / distance.km).milliseconds;
  309. }
  310. Future<void> checkLocationService() async {
  311. // Test if location services are enabled.
  312. var serviceEnabled = await isLocationServiceEnabled();
  313. if (!serviceEnabled) {
  314. await AskLocationServiceDialog.show();
  315. if(!await isLocationServiceEnabled()){
  316. // Location services are not enabled don't continue
  317. // accessing the position and request users of the
  318. // App to enable the location services.
  319. return Future.error(NoServiceError());
  320. }
  321. }
  322. }
  323. Future<void> tryCatchApi(Future<void> Function() call, {
  324. String? errTitle,
  325. bool Function(GrpcError err)? onError,
  326. Future<void> Function()? onSuccess,
  327. VoidCallback? onFinally,
  328. })async{
  329. try {
  330. await call();
  331. await onSuccess?.call();
  332. } on GrpcError catch (e) {
  333. warn(e);
  334. if(onError!= null){
  335. if (onError(e)){
  336. return;
  337. }
  338. }
  339. switch (e.code) {
  340. case StatusCode.unavailable:
  341. Get.snackbar('网络错误', "请稍后重试");
  342. break;
  343. case StatusCode.unauthenticated:
  344. if(await r.Route.toLogin(thenBack: true)){
  345. try{
  346. await call();
  347. }catch(e){
  348. Get.snackbar(errTitle?? "出错了", "未知错误");
  349. }
  350. }
  351. break;
  352. case StatusCode.unknown:
  353. Get.snackbar(errTitle?? "出错了", "未知错误");
  354. break;
  355. default:
  356. Get.snackbar(errTitle?? "出错了", e.message??'');
  357. }
  358. } catch (e) {
  359. warn(e);
  360. Get.snackbar(errTitle?? "出错了", "未知错误");
  361. }finally{
  362. onFinally?.call();
  363. }
  364. }
  365. String getFileExtensionFromUrl(String url) {
  366. String extension = '';
  367. if (url.isNotEmpty) {
  368. final path = Uri.parse(url).path;
  369. if (path.isNotEmpty) {
  370. int position = path.lastIndexOf('/');
  371. String fileName = path.substring(position + 1);
  372. position = fileName.lastIndexOf('.');
  373. if (position >= 0 && position < fileName.length - 1) {
  374. extension = fileName.substring(position + 1);
  375. }
  376. }
  377. }
  378. return extension;
  379. }
  380. extension MGetOnNum on num{
  381. Distance get km=> Distance(km: toDouble());
  382. Distance get meter=> Distance(m: toDouble());
  383. int get compassDegrees{
  384. var d = toInt();
  385. while (d < 0) {
  386. d += 360;
  387. }
  388. while (d > 360) {
  389. d -= 360;
  390. }
  391. return d;
  392. }
  393. }