utils.dart 11 KB

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