import 'dart:math'; import 'dart:typed_data'; import 'package:grpc/grpc.dart'; import 'package:fixnum/fixnum.dart' as fixnum; import 'package:trackoffical_app/model.dart'; import 'package:trackoffical_app/service/app.dart'; import 'package:trackoffical_app/pb.dart' as pb; import 'package:trackoffical_app/service/service.dart'; import 'package:trackoffical_app/utils.dart'; import 'package:protobuf/protobuf.dart'; import '../logger.dart'; import '../global.dart'; class _Stub{ _Stub( this.channel, this.stub ); ClientChannel channel; pb.ApiAppClient stub; } class ApiService extends IService { static ApiService get to => Get.find(); ClientChannel? channel; String? get token { final out = App.to.userProfile.token.val; if (out.isEmpty) { return null; } return out; } set token(String? v) { App.to.userProfile.token.val = v ?? ''; } String get _appVersion => App.to.appVersion; ClientChannel _newChannel(){ return ClientChannel( GlobalVar.apiHost, port: GlobalVar.apiPort, options: const ChannelOptions(credentials: ChannelCredentials.secure()), ); } pb.ApiAppClient _getStub({Duration? timeout, ClientChannel? channel}){ if (this.channel == null) { throw Exception('$runtimeType 未初始化'); } final metadata = { 'source': "${pb.LoginSource.UserApp.value}" }; metadata['version'] = _appVersion; if (token != null) { metadata['token'] = token!; } return pb.ApiAppClient(channel??this.channel!, options: CallOptions( metadata: metadata, timeout: timeout, )); } pb.ApiAppClient get stub { return _getStub(timeout: 10.seconds); } _Stub get _stubForStream { final ch = _newChannel(); return _Stub(ch, _getStub(channel: ch)) ; } Future syncTime()async{ try{ final serverNow = await serverTime(); App.to.correctByServerNow(serverNow); info('服务器时间:${App.to.now}'); }catch(e){ warn("获取服务器时间失败: ", e); } } @override Future init() async { channel = _newChannel(); syncTime(); return this; } Future isSignIn() async { final token = this.token; if (token == null) { return false; } if (token.isNotEmpty) { try { await flushUserInfo(); } catch (e) { return false; } return true; } else { return false; } } Future getVfCode() async { await stub.getVfPic(pb.DefaultRequest()); } Future authSendCodeToPhone(String phone, pb.SmsType smsType)async{ info('authSendCodeToPhone [$phone]'); await stub.authSendCodeToPhone(pb.AuthSendCodeToPhoneRequest() ..phone= phone ..userType= pb.UserType.AppUser ..smsType= smsType ); } Future signUp(String phone, String code, String? name, pb.User_Sex sex)async{ info('signUp [$phone]'); var r = await stub.signUp(pb.SignUpRequest() ..phone= phone ..password= code ..nickname= name??'' ..userType= pb.UserType.AppUser ..sex= sex ); token = r.token; } Future signIn(String name, String password) async { final r = await stub.signIn(pb.SignInRequest() ..name = name ..password = password ); token = r.token; debug('sign in success: $token'); } void signOut(){ stub.signOut(pb.SignOutRequest()); token = null; } Future gameStart( int actId, fixnum.Int64 courseId, ) async { await syncTime(); final r = await stub.gameStart(pb.GameStartRequest() ..actId= fixnum.Int64(actId) ..courseId= courseId ..hrBand= pb.HrBandType.UseHrBand ); return r.gameData; } Future gameSaveUpload(pb.GameSaveUploadRequest save) async { info('gameSaveUpload'); final save2 = save.deepCopy(); save2.gameSave.gameHrInfos.clear(); save2.gameSave.gameGpsInfos.clear(); await stub.gameSaveUpload(save2); return; } Future activityList({ MPosition? position }) async { info('call providerList $position'); final req = pb.PositionRequest(); if(position!= null){ req.position=position.toPb(); } final r = await stub.activityList(req); return r; } Future activityDetail(int id) async { debug('call activityDetail $id'); final r = await stub.activityDetail(pb.IdRequest()..id= fixnum.Int64(id)); return r; } Future flushUserInfo()async{ info('flushUserInfo'); final r = await stub.myUserQuery(pb.DefaultRequest()); final userProfile = App.to.userProfile; userProfile.username.value = r.name; userProfile.head.value = r.head.toModel(); userProfile.sex = r.sex.toModel(); userProfile.age.val = App.to.now.year - r.birthdayYear; userProfile.heightCm.val = r.heightMillimeter / 10; userProfile.weightKg.val = r.weightGram / 1000; userProfile.rhr.val = r.staticHr; } Future _userInfoEdit( String nickName, Sex sex, int birthdayYear, double heightCm, double weightKg, int rhr, ){ return stub.userInfoEdit(pb.UserInfoEditRequest() ..nickName= nickName ..sex= sex.toPb() ..birthdayYear= birthdayYear ..heightMillimeter= (heightCm * 10).toInt() ..weightGram= (weightKg*1000).toInt() ..staticHr = rhr ); } Future saveUserInfo(){ App app = Get.find(); final userProfile = app.userProfile; return _userInfoEdit( userProfile.username.value, userProfile.sex, app.now.year - userProfile.age.val, userProfile.heightCm.val, userProfile.weightKg.val, userProfile.rhr.val); } Future gameHistory({ required pb.GameState state, required int offset, required int limit, })async{ return await stub.myHistoryGame(pb.MyHistoryGameRequest() ..state= state ..offset= offset ..limit=limit ); } Future gameHistoryDel(fixnum.Int64 id){ return stub.historyGameDel(pb.IdRequest()..id= id); } Future closeAccount()async{ await stub.unsubscribe(pb.DefaultRequest()); } Future gameGpsUpload( fixnum.Int64 gameId, List data, int distanceMeter, int paceSecond, ){ info('gameGpsUpload'); List l = data.map((p) => p.toPb2()).toList(); final req = pb.GameGpsUploadRequest() ..gameId= gameId.toInt() ..distance= distanceMeter ..pace= paceSecond ..gameGpsInfos.addAll(l); return stub.gameGpsUpload(req); } Future gameHrUpload( fixnum.Int64 gameId, List data, ){ info('gameHrUpload'); return stub.gameHrUpload(pb.GameHrUploadRequest() ..gameId= gameId.toInt() ..gameHrInfos.addAll(data) ); } Future gameExerciseStateUpload( fixnum.Int64 gameId, int avgHr, int maxHr, /// 单位卡,不是千卡 int cle, double ck, double ei, int stepCount, // % int heartRatePercent ){ info('gameExerciseStateUpload'); return stub.gameCleUpload(pb.GameCleUploadRequest() ..gameId= gameId.toInt() ..avgHr= avgHr ..maxHr= maxHr ..cle= cle ..ck= ck.round() .. ei= ei ..stepNum= stepCount ..heartRatePercent= heartRatePercent ); } @override void onClose() { channel?.shutdown(); } Future serverTime() async { final begin = DateTime.now(); final r = await stub.getServerTime(pb.DefaultRequest()); final cost = DateTime.now().difference(begin); final serverNow = DateTime.fromMillisecondsSinceEpoch( r.millisecondStamp.toInt(), isUtc: true) .toLocal(); return serverNow.add(cost); } Future getRegionCode() async{ final r =await stub.getRegion(pb.DefaultRequest()); info('位置:${r.name}'); return r.code; } Future> getRegionList() async{ info('getRegionList'); final r =await stub.regionList(pb.RegionListRequest()..countryCode= "CN"); final list = r.region; return list; } Future getSmsSendLeftTime(String phone)async{ final r = await stub.getSmsSendLeftTime(pb.GetSmsSendLeftTimeRequest()..phone= phone); info('getSmsSendLeftTime: $phone - ${r.second}s'); return r.second.seconds; } Future getUpdateVersion(String version)async{ info('getUpdateVersion: $version'); final r = await stub.getUpdateVersion(pb.GetUpdateVersionRequest()..vCode= version); return r; } Future getGameHistoryDetail(fixnum.Int64 id)async{ info('getGameHistoryDetail: $id'); return await stub.historyGameDetail(id.toIdRequest()); } Future gameFinish(fixnum.Int64 id, bool isDrop)async{ info('gameFinish: $id , isDrop: $isDrop'); return await stub.gameFinish(pb.GameFinishRequest()..gameId= id.toInt().. isDrop= isDrop); } Future getInGameData()async{ info('getInGameData'); return await stub.getInGameData(pb.DefaultRequest()); } Future> guardianList()async{ info('guardianList'); final r = await stub.myCareTakerQuery(pb.DefaultRequest()); return r.list; } Future> underGuardianList()async{ info('underGuardianList'); final r = await stub.myPupilListQuery(pb.DefaultRequest()); return r.list; } Future guardianAdd(String phone, String memo)async{ info('guardianAdd: $phone'); await stub.myCareTakerAdd(pb.CareTakerInfo() ..cPhone= phone ..memo= memo ); return; } Future guardianDel(pb.CareTakerInfo v)async{ info('guardianAdd: ${v.crId}'); await stub.myCareTakerDel(v.crId.toIdRequest()); return; } Future> mapRecommendList(MPosition? position)async{ info('mapRecommendList: $position'); final req = pb.PositionRequest(); if(position!= null){ req.position=position.toPb(); } final r = await stub.mapRecommendList(req); return r.mapList.map((e) => e.toModel()..isRecommend=true).toList(); } Future> mapList(MPosition? position, int offset, int limit)async{ info('mapList: $position'); final req = pb.MapListRequest() ..offset=offset ..limit=limit; if(position!= null){ req.position=position.toPb(); } final r = await stub.mapList(req); return r.mapList.map((e) => e.toModel()).toList(); } StreamGuardianWatch guardianWatch(int id){ info('guardianWatch: $id'); final stub = _stubForStream; final stream = stub.stub.pupilInGameWatch(id.toIdRequest()); return StreamGuardianWatch(stub.channel, stream); } Future getBinByMd5(Uint8List md5, OnPercentage onPercentage)async{ final stream = _getStub().getBinaryByMd5(pb.GetBinaryByMd5Request()..md5= md5); String ext=''; Uint8List? data; var nonce=Uint8List(0); var i = 0; await for (var one in stream){ if(data == null){ data = Uint8List(one.allCount); ext = one.ext; nonce= Uint8List.fromList(one.nonce); } for(var b in one.data){ data[i]=b; i++; } onPercentage(i, data.length); } await stream.cancel(); return Bin() ..ext=ext ..data=data??Uint8List(0) ..nonce=nonce; } Future userHeadEdit(String ext, Uint8List data)async{ await stub.userHeadEdit(pb.Image()..ext= ext ..data= data); } Future mapActivityList(int mapId, String pin)async{ info('mapActivityList mapId: $mapId pin: $pin'); final r = await stub.mapActivityList(pb.MapActivityListRequest() ..id= mapId ..pinCode= pin.trim() ); return r; } Future pinCodeActivityList(String pin)async{ info('pinCodeActivityList pin: $pin'); final r = await stub.pinCodeActivityList(pb.PinCodeActivityListRequest() .. pinCode= pin.trim() ); return r; } } class Bin{ var ext=''; var data=Uint8List(0); var nonce=Uint8List(0); } typedef OnPercentage = void Function(int, int); class ApiServiceMock extends ApiService { @override Future init() async { debug('$runtimeType ready!'); return this; } final random = Random(); @override Future signIn(String name, String password) async {} @override Future gameStart( int projectId, fixnum.Int64 mapRouteId) async { info('gameStart Mock'); final data = pb.GameData(); final p1 = pb.ControlPoint() ..id= fixnum.Int64(1) ..info = pb.ControlPointInfo(); final p2 = pb.ControlPoint()..id= fixnum.Int64(2) ..info = pb.ControlPointInfo(); p1.nfcIdList.add('0465be121c1291'); p2.nfcIdList.add('0465bd121c1291'); data.controlPointSortedList.addAll([p1, p2]); data.mapZip = pb.NetImage() ..url= 'https://p3.itc.cn/q_70/images03/20210317/94c7636527d042d8a7c9ed08529cc8b2.jpeg' ..md5= [1, 1, 1]; data.maxDuration = pb.Duration()..seconds= fixnum.Int64(60) * 60; return data; } @override Future gameSaveUpload(pb.GameSaveUploadRequest save) async { return; } @override void onReady() {} @override void onClose() {} @override Future serverTime() async { return DateTime.now(); } @override Future mapActivityList(int mapId, String pin)async{ return pb.MapActivityListReply() ..list.addAll([ pb.MapActivitySimpleInfo() ..name= '穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅' ..difficulty= 1 ..distanceMinMeter= 217.1 ..closeDoorTime= 190.minutes.toPb() ..isUsed= false ..routeCount= 28 ..totalControlNum= 12 , pb.MapActivitySimpleInfo() ..name= '极限挑战 战胜重力' ..difficulty= 2 ..distanceMinMeter= 8.1 ..closeDoorTime= 190.minutes.toPb() ..isUsed= true ..routeCount= 6 ..totalControlNum= 16 , ]) ; } @override Future activityDetail(int id) async { final simple = pb.MapActivitySimpleInfo() ..id= 1 ..name= '周末爬山健身运动' ..closeDoorTime= 90.minutes.toPb() ..distanceMinMeter= 71.3; return pb.ActivityDetailReply() ..baseInfoV2= simple ..content= ' 此主题为全民爬山健身定向运动,全民爬山健身运动是我国体育事业发展的重要组成部分,也是实现中华民族伟大复兴中国梦的重要内容。' ..routes.addAll([ pb.MapRoute() ..name='XL0002' ..distanceMeter= 3331 ..altitudeDiffMeter= 223 ..useCount= 0 , pb.MapRoute() ..name= 'XL0003' ..distanceMeter= 5430 ..altitudeDiffMeter= 123 ..useCount=2 , ]) ; } @override Future> mapRecommendList(MPosition? position)async{ info('mapRecommendList: $position'); return [ MapInfo() ..name= '英雄山风景区' ..isOpen=true ..isRecommend=true , MapInfo()..name= '板桥广场' ..isRecommend=true ]; } @override Future> mapList(MPosition? position, int offset, int limit)async{ info('mapList: $position'); final out = []; for(var i=0;i< limit;i++){ out.add(MapInfo() ..name ='$offset-${random.nextInt(10000)}公园' ); } return out; } @override Future getRegionCode() async{ return 'test'; } @override Future> getRegionList() async{ info('getRegionList'); return []; } } class StreamGuardianWatch{ StreamGuardianWatch( this.channel, this.stream ); ClientChannel channel; ResponseStream stream; } extension ExtInt64 on fixnum.Int64{ pb.IdRequest toIdRequest(){ return pb.IdRequest() ..id=this; } } extension ExtNum on num{ pb.IdRequest toIdRequest(){ return pb.IdRequest() ..id=fixnum.Int64(toInt()); } }