import 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'package:f_cache/manager.dart'; 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.ApiToAppClient 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() ChannelCredentials.insecure() ), ); } pb.ApiToAppClient _getStub({Duration? timeout, ClientChannel? channel}){ if (this.channel == null) { throw Exception('$runtimeType 未初始化'); } final metadata = { 'source': "${pb.LoginSource.ToApp.value}" }; metadata['version'] = _appVersion; if (token != null) { metadata['token'] = token!; } info("token: $token"); return pb.ApiToAppClient(channel??this.channel!, options: CallOptions( metadata: metadata, timeout: timeout, )); } pb.ApiToAppClient 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.toSendCodeToPhoneV2(pb.ToSendCodeToPhoneRequestV2() ..phone= phone ..smsType= smsType ); } // 场控端_登录 Future signIn(String userCode, String password, String ip) async { final r = await stub.toSignInV2(pb.ToSignInRequestV2() ..userCode = userCode ..password = password ..ip = ip ); token = r.token; debug('sign in success: $token'); } // 场控端_登出 void signOut(){ stub.toSignOutV2(pb.DefaultRequest()); token = null; } @override void onClose() { channel?.shutdown(); } // 获取系统时间 Future serverTime() async { final begin = DateTime.now(); final r = await stub.toGetServerTime(pb.DefaultRequest()); final cost = DateTime.now().difference(begin); final serverNow = DateTime.fromMillisecondsSinceEpoch( r.millisecondStamp.toInt(), isUtc: true) .toLocal(); return serverNow.add(cost); } Future getSmsSendLeftTime(String phone)async{ final r = await stub.toGetSmsSendLeftTimeV2(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> mapList(MPosition? position, int offset, int limit) async{ info('mapList: $position'); final req = pb.MapListRequestV2() ..offset = offset ..limit = limit; if (position != null){ req.position = position.toPb(); } final r = await stub.toMapListV2(req); return r.list.map((e) => e.toModel()).toList(); } // 场控端_地图自身信息 Future mapDetail(int mapId, String pin)async{ info('mapDetail mapId: $mapId pin: $pin'); final r = await stub.toMapDetailV2(pb.IdRequest() ..id = fixnum.Int64(mapId) ); return r; } // 场控端_地图内正在进行中的活动人员详情 Future userDetailQuery(int mapId, bool isFullQuery)async{ info('userDetailQuery mapId: $mapId isFullQuery: $isFullQuery'); final r = await stub.toUserDetailQueryV2(pb.ToUserDetailQueryRequestV2() // ..mapId = fixnum.Int64(mapId) ..mapId = mapId ..isFullQuery = isFullQuery ); return r; } // Future getBinByMd5(Uint8List md5, OnPercentage onPercentage) async{ // final stream = _getStub().toGetBinaryByMd5(pb.ToGetBinaryByMd5Request()..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; // return Bin(); // } Future getBinReaderByMd5(Uint8List md5) async { final stream = _getStub().toGetBinaryByMd5(pb.ToGetBinaryByMd5Request()..md5=md5); final controller = StreamController>(); controller.onCancel = (){ stream.cancel(); }; Future rcv()async{ try{ await for(final one in stream){ controller.add(one.data); } }finally{ controller.close(); stream.cancel(); } } rcv(); stream.headers.then((value) => debug(value)); final headers = await stream.headers; final lenStr = headers['all-length']!; final length = int.parse(lenStr); final nonce = headers['nonce']!; final ext = headers['ext']!; return BinReader(Reader( controller.stream, length)) ..ext=ext ..nonce=nonce ; } } // class Bin{ // var ext=''; // var data = Uint8List(0); // var nonce = Uint8List(0); // } class BinReader{ BinReader(this.reader); var ext=''; Reader reader; var nonce=''; } // 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 userCode, String password, String ip) async {} @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() ..id=0 ..name= '穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅' ..difficulty= 1 ..distanceMinMeter= 217.1 ..closeDoorTime= 190.minutes.toPb() ..isUsed= false ..routeCount= 28 ..totalControlNum= 12 , pb.MapActivitySimpleInfo() ..id=1 ..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; } } /* 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()); } }