import 'dart:core'; import 'dart:typed_data'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:grpc/grpc.dart'; import 'package:trackoffical_app/exception/exception.dart'; import 'package:trackoffical_app/logger.dart'; import 'package:trackoffical_app/route.dart' as r; import 'package:sensor/sensor.dart'; import 'generated/google/protobuf/timestamp.pb.dart' as pb; import 'generated/google/protobuf/duration.pb.dart' as pb; import 'package:geolocator/geolocator.dart' as geo; import 'model/distance.dart'; export 'model/distance.dart'; extension PbDurationExtension on pb.Duration { Duration toDuration() { final microseconds = hasNanos() ? nanos / 1000 : 0; return Duration( seconds: seconds.toInt(), microseconds: microseconds.toInt()); } } extension DurationExtension on Duration { pb.Duration toPb() { final nanos = (inMicroseconds - inSeconds * Duration.microsecondsPerSecond) * 1000; return pb.Duration()..seconds= Int64(inSeconds) ..nanos= nanos; } String toMinSecondString(){ final minute = inMinutes; var second = inSeconds; second = second - minute * Duration.secondsPerMinute; return '$minute\'$second\'\''; } String toAppString() { final countDown = this; var s = countDown.inSeconds; final h = (s / 3600).floor(); final hStr = h.twoDigits(); final m = ((s - h * 3600) / 60).floor(); final mStr = m.twoDigits(); final sStr = (s - h * 3600 - m * 60).twoDigits(); return '$hStr:$mStr:$sStr'; } Color paceColor(){ final duration = inMilliseconds.abs().toDouble() / 60000; const maxPace = 5.0; const minPace = 15.0; var v = duration - maxPace; var p = v / (minPace - maxPace); if(p > 1){ p = 1; } if(p < 0){ p = 0; } var r = 0; var g = 255; if(p <= 0.5){ r = (255.0 * (p / 0.5)).round(); }else{ r = 255; } if(p > 0.5){ g = g - (255.0 * ((p-0.5) / 0.5)).round(); }else{ g = 255; } return Color.fromARGB(255, r, g, 34); } } extension DateTimeExtension on DateTime { String toAppString() { String y = (year >= -9999 && year <= 9999) ? _fourDigits(year) : _sixDigits(year); String m = _twoDigits(month); String d = _twoDigits(day); String h = _twoDigits(hour); String min = _twoDigits(minute); String sec = _twoDigits(second); return "$y-$m-$d $h:$min:$sec"; } String toAppStringNoDate() { String h = _twoDigits(hour); String min = _twoDigits(minute); String sec = _twoDigits(second); return "$h:$min:$sec"; } } extension Uint8ListExtension on Uint8List { String toHexString({String separator = ''}) { return isEmpty ? "" : map((e) { var s = e.toRadixString(16).toUpperCase(); if (e < 10) { s = '0$s'; } return s; }).join(separator); } } const hrPColors = [ Color(0xff028FE1), Color(0xFF4D28ED), Color(0xFF0AB105), Color(0xFFFFB308), Color(0xFFFF6200), Color(0xFFD11122) ]; const userMarkColors = [ Color(0xFFD112B7), Color(0xFF5312D1), Color(0xFF1272D1), Color(0xFF21AEAE), Color(0xFF612D61), Color(0xFFF6DE00), Color(0xFFFF7904), Color(0xFFFF8E97), Color(0xFF003C71), Color(0xFF850000), ]; extension IntExtension on int { String twoDigits() { if (this >= 10) return "$this"; return "0$this"; } Color toHRPColor() { final hrp = this; var color = const Color(0xff028FE1); if (hrp >= 40 && hrp <= 54) { color = const Color(0xFF4D28ED); } if (hrp >= 55 && hrp <= 69) { color = const Color(0xFF0AB105); } if (hrp >= 70 && hrp <= 79) { color = const Color(0xFFFFB308); } if (hrp >= 80 && hrp <= 89) { color = const Color(0xFFFF6200); } if (hrp >= 90 && hrp <= 9999999) { color = const Color(0xFFD11122); } return color; } Color toUserMarkColor() { final userid = this; return userMarkColors[userid % 10]; } } String _twoDigits(int n) { if (n >= 10) return "$n"; return "0$n"; } String _fourDigits(int n) { int absN = n.abs(); String sign = n < 0 ? "-" : ""; if (absN >= 1000) return "$n"; if (absN >= 100) return "${sign}0$absN"; if (absN >= 10) return "${sign}00$absN"; return "${sign}000$absN"; } String _sixDigits(int n) { assert(n < -9999 || n > 9999); int absN = n.abs(); String sign = n < 0 ? "-" : "+"; if (absN >= 100000) return "$sign$absN"; return "${sign}0$absN"; } extension PbDateTimeExt on pb.Timestamp { DateTime? toDateTimeNullable() { if (nanos == 0 && seconds == 0) { return null; } return toDateTime(toLocal: true); } DateTime toModel() { return toDateTime(toLocal: true); } } extension DateTimeExt on DateTime? { pb.Timestamp toPb() { if (this != null) { return pb.Timestamp.fromDateTime(this!.toUtc()); } else { return pb.Timestamp(); } } String toHHmm() { final t = this; if (t == null) { return "00:00"; } final hStr = t.hour.twoDigits(); final mStr = t.minute.twoDigits(); return '$hStr:$mStr'; } } extension DoubleExtension on double { String kmToStr({int fixed=1}) { if(this < 1){ return '${(this*1000).round()} m'; }else{ return '${toStringAsFixed(fixed)} km'; } } Color paceColor(){ var v = this - 5; var p = v / 4; if(p > 1){ p = 1; } if(p < 0){ p = 0; } var r = 0; var g = 255; if(p <= 0.5){ r = (255.0 * (p / 0.5)).round(); }else{ r = 255; } if(p > 0.5){ g = g - (255.0 * ((p-0.5) / 0.5)).round(); }else{ g = 255; } return Color.fromARGB(255, r, g, 34); } } class _AskLocationServiceState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); //2.页面初始化的时候,添加一个状态的监听者 WidgetsBinding.instance.addObserver(this); } @override void dispose() { super.dispose(); //3. 页面销毁时,移出监听者 WidgetsBinding.instance.removeObserver(this); } var isActive=true; //监听程序进入前后台的状态改变的方法 @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { //进入应用时候不会触发该状态 应用程序处于可见状态,并且可以响应用户的输入事件。它相当于 Android 中Activity的onResume case AppLifecycleState.resumed: isActive=true; break; //应用状态处于闲置状态,并且没有用户的输入事件, // 注意:这个状态切换到 前后台 会触发,所以流程应该是先冻结窗口,然后停止UI case AppLifecycleState.inactive: break; //当前页面即将退出 case AppLifecycleState.detached: break; // 应用程序处于不可见状态 case AppLifecycleState.paused: break; } } @override Widget build(BuildContext context) { return AlertDialog( icon: const Icon(Icons.location_on), title: const Text('需要打开系统定位'), content: const Text('用于展示附近商家和辅助定向'), actions: [ TextButton(onPressed: ()=>Get.back(), child: const Text('暂不开启')), FilledButton(onPressed: ()async{ await geo.Geolocator.openLocationSettings(); isActive=false; while(!isActive){ await 10.milliseconds.delay(); } if(mounted){ Get.back(); } }, child: const Text('去设置')) ], ); } } class AskLocationServiceDialog extends StatefulWidget{ const AskLocationServiceDialog({super.key}); static Future show(){ return Get.dialog(const AskLocationServiceDialog()); } @override State createState() { return _AskLocationServiceState(); } } Future isLocationServiceEnabled() { return Sensor.isLocationServiceOpen(); } Duration pacePerKm(Distance distance, Duration d){ if(distance.km==0){ return Duration.zero; } final m = d.inMilliseconds.toDouble(); return (m / distance.km).milliseconds; } Future checkLocationService() async { // Test if location services are enabled. var serviceEnabled = await isLocationServiceEnabled(); if (!serviceEnabled) { await AskLocationServiceDialog.show(); if(!await isLocationServiceEnabled()){ // Location services are not enabled don't continue // accessing the position and request users of the // App to enable the location services. return Future.error(NoServiceError()); } } } Future tryCatchApi(Future Function() call, { String? errTitle, bool Function(GrpcError err)? onError, Future Function()? onSuccess, VoidCallback? onFinally, })async{ try { await call(); await onSuccess?.call(); } on GrpcError catch (e) { warn(e); if(onError!= null){ if (onError(e)){ return; } } switch (e.code) { case StatusCode.unavailable: Get.snackbar('网络错误', "请稍后重试"); break; case StatusCode.unauthenticated: if(await r.Route.toLogin(thenBack: true)){ try{ await call(); }catch(e){ Get.snackbar(errTitle?? "出错了", "未知错误"); } } break; case StatusCode.unknown: Get.snackbar(errTitle?? "出错了", "未知错误"); break; default: Get.snackbar(errTitle?? "出错了", e.message??''); } } catch (e) { warn(e); Get.snackbar(errTitle?? "出错了", "未知错误"); }finally{ onFinally?.call(); } } String getFileExtensionFromUrl(String url) { String extension = ''; if (url.isNotEmpty) { final path = Uri.parse(url).path; if (path.isNotEmpty) { int position = path.lastIndexOf('/'); String fileName = path.substring(position + 1); position = fileName.lastIndexOf('.'); if (position >= 0 && position < fileName.length - 1) { extension = fileName.substring(position + 1); } } } return extension; } extension MGetOnNum on num{ Distance get km=> Distance(km: toDouble()); Distance get meter=> Distance(m: toDouble()); int get compassDegrees{ var d = toInt(); while (d < 0) { d += 360; } while (d > 360) { d -= 360; } return d; } }