| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- import 'dart:async';
- import 'dart:io';
- import 'package:assets_audio_player/assets_audio_player.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:get/get.dart';
- import 'package:get_storage/get_storage.dart';
- import 'package:trackoffical_app/generated/assets.dart';
- import 'package:trackoffical_app/model.dart';
- import 'package:trackoffical_app/pb.dart' as pb;
- import 'package:trackoffical_app/service/app.dart';
- import 'package:trackoffical_app/service/game/game_model.dart';
- import 'package:trackoffical_app/service/sport_wear.dart';
- import 'package:screen_brightness/screen_brightness.dart';
- import '../game_settings.dart';
- import 'dialog/dialog_base.dart';
- import 'dialog/dialog_confirm_finish.dart';
- import 'dialog/dialog_cp_order_err.dart';
- import 'dialog/dialog_check_rich2.dart';
- import 'dialog/dialog_check_text.dart';
- import 'package:trackoffical_app/widget/compass2.dart';
- import '../../model/m_control_point.dart';
- import 'settlement_view.dart';
- import '../../logger.dart';
- import 'package:nfc_manager/nfc_manager.dart';
- import '../../service/game/game.dart';
- import '../../utils.dart';
- import 'package:nfc_manager/platform_tags.dart';
- import 'package:vector_math/vector_math_64.dart' as vec;
- import 'package:sensor/sensor.dart' as sensor;
- class InGameController extends GetxController {
- static double bottomBarHeight = 120.0;
- final service = GameService.to;
- bool get isShowCompass => service.isShowCompass.value;
- DateTime? get startAt => GameService.to.startAt;
- Duration get maxDuration => GameService.to.maxPassDuration;
- double compassAngleDegree = 0.0;
- DateTime? lastGyroscopeTime;
- bool get isNfcScanUseDialog => GameService.to.isNfcScanUseDialog;
- List<MControlPoint> get checkedPointHistory => GameService.to.checkedPointsHistory;
- final isShowTimeBar = true.obs;
- final phoneX = 0.obs;
- final phoneY = 0.obs;
- final legend = GameService.to.gameState.pbGameData.legendImage.toModel();
- Function? disposeListen;
- final GameModel _model = Get.find();
- StreamSubscription<double>? _compassSubscription;
- StreamSubscription<MPosition?>? _myPositionSubscription;
- final compassLevel = Compass2.levelMin.obs;
- final isMapRotateAtCompassCenter = false.obs;
- /// 是否显示下一个点的方向
- final isShowNextCPRadians = true.obs;
- final isShowRuler = false.obs;
- var _compassSizeIndex = 0;
- final _compassSizeList = <double>[160.0, 200, 240, 280];
- final uiMode = App.to.userProfile.gameSettingsUIMode.value.obs;
- final _isUseRealNorth = Get.find<GameModel>().isUseRealNorth.obs;
- bool get isUseRealNorth => _isUseRealNorth.value;
- set isUseRealNorth(v){
- _isUseRealNorth.value=v;
- _model.isUseRealNorth=v;
- }
- final _isInGameUISimplifyMode = App.to.userProfile.isInGameUISimplifyMode.val.obs;
- bool get isInGameUISimplifyMode => _isInGameUISimplifyMode.value;
- set isInGameUISimplifyMode(v){
- _isInGameUISimplifyMode.value=v;
- App.to.userProfile.isInGameUISimplifyMode.val=v;
- }
- final _isEnableOutBoundaryWarn = App.to.userProfile.isEnableOutBoundaryWarn.val.obs;
- bool get isEnableOutBoundaryWarn => _isEnableOutBoundaryWarn.value;
- set isEnableOutBoundaryWarn(v){
- _isEnableOutBoundaryWarn.value=v;
- App.to.userProfile.isEnableOutBoundaryWarn.val=v;
- }
- final _audioPlayer = AssetsAudioPlayer.newPlayer();
- bool get isEnableUserLocation=>_model.isEnableUserLocation;
- set isEnableUserLocation(v){
- _model.isEnableUserLocation=v;
- }
- final isStartShowWarn = _StorageValue(App.to.userProfile.isStartShowWarn);
- final isEnableGameSound = _StorageValue(App.to.userProfile.isEnableGameSound);
- final isEnableGameVibrate = _StorageValue(App.to.userProfile.isEnableGameVibrate);
- final isEnableGameCulture = _StorageValue(App.to.userProfile.isEnableGameCulture);
- final isEnableInGameStartCPBubble = _StorageValue(App.to.userProfile.isEnableInGameStartCPBubble);
- bool get isShowOutBoundaryWarn => isEnableOutBoundaryWarn && service.isOutBoundary;
- bool get isBrightnessMax => App.to.userProfile.isEnableInGameBrightnessMax.val;
- Future<void> setIsBrightnessMax(bool v)async{
- App.to.userProfile.isEnableInGameBrightnessMax.val=v;
- if (v) {
- await ScreenBrightness().setScreenBrightness(1);
- } else {
- await ScreenBrightness().resetScreenBrightness();
- }
- }
- Future<void> toSettings()async{
- await Get.to(() => const GameSettingsView(isInGame: true));
- }
- double? get nextControlPointDistanceKm{
- final real = _model.nextPlanCPDistanceKm;
- if(real == null){
- return null;
- }
- var show = real.km - _model.controlPointEffectiveAreaRadiusKm
- + GameModel.controlPointEffectiveAreaRadiusKmOffset;
- if(show < 0 ){
- show = 0;
- }
- return show;
- }
- final _trajectorySeconds = App.to.userProfile.inGameTrajectorySeconds.val.obs;
- int get trajectorySeconds => _trajectorySeconds.value;
- set trajectorySeconds(v){
- _trajectorySeconds.value=v;
- App.to.userProfile.inGameTrajectorySeconds.val=v;
- }
- Offset get compassCenter => Offset(
- App.to.screenSize.width/2,
- App.to.screenSize.height - bottomBarHeight - compassDiameter/2);
- double get compassDiameter => _model.compassDiameter.value;
- sensor.Orientation get orientation => _model.orientation.value;
- bool get isPhoneHorizontal{
- final x = vec.degrees(orientation.x);
- final y = vec.degrees(orientation.y);
- if (x.abs() > 30 || y.abs()>30){
- return false;
- }else{
- return true;
- }
- }
- bool get isShowPhoneHorizontalWarn => (!isPhoneHorizontal)&&(!_model.isMoving);
- final isNoMapRulerScaleMode = false.obs;
- bool get isShowCheckCPButton{
- final next = service.getNextWantPoint(0);
- final nextPlan = _model.nextPlanPoint;
- return isNfcScanUseDialog || next?.type==MControlPointType.gps || nextPlan?.type==MControlPointType.gps;
- }
- bool get isCheckCPButtonEnable {
- final next = service.getNextWantPoint(0);
- final nextPlan = _model.nextPlanPoint;
- final iosEnable = isNfcScanUseDialog
- && (next?.type==MControlPointType.nfc || nextPlan?.type==MControlPointType.nfc);
- return (_model.isInPlanControlPointArea ||_model.isInWantControlPointArea || iosEnable) && isShowCheckCPButton;
- }
- bool get isCheckCPButtonWarn => _model.isInWantControlPointArea && isCheckCPButtonEnable;
- bool get isSportWearConnected => Get.find<SportWearService>().connectedSportWear.value != null;
- /// 地图比例尺 1:[mapScale]
- final Rx<double?> mapScale = Rx(null);
- String get beginDurationStr {
- final startAt = this.startAt;
- if (startAt == null) {
- return '00:00:00';
- } else {
- final countDown = GameService.to.beginDuration.value;
- 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';
- }
- }
- /// 下个点方向弧度
- double? get nextCPRadians {
- final p0 = _model.myPosition.value;
- final p1 = _model.nextPlanPoint?.position;
- if(p0 == null || p1 == null){
- return null;
- }
- return _model.compassRadiansFused.value + p0.directionTo(p1);
- }
- void setRotateCenterToScreenCenter(){
- _model.setRotateCenterToScreenCenter();
- }
- void setRotateCenterToCompassCenter(){
- _model.mapRotateCenter = compassCenter;
- }
- void flushRotateCenter(){
- if (isMapRotateAtCompassCenter.value) {
- setRotateCenterToCompassCenter();
- } else {
- setRotateCenterToScreenCenter();
- }
- if(_model.isLockScreenCenterToMyPositionSystem){
- service.showLocation();
- }
- }
- void showCompassSwitch() {
- service.isShowCompass.value = !service.isShowCompass.value;
- }
- void showTimeSwitch(){
- isShowTimeBar.value = !isShowTimeBar.value;
- }
- var _lastRelocateTime = DateTime.now();
- @override
- void onReady() {
- final box = GetStorage();
- disposeListen = box.listen((){
- uiMode.value = App.to.userProfile.gameSettingsUIMode.value;
- });
- setIsBrightnessMax(isBrightnessMax);
- onRequestData().then((value) => info('数据加载成功'));
- final gameService = GameService.to;
- final mapStatus = gameService.mapStatus;
- _compassSubscription = _model.compassRadiansFused.listen((v) {
- if (mapStatus.mapMode.value == MapMode.compass) {
- gameService.setBeginMatrix();
- mapStatus.setRotate(v);
- }
- });
- _myPositionSubscription = _model.myPosition.listen((p0) {
- if(p0 != null && _model.isLockScreenCenterToMyPositionSystem){
- final now = DateTime.now();
- if(now.difference(_lastRelocateTime) > 100.milliseconds){
- gameService.showLocation();
- _lastRelocateTime = now;
- }
- }
- });
- if (!isNfcScanUseDialog){
- NfcManager.instance.startSession(
- onDiscovered: _onNfcDiscovered, alertMessage: '保持NFC靠近');
- info('Nfc开始扫描');
- }
- workPlayDistanceSound();
- }
- Future<void> _onNfcDiscovered(NfcTag tag) async {
- info("NFC: \n ${tag.data}");
- String identifier = "";
- if (Platform.isAndroid) {
- identifier = (NfcA.from(tag)?.identifier ??
- NfcB.from(tag)?.identifier ??
- NfcF.from(tag)?.identifier ??
- NfcV.from(tag)?.identifier ??
- Uint8List(0))
- .toHexString();
- }
- if (Platform.isIOS) {
- identifier = (Iso15693.from(tag)?.identifier ??
- Iso7816.from(tag)?.identifier ??
- MiFare.from(tag)?.identifier ??
- Uint8List(0))
- .toHexString();
- }
- info('Id: $identifier');
- GameService.to.checkPointNFC(
- identifier,
- _onChecked,
- _onConfirmFinish,
- _onProjectPoint,
- _onNoPoint
- );
- }
- Future<void> onCheckControlPoint() async {
- final next = _model.getNextWantPoint(0);
- if(next != null){
- switch (next.type){
- case MControlPointType.nfc:
- await onIosNfcStart();
- break;
- case MControlPointType.gps:
- await GameService.to.checkPointGps(
- _onChecked, _onConfirmFinish
- );
- break;
- }
- }
- }
- Future<void> onCheckGpsControlPoint() async {
- GameService.to.checkPointGps(
- _onChecked, _onConfirmFinish
- );
- }
- Future<void> onSwitchCompassSize() async {
- _compassSizeIndex++;
- if(_compassSizeIndex>=_compassSizeList.length){
- _compassSizeIndex=0;
- }
- _model.compassDiameter.value = _compassSizeList[_compassSizeIndex];
- flushRotateCenter();
- }
- _closeExistDialog(){
- if(Get.isOverlaysOpen){
- Get.back();
- }
- }
- _playCheckSound(MControlPoint cp) async{
- if(cp.isFinish){
- return;
- }
- await Future.delayed(200.milliseconds);
- var src = cp.isSuccess? 'assets/sound/ok.wav': 'assets/sound/fail.wav';
- if(!cp.isSuccess && cp.isPlan){
- src = Assets.soundPlanOk;
- }
- if(isEnableGameSound.value) {
- AssetsAudioPlayer.newPlayer().open(
- Audio(src),
- autoStart: true,
- showNotification: false,
- );
- }
- }
- Future<bool> _onConfirmFinish()async{
- return true;
- return await dialogAskConfirmFinish();
- }
- _onChecked(MControlPoint cp){
- _closeExistDialog();
- final status = GameService.to.status;
- info('状态:$status');
- final isOver = [GameStatus.settlement, GameStatus.idle].contains(status);
- final next = GameService.to.getNextWantPoint(0);
- onConfirm(){
- if(isOver){
- SettlementView.show();
- }
- }
- _playCheckSound(cp);
- if(cp.isSuccess){
- if(cp.isStart){
- Get.dialog(DialogCheckText(text: '开始', color: Colors.white, autoPlayAfter: 200.milliseconds,));
- }else if(cp.isFinish){
- onConfirm();
- }else{
- showDialogCheckRich(cp, _model.gameQuestionShowDuration).then((value) {
- service.save();
- onConfirm();
- } );
- }
- }else{
- if(isOver){
- onConfirm();
- }else{
- if(cp.isPlan){
- Get.dialog(const DialogCheckText(
- text: '已打点', color: Color(0xffff870d),
- ));
- }else if(next!= null){
- dialogCPOrderErr(next);
- }else{
- Get.dialog(const DialogCheckText(text: '打点错误', color: Colors.red));
- }
- }
- }
- }
- _onProjectPoint(pb.ControlPointSimple point){
- _closeExistDialog();
- _playCheckSound(MControlPoint()
- ..isSuccess=false);
- Get.dialog(dialogTitle(
- '非线路检查点',
- Colors.red, const Text('请检查地图线路'),
- offAfter: 3.seconds
- ));
- }
- _onNoPoint(){
- _closeExistDialog();
- _playCheckSound(MControlPoint()
- ..isSuccess=false);
- Get.dialog(dialogTitle(
- '打点错误',
- Colors.red, const Text('不是检查点'),
- offAfter: 3.seconds
- ));
- }
- Future<void> onIosNfcStart() async {
- if (!(await App.to.isNfcAvailable)) {
- Get.showSnackbar(const GetSnackBar(
- message: 'NFC不可用',
- ));
- return;
- }
- NfcManager.instance.startSession(
- alertMessage: '请靠近打卡点',
- onDiscovered: (tag) async {
- try {
- await _onNfcDiscovered(tag);
- await NfcManager.instance.stopSession(alertMessage: '打卡成功');
- } catch (e) {
- await NfcManager.instance.stopSession(errorMessage: '$e');
- }
- },
- );
- }
- Future<void> onRequestData() async {}
- Future<void> forceExit() async {
- await GameService.to.gameGiveUpAndToFinishView();
- }
- Future<void> workPlayDistanceSound()async{
- while(!isClosed){
- final disKm = nextControlPointDistanceKm;
- if(disKm != null && service.endAt==null){
- final disM = disKm * 1000;
- var d = 5200.milliseconds;
- String? src = 'assets/sound/beep.wav';
- if(disM < 30){
- d = 1200.milliseconds;
- }
- if(disM < 10){
- d = 700.milliseconds;
- }
- if((_model.isInPlanControlPointArea && _model.nextPlanPoint?.type == MControlPointType.gps)
- || (_model.isInWantControlPointArea&& _model.getNextWantPoint(0)?.type == MControlPointType.gps)){
- d = 3400.milliseconds;
- src = 'assets/sound/punch_alarm.mp3';
- }
- if(isEnableGameSound.value){
- await _audioPlayer.open(
- Audio(src),
- autoStart: false,
- showNotification: false,
- );
- await _audioPlayer.play();
- }
- await Future.delayed(d);
- }else{
- await Future.delayed(500.milliseconds);
- }
- }
- }
- @override
- void onClose() {
- disposeListen?.call();
- _audioPlayer.stop();
- _myPositionSubscription?.cancel();
- _compassSubscription?.cancel();
- if (!isNfcScanUseDialog) {
- NfcManager.instance.stopSession();
- }
- debug('停止指南');
- }
- }
- class _StorageValue<T>{
- _StorageValue(this._data): _state=_data.val.obs;
- final ReadWriteValue<T> _data;
- final Rx<T> _state;
- T get value => _state.value;
- set value(T v){
- _state.value = v;
- _data.val = v;
- }
- }
- class InGameControllerMock extends InGameController {
- @override
- Future<void> onRequestData() async {}
- }
|