api.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. import 'dart:math';
  2. import 'dart:typed_data';
  3. import 'package:grpc/grpc.dart';
  4. import 'package:fixnum/fixnum.dart' as fixnum;
  5. import 'package:trackoffical_app/model.dart';
  6. import 'package:trackoffical_app/service/app.dart';
  7. import 'package:trackoffical_app/pb.dart' as pb;
  8. import 'package:trackoffical_app/service/service.dart';
  9. import 'package:trackoffical_app/utils.dart';
  10. import 'package:protobuf/protobuf.dart';
  11. import '../logger.dart';
  12. import '../global.dart';
  13. class _Stub{
  14. _Stub(
  15. this.channel,
  16. this.stub
  17. );
  18. ClientChannel channel;
  19. pb.ApiAppClient stub;
  20. }
  21. class ApiService extends IService {
  22. static ApiService get to => Get.find();
  23. ClientChannel? channel;
  24. String? get token {
  25. final out = App.to.userProfile.token.val;
  26. if (out.isEmpty) {
  27. return null;
  28. }
  29. return out;
  30. }
  31. set token(String? v) {
  32. App.to.userProfile.token.val = v ?? '';
  33. }
  34. String get _appVersion => App.to.appVersion;
  35. ClientChannel _newChannel(){
  36. return ClientChannel(
  37. GlobalVar.apiHost,
  38. port: GlobalVar.apiPort,
  39. options: const ChannelOptions(credentials: ChannelCredentials.secure()),
  40. );
  41. }
  42. pb.ApiAppClient _getStub({Duration? timeout, ClientChannel? channel}){
  43. if (this.channel == null) {
  44. throw Exception('$runtimeType 未初始化');
  45. }
  46. final metadata = <String, String>{
  47. 'source': "${pb.LoginSource.UserApp.value}"
  48. };
  49. metadata['version'] = _appVersion;
  50. if (token != null) {
  51. metadata['token'] = token!;
  52. }
  53. return pb.ApiAppClient(channel??this.channel!,
  54. options: CallOptions(
  55. metadata: metadata,
  56. timeout: timeout,
  57. ));
  58. }
  59. pb.ApiAppClient get stub {
  60. return _getStub(timeout: 10.seconds);
  61. }
  62. _Stub get _stubForStream {
  63. final ch = _newChannel();
  64. return _Stub(ch, _getStub(channel: ch)) ;
  65. }
  66. Future<void> syncTime()async{
  67. try{
  68. final serverNow = await serverTime();
  69. App.to.correctByServerNow(serverNow);
  70. info('服务器时间:${App.to.now}');
  71. }catch(e){
  72. warn("获取服务器时间失败: ", e);
  73. }
  74. }
  75. @override
  76. Future<ApiService> init() async {
  77. channel = _newChannel();
  78. syncTime();
  79. return this;
  80. }
  81. Future<bool> isSignIn() async {
  82. final token = this.token;
  83. if (token == null) {
  84. return false;
  85. }
  86. if (token.isNotEmpty) {
  87. try {
  88. await flushUserInfo();
  89. } catch (e) {
  90. return false;
  91. }
  92. return true;
  93. } else {
  94. return false;
  95. }
  96. }
  97. Future<void> getVfCode() async {
  98. await stub.getVfPic(pb.DefaultRequest());
  99. }
  100. Future<void> authSendCodeToPhone(String phone, pb.SmsType smsType)async{
  101. info('authSendCodeToPhone [$phone]');
  102. await stub.authSendCodeToPhone(pb.AuthSendCodeToPhoneRequest()
  103. ..phone= phone
  104. ..userType= pb.UserType.AppUser
  105. ..smsType= smsType
  106. );
  107. }
  108. Future<void> signUp(String phone, String code, String? name, pb.User_Sex sex)async{
  109. info('signUp [$phone]');
  110. var r = await stub.signUp(pb.SignUpRequest()
  111. ..phone= phone
  112. ..password= code
  113. ..nickname= name??''
  114. ..userType= pb.UserType.AppUser
  115. ..sex= sex
  116. );
  117. token = r.token;
  118. }
  119. Future<void> signIn(String name, String password) async {
  120. final r = await stub.signIn(pb.SignInRequest()
  121. ..name = name
  122. ..password = password
  123. );
  124. token = r.token;
  125. debug('sign in success: $token');
  126. }
  127. void signOut(){
  128. stub.signOut(pb.SignOutRequest());
  129. token = null;
  130. }
  131. Future<pb.GameData> gameStart(
  132. int actId,
  133. fixnum.Int64 courseId,
  134. ) async {
  135. await syncTime();
  136. final r = await stub.gameStart(pb.GameStartRequest()
  137. ..actId= fixnum.Int64(actId)
  138. ..courseId= courseId
  139. ..hrBand= pb.HrBandType.UseHrBand
  140. );
  141. return r.gameData;
  142. }
  143. Future<void> gameSaveUpload(pb.GameSaveUploadRequest save) async {
  144. info('gameSaveUpload');
  145. final save2 = save.deepCopy();
  146. save2.gameSave.gameHrInfos.clear();
  147. save2.gameSave.gameGpsInfos.clear();
  148. await stub.gameSaveUpload(save2);
  149. return;
  150. }
  151. Future<pb.ActivityListReply> activityList({
  152. MPosition? position
  153. }) async {
  154. info('call providerList $position');
  155. final req = pb.PositionRequest();
  156. if(position!= null){
  157. req.position=position.toPb();
  158. }
  159. final r = await stub.activityList(req);
  160. return r;
  161. }
  162. Future<pb.ActivityDetailReply> activityDetail(int id) async {
  163. debug('call activityDetail $id');
  164. final r =
  165. await stub.activityDetail(pb.IdRequest()..id= fixnum.Int64(id));
  166. return r;
  167. }
  168. Future<void> flushUserInfo()async{
  169. info('flushUserInfo');
  170. final r = await stub.myUserQuery(pb.DefaultRequest());
  171. final userProfile = App.to.userProfile;
  172. userProfile.username.value = r.name;
  173. userProfile.head.value = r.head.toModel();
  174. userProfile.sex = r.sex.toModel();
  175. userProfile.age.val = App.to.now.year - r.birthdayYear;
  176. userProfile.heightCm.val = r.heightMillimeter / 10;
  177. userProfile.weightKg.val = r.weightGram / 1000;
  178. userProfile.rhr.val = r.staticHr;
  179. }
  180. Future<void> _userInfoEdit(
  181. String nickName,
  182. Sex sex,
  183. int birthdayYear,
  184. double heightCm,
  185. double weightKg,
  186. int rhr,
  187. ){
  188. return stub.userInfoEdit(pb.UserInfoEditRequest()
  189. ..nickName= nickName
  190. ..sex= sex.toPb()
  191. ..birthdayYear= birthdayYear
  192. ..heightMillimeter= (heightCm * 10).toInt()
  193. ..weightGram= (weightKg*1000).toInt()
  194. ..staticHr = rhr
  195. );
  196. }
  197. Future<void> saveUserInfo(){
  198. App app = Get.find();
  199. final userProfile = app.userProfile;
  200. return _userInfoEdit(
  201. userProfile.username.value,
  202. userProfile.sex,
  203. app.now.year - userProfile.age.val,
  204. userProfile.heightCm.val,
  205. userProfile.weightKg.val,
  206. userProfile.rhr.val);
  207. }
  208. Future<pb.MyHistoryGameReply> gameHistory({
  209. required pb.GameState state,
  210. required int offset,
  211. required int limit,
  212. })async{
  213. return await stub.myHistoryGame(pb.MyHistoryGameRequest()
  214. ..state= state
  215. ..offset= offset
  216. ..limit=limit
  217. );
  218. }
  219. Future<void> gameHistoryDel(fixnum.Int64 id){
  220. return stub.historyGameDel(pb.IdRequest()..id= id);
  221. }
  222. Future<void> closeAccount()async{
  223. await stub.unsubscribe(pb.DefaultRequest());
  224. }
  225. Future<void> gameGpsUpload(
  226. fixnum.Int64 gameId,
  227. List<MPosition> data,
  228. int distanceMeter,
  229. int paceSecond,
  230. ){
  231. info('gameGpsUpload');
  232. List<pb.GameGpsInfo> l = data.map((p) => p.toPb2()).toList();
  233. final req = pb.GameGpsUploadRequest()
  234. ..gameId= gameId.toInt()
  235. ..distance= distanceMeter
  236. ..pace= paceSecond
  237. ..gameGpsInfos.addAll(l);
  238. return stub.gameGpsUpload(req);
  239. }
  240. Future<void> gameHrUpload(
  241. fixnum.Int64 gameId,
  242. List<pb.HeartRate> data,
  243. ){
  244. info('gameHrUpload');
  245. return stub.gameHrUpload(pb.GameHrUploadRequest()
  246. ..gameId= gameId.toInt()
  247. ..gameHrInfos.addAll(data)
  248. );
  249. }
  250. Future<void> gameExerciseStateUpload(
  251. fixnum.Int64 gameId,
  252. int avgHr,
  253. int maxHr,
  254. /// 单位卡,不是千卡
  255. int cle,
  256. double ck,
  257. double ei,
  258. int stepCount,
  259. // %
  260. int heartRatePercent
  261. ){
  262. info('gameExerciseStateUpload');
  263. return stub.gameCleUpload(pb.GameCleUploadRequest()
  264. ..gameId= gameId.toInt()
  265. ..avgHr= avgHr
  266. ..maxHr= maxHr
  267. ..cle= cle
  268. ..ck= ck.round()
  269. .. ei= ei
  270. ..stepNum= stepCount
  271. ..heartRatePercent= heartRatePercent
  272. );
  273. }
  274. @override
  275. void onClose() {
  276. channel?.shutdown();
  277. }
  278. Future<DateTime> serverTime() async {
  279. final begin = DateTime.now();
  280. final r = await stub.getServerTime(pb.DefaultRequest());
  281. final cost = DateTime.now().difference(begin);
  282. final serverNow = DateTime.fromMillisecondsSinceEpoch(
  283. r.millisecondStamp.toInt(),
  284. isUtc: true)
  285. .toLocal();
  286. return serverNow.add(cost);
  287. }
  288. Future<String> getRegionCode() async{
  289. final r =await stub.getRegion(pb.DefaultRequest());
  290. info('位置:${r.name}');
  291. return r.code;
  292. }
  293. Future<List<pb.Region>> getRegionList() async{
  294. info('getRegionList');
  295. final r =await stub.regionList(pb.RegionListRequest()..countryCode= "CN");
  296. final list = r.region;
  297. return list;
  298. }
  299. Future<Duration> getSmsSendLeftTime(String phone)async{
  300. final r = await stub.getSmsSendLeftTime(pb.GetSmsSendLeftTimeRequest()..phone= phone);
  301. info('getSmsSendLeftTime: $phone - ${r.second}s');
  302. return r.second.seconds;
  303. }
  304. Future<pb.GetUpdateVersionReply> getUpdateVersion(String version)async{
  305. info('getUpdateVersion: $version');
  306. final r = await stub.getUpdateVersion(pb.GetUpdateVersionRequest()..vCode= version);
  307. return r;
  308. }
  309. Future<pb.GameDetailReply> getGameHistoryDetail(fixnum.Int64 id)async{
  310. info('getGameHistoryDetail: $id');
  311. return await stub.historyGameDetail(id.toIdRequest());
  312. }
  313. Future<pb.GameDetailReply> gameFinish(fixnum.Int64 id, bool isDrop)async{
  314. info('gameFinish: $id , isDrop: $isDrop');
  315. return await stub.gameFinish(pb.GameFinishRequest()..gameId= id.toInt().. isDrop= isDrop);
  316. }
  317. Future<pb.GetInGameDataReply> getInGameData()async{
  318. info('getInGameData');
  319. return await stub.getInGameData(pb.DefaultRequest());
  320. }
  321. Future<List<pb.CareTakerInfo>> guardianList()async{
  322. info('guardianList');
  323. final r = await stub.myCareTakerQuery(pb.DefaultRequest());
  324. return r.list;
  325. }
  326. Future<List<pb.MyPupil>> underGuardianList()async{
  327. info('underGuardianList');
  328. final r = await stub.myPupilListQuery(pb.DefaultRequest());
  329. return r.list;
  330. }
  331. Future<void> guardianAdd(String phone, String memo)async{
  332. info('guardianAdd: $phone');
  333. await stub.myCareTakerAdd(pb.CareTakerInfo()
  334. ..cPhone= phone
  335. ..memo= memo
  336. );
  337. return;
  338. }
  339. Future<void> guardianDel(pb.CareTakerInfo v)async{
  340. info('guardianAdd: ${v.crId}');
  341. await stub.myCareTakerDel(v.crId.toIdRequest());
  342. return;
  343. }
  344. Future<List<MapInfo>> mapRecommendList(MPosition? position)async{
  345. info('mapRecommendList: $position');
  346. final req = pb.PositionRequest();
  347. if(position!= null){
  348. req.position=position.toPb();
  349. }
  350. final r = await stub.mapRecommendList(req);
  351. return r.mapList.map((e) => e.toModel()..isRecommend=true).toList();
  352. }
  353. Future<List<MapInfo>> mapList(MPosition? position, int offset, int limit)async{
  354. info('mapList: $position');
  355. final req = pb.MapListRequest()
  356. ..offset=offset
  357. ..limit=limit;
  358. if(position!= null){
  359. req.position=position.toPb();
  360. }
  361. final r = await stub.mapList(req);
  362. return r.mapList.map((e) => e.toModel()).toList();
  363. }
  364. StreamGuardianWatch guardianWatch(int id){
  365. info('guardianWatch: $id');
  366. final stub = _stubForStream;
  367. final stream = stub.stub.pupilInGameWatch(id.toIdRequest());
  368. return StreamGuardianWatch(stub.channel, stream);
  369. }
  370. Future<Bin> getBinByMd5(Uint8List md5, OnPercentage onPercentage)async{
  371. final stream = _getStub().getBinaryByMd5(pb.GetBinaryByMd5Request()..md5= md5);
  372. String ext='';
  373. Uint8List? data;
  374. var nonce=Uint8List(0);
  375. var i = 0;
  376. await for (var one in stream){
  377. if(data == null){
  378. data = Uint8List(one.allCount);
  379. ext = one.ext;
  380. nonce= Uint8List.fromList(one.nonce);
  381. }
  382. for(var b in one.data){
  383. data[i]=b;
  384. i++;
  385. }
  386. onPercentage(i, data.length);
  387. }
  388. await stream.cancel();
  389. return Bin()
  390. ..ext=ext
  391. ..data=data??Uint8List(0)
  392. ..nonce=nonce;
  393. }
  394. Future<void> userHeadEdit(String ext, Uint8List data)async{
  395. await stub.userHeadEdit(pb.Image()..ext= ext ..data= data);
  396. }
  397. Future<pb.MapActivityListReply> mapActivityList(int mapId, String pin)async{
  398. info('mapActivityList mapId: $mapId pin: $pin');
  399. final r = await stub.mapActivityList(pb.MapActivityListRequest()
  400. ..id= mapId
  401. ..pinCode= pin.trim()
  402. );
  403. return r;
  404. }
  405. Future<pb.MapActivityListReply> pinCodeActivityList(String pin)async{
  406. info('pinCodeActivityList pin: $pin');
  407. final r = await stub.pinCodeActivityList(pb.PinCodeActivityListRequest()
  408. .. pinCode= pin.trim()
  409. );
  410. return r;
  411. }
  412. }
  413. class Bin{
  414. var ext='';
  415. var data=Uint8List(0);
  416. var nonce=Uint8List(0);
  417. }
  418. typedef OnPercentage = void Function(int, int);
  419. class ApiServiceMock extends ApiService {
  420. @override
  421. Future<ApiService> init() async {
  422. debug('$runtimeType ready!');
  423. return this;
  424. }
  425. final random = Random();
  426. @override
  427. Future<void> signIn(String name, String password) async {}
  428. @override
  429. Future<pb.GameData> gameStart(
  430. int projectId, fixnum.Int64 mapRouteId) async {
  431. info('gameStart Mock');
  432. final data = pb.GameData();
  433. final p1 = pb.ControlPoint()
  434. ..id= fixnum.Int64(1)
  435. ..info = pb.ControlPointInfo();
  436. final p2 = pb.ControlPoint()..id= fixnum.Int64(2)
  437. ..info = pb.ControlPointInfo();
  438. p1.nfcIdList.add('0465be121c1291');
  439. p2.nfcIdList.add('0465bd121c1291');
  440. data.controlPointSortedList.addAll([p1, p2]);
  441. data.mapZip = pb.NetImage()
  442. ..url=
  443. 'https://p3.itc.cn/q_70/images03/20210317/94c7636527d042d8a7c9ed08529cc8b2.jpeg'
  444. ..md5= [1, 1, 1];
  445. data.maxDuration = pb.Duration()..seconds= fixnum.Int64(60) * 60;
  446. return data;
  447. }
  448. @override
  449. Future<void> gameSaveUpload(pb.GameSaveUploadRequest save) async {
  450. return;
  451. }
  452. @override
  453. void onReady() {}
  454. @override
  455. void onClose() {}
  456. @override
  457. Future<DateTime> serverTime() async {
  458. return DateTime.now();
  459. }
  460. @override
  461. Future<pb.MapActivityListReply> mapActivityList(int mapId, String pin)async{
  462. return pb.MapActivityListReply()
  463. ..list.addAll([
  464. pb.MapActivitySimpleInfo()
  465. ..name= '穿越荒野:勇闯野性之旅-穿越荒野:勇闯野性之旅'
  466. ..difficulty= 1
  467. ..distanceMinMeter= 217.1
  468. ..closeDoorTime= 190.minutes.toPb()
  469. ..isUsed= false
  470. ..routeCount= 28
  471. ..totalControlNum= 12
  472. ,
  473. pb.MapActivitySimpleInfo()
  474. ..name= '极限挑战 战胜重力'
  475. ..difficulty= 2
  476. ..distanceMinMeter= 8.1
  477. ..closeDoorTime= 190.minutes.toPb()
  478. ..isUsed= true
  479. ..routeCount= 6
  480. ..totalControlNum= 16
  481. ,
  482. ])
  483. ;
  484. }
  485. @override
  486. Future<pb.ActivityDetailReply> activityDetail(int id) async {
  487. final simple = pb.MapActivitySimpleInfo()
  488. ..id= 1
  489. ..name= '周末爬山健身运动'
  490. ..closeDoorTime= 90.minutes.toPb()
  491. ..distanceMinMeter= 71.3;
  492. return pb.ActivityDetailReply()
  493. ..baseInfoV2= simple
  494. ..content= ' 此主题为全民爬山健身定向运动,全民爬山健身运动是我国体育事业发展的重要组成部分,也是实现中华民族伟大复兴中国梦的重要内容。'
  495. ..routes.addAll([
  496. pb.MapRoute()
  497. ..name='XL0002'
  498. ..distanceMeter= 3331
  499. ..altitudeDiffMeter= 223
  500. ..useCount= 0
  501. ,
  502. pb.MapRoute()
  503. ..name= 'XL0003'
  504. ..distanceMeter= 5430
  505. ..altitudeDiffMeter= 123
  506. ..useCount=2
  507. ,
  508. ])
  509. ;
  510. }
  511. @override
  512. Future<List<MapInfo>> mapRecommendList(MPosition? position)async{
  513. info('mapRecommendList: $position');
  514. return [
  515. MapInfo()
  516. ..name= '英雄山风景区'
  517. ..isOpen=true
  518. ..isRecommend=true
  519. ,
  520. MapInfo()..name= '板桥广场'
  521. ..isRecommend=true
  522. ];
  523. }
  524. @override
  525. Future<List<MapInfo>> mapList(MPosition? position, int offset, int limit)async{
  526. info('mapList: $position');
  527. final out = <MapInfo>[];
  528. for(var i=0;i< limit;i++){
  529. out.add(MapInfo()
  530. ..name ='$offset-${random.nextInt(10000)}公园'
  531. );
  532. }
  533. return out;
  534. }
  535. @override
  536. Future<String> getRegionCode() async{
  537. return 'test';
  538. }
  539. @override
  540. Future<List<pb.Region>> getRegionList() async{
  541. info('getRegionList');
  542. return [];
  543. }
  544. }
  545. class StreamGuardianWatch{
  546. StreamGuardianWatch(
  547. this.channel,
  548. this.stream
  549. );
  550. ClientChannel channel;
  551. ResponseStream<pb.PupilInGameWatchReply> stream;
  552. }
  553. extension ExtInt64 on fixnum.Int64{
  554. pb.IdRequest toIdRequest(){
  555. return pb.IdRequest()
  556. ..id=this;
  557. }
  558. }
  559. extension ExtNum on num{
  560. pb.IdRequest toIdRequest(){
  561. return pb.IdRequest()
  562. ..id=fixnum.Int64(toInt());
  563. }
  564. }