From 1765403f212f6e25d2e4408e36dff505c0ea4599 Mon Sep 17 00:00:00 2001 From: wyf <494641114@qq.com> Date: Wed, 16 Apr 2025 14:27:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3574 -> 2908 bytes .../src/main/res/mipmap-ldpi/ic_launcher.png | Bin 1904 -> 1646 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2644 -> 1989 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4924 -> 3725 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7751 -> 5411 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 10835 -> 7131 bytes assets/img/google.png | Bin 0 -> 1344 bytes assets/img/help_op.png | Bin 0 -> 6053 bytes assets/img/icon/add.svg | 1 + assets/img/icon/arrow_left.svg | 1 + assets/img/icon/bluetooth.svg | 1 + assets/img/icon/close.svg | 1 + assets/img/icon/query.svg | 1 + assets/img/icon/scan.svg | 1 + assets/img/icon/sound.svg | 1 + assets/img/icon/tick.svg | 1 + assets/img/man.png | Bin 0 -> 2017 bytes assets/img/mye.png | Bin 0 -> 4482 bytes assets/img/netlove.png | Bin 0 -> 3761 bytes assets/img/tel.png | Bin 0 -> 892 bytes assets/img/wechat.png | Bin 0 -> 1087 bytes assets/img/woman.png | Bin 0 -> 1927 bytes assets/langs/zh_CN.json | 53 +- lib/common/color/appConstants.dart | 1 + lib/common/util/Ble.dart | 1100 +++++++++++++++++ .../home_page/SleepDataModuleWidget.dart | 145 +++ lib/component/home_page/SleepDateWidget.dart | 136 ++ lib/component/tool/ClickableContainer.dart | 45 + lib/component/tool/CustomCard.dart | 2 - lib/component/tool/FrostedDialog.dart | 37 + .../device/blueteeth_bind_controller.dart | 41 +- lib/controller/login/login_controller.dart | 175 +++ lib/controller/login/login_controller.g.dart | 33 + .../main_bottom/global_controller.dart | 54 +- lib/controller/person/person_controller.dart | 33 + .../person/person_controller.g.dart | 19 + lib/controller/time/countdown_controller.dart | 33 + lib/controller/user_info_controller.dart | 20 +- lib/controller/user_info_controller.g.dart | 4 +- lib/main.dart | 11 + lib/model/BleDeviceData.dart | 35 + lib/pages/common/selectDialog.dart | 773 ++++++++++++ .../device_bind/bind_device_success.dart | 320 +++++ .../device_bind/blueteeth_device_page.dart | 1059 ++++++++++++++++ .../DoubleBlueteethDeviceCompoentWidget.dart | 294 +++++ .../componnet/FancyCircleCheckbox.dart | 86 ++ .../SingleBlueteethDeviceCompoentWidget.dart | 184 +++ .../device_bind/componnet/bind_dialog.dart | 324 +++++ lib/pages/device_bind/device_type.dart | 433 +++---- lib/pages/device_bind/wifi_page.dart | 502 ++++++++ lib/pages/login/login.dart | 100 +- lib/pages/login/other_login.dart | 801 ++++++++++++ lib/pages/main_bottom/home_page.dart | 387 +++++- .../main_bottom/main_page_bottom_change.dart | 91 +- lib/pages/person/person_page.dart | 619 ++++++++++ lib/pages/person/select_time.dart | 290 +++++ lib/routers/routers.dart | 5 + pubspec.yaml | 1 + 58 files changed, 7821 insertions(+), 433 deletions(-) create mode 100644 assets/img/google.png create mode 100644 assets/img/help_op.png create mode 100644 assets/img/icon/add.svg create mode 100644 assets/img/icon/arrow_left.svg create mode 100644 assets/img/icon/bluetooth.svg create mode 100644 assets/img/icon/close.svg create mode 100644 assets/img/icon/query.svg create mode 100644 assets/img/icon/scan.svg create mode 100644 assets/img/icon/sound.svg create mode 100644 assets/img/icon/tick.svg create mode 100644 assets/img/man.png create mode 100644 assets/img/mye.png create mode 100644 assets/img/netlove.png create mode 100644 assets/img/tel.png create mode 100644 assets/img/wechat.png create mode 100644 assets/img/woman.png create mode 100644 lib/common/util/Ble.dart create mode 100644 lib/component/home_page/SleepDataModuleWidget.dart create mode 100644 lib/component/home_page/SleepDateWidget.dart create mode 100644 lib/component/tool/ClickableContainer.dart create mode 100644 lib/component/tool/FrostedDialog.dart create mode 100644 lib/controller/login/login_controller.dart create mode 100644 lib/controller/login/login_controller.g.dart create mode 100644 lib/controller/person/person_controller.dart create mode 100644 lib/controller/person/person_controller.g.dart create mode 100644 lib/controller/time/countdown_controller.dart create mode 100644 lib/model/BleDeviceData.dart create mode 100644 lib/pages/common/selectDialog.dart create mode 100644 lib/pages/device_bind/bind_device_success.dart create mode 100644 lib/pages/device_bind/blueteeth_device_page.dart create mode 100644 lib/pages/device_bind/componnet/DoubleBlueteethDeviceCompoentWidget.dart create mode 100644 lib/pages/device_bind/componnet/FancyCircleCheckbox.dart create mode 100644 lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart create mode 100644 lib/pages/device_bind/componnet/bind_dialog.dart create mode 100644 lib/pages/device_bind/wifi_page.dart create mode 100644 lib/pages/login/other_login.dart create mode 100644 lib/pages/person/person_page.dart create mode 100644 lib/pages/person/select_time.dart diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 17f82651c826329ba9a16dcb97a53f2246519202..7769da390226f0dea131ef494e1674741e8144b0 100644 GIT binary patch literal 2908 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 mC_#w*olks-{Vxs?1o;CV`NiI%0c@uL0000 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 U>`-%I>i_@%07*qoM6N<$f=Xx{Gynhq literal 2644 zcmV-a3aj;rP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 7751 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 I`0eNa0hBCD)Bpeg diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index d31b2cbbf6200134134d0801b4d5ca8758666fd1..3475f3f187e95b4c9ef5f0c561c6709b0c1e64ee 100644 GIT binary patch literal 7131 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 aXB^h)$kB6h6@&l!b6Pj`)T&hNqW=ffT#q0C literal 10835 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 diff --git a/assets/img/google.png b/assets/img/google.png new file mode 100644 index 0000000000000000000000000000000000000000..eecdf848c82e3e5e55b10562dcea5405f4f46aa2 GIT binary patch literal 1344 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资源 275 \ No newline at end of file diff --git a/assets/img/icon/arrow_left.svg b/assets/img/icon/arrow_left.svg new file mode 100644 index 0000000..0e389d4 --- /dev/null +++ b/assets/img/icon/arrow_left.svg @@ -0,0 +1 @@ +资源 261 \ No newline at end of file diff --git a/assets/img/icon/bluetooth.svg b/assets/img/icon/bluetooth.svg new file mode 100644 index 0000000..49752b7 --- /dev/null +++ b/assets/img/icon/bluetooth.svg @@ -0,0 +1 @@ +资源 262 \ No newline at end of file diff --git a/assets/img/icon/close.svg b/assets/img/icon/close.svg new file mode 100644 index 0000000..ee25aa2 --- /dev/null +++ b/assets/img/icon/close.svg @@ -0,0 +1 @@ +资源 231 \ No newline at end of file diff --git a/assets/img/icon/query.svg b/assets/img/icon/query.svg new file mode 100644 index 0000000..90aa229 --- /dev/null +++ b/assets/img/icon/query.svg @@ -0,0 +1 @@ +资源 233 \ No newline at end of file diff --git a/assets/img/icon/scan.svg b/assets/img/icon/scan.svg new file mode 100644 index 0000000..6e38491 --- /dev/null +++ b/assets/img/icon/scan.svg @@ -0,0 +1 @@ +资源 263 \ No newline at end of file diff --git a/assets/img/icon/sound.svg b/assets/img/icon/sound.svg new file mode 100644 index 0000000..fcec23f --- /dev/null +++ b/assets/img/icon/sound.svg @@ -0,0 +1 @@ +资源 234 \ No newline at end of file diff --git a/assets/img/icon/tick.svg b/assets/img/icon/tick.svg new file mode 100644 index 0000000..dbd8ee1 --- /dev/null +++ b/assets/img/icon/tick.svg @@ -0,0 +1 @@ +资源 235 \ No newline at end of file diff --git a/assets/img/man.png b/assets/img/man.png new file mode 100644 index 0000000000000000000000000000000000000000..606e5bb50e1808abe3e83f6008d4870e94a0bc0d GIT binary patch literal 2017 zcmXYx2Ut^C7RM7nDN+STiZlfgTp@HNA^}kZ1Oau%f)YnWKv%-bvNDk|D>@^D5{eEj z5Ks)ojM4dk!B7H$0HK&rLP7+BR3U)~Llj|U59_|~-uvFY-+AZ!|G)FTmmC!6qoHP~ zhCm=Rd{20X!2ROpR8fR=|Ey{i0-?YSI(g=p5AGzgrP8#!0NGN3Xs$%_(=B@Q)Y{6F zS}ME*DUSVFE(4^a!W8@dtYgAth}3B>^%kc5jr$nYm%W8m?lF{#8A^5Ir)#&DDzeIt z3W<6h6iB7g`dowk41(eH!~8TaArVd>UY}EB)I~PyE`VCFHSfuXT1N#b>TS;;(xE^5 z;9$n~j#5Yq1J3n2N)8QXAUA`lHryu$oFZ633A_$@jJwFNg=ZLW2*bHQQ=47tIzY1M z$=lIE0Xwid^liGZi-H0K`Q73%YYiN7;=hs_v9n5a-abO0Eoh7_leL)AO>B4jxVjR0)fuuX@OkQzBF^-ZE5US z3tukn`fKW?R9HAWm@wWnvAXQqPj=!JWlr}zneD2Qh-`XGJqOArSC$i9)E*AWP%W4_*=XCnVyEnErH^VCDgK59Qe2P50O+7u)U%+`g4%Zdy%eH(+p zn4eTo)zVSQrZXnOWPdo~af~i$0c~~a^FuopBhNKv+MF|PI@hL|)QpWZxR5WeWsZ+k zc6N-QYho_oC6k%DnVCFvt;Bd_fE%)6irVUA&^W`bNL0XYd|)y7&a_ZWPbxm(a{W(# zpz%FR2A90_ILO9+DpEdRt>L%tQ2=4F`;eaeg>Y(e?rY=nOz`mS_TPvkb0d-6ISZCa z6aA*n1-Cz^x97?Gu365`B6at6)c%lDc89>q0y;aTix?S_E5hU!!& z_uw>lUj@6xL&0RJiQQrXhL%VV8I618**+-pshW&ZF5M#0ToG-Mnfqz3sVY z?e3Le`g~MGnBFh-3zkyJ-O=?&p(S*Wc83aI7eV9U-0PpHbW=ykqv~dt7@A)F*5UM> z>a1}};vlQdcFVBlwuxOCCimql?~-@cBZX0QPQ;}6g;qSqB_krXqwzzi&0q|&h7h5@ zY}IkARroU@v6wr3X8mkd@t9)#k>2061ojWQn8hylFCx)4D*Zcd`HN1WB058f(i5UR zHU;53JPbwe1+79}O}Jr!QkfohF@c#ou}gGYLcTa^xp&yyNVzRe`89dfqWzaoy*=gS zsQ8K5@LErmG+t+f;^_BQXpT_{<0m047(F8K>+o@9JXA;PQXi{FGf>fi*Qxb!(_Xii zZn&X6Y`m{hTR(=pSgDSWzki<8ly`?F$?(b}e+dhaD2EoB-aB*LJFdll3I4&wki!(t6fw z_kDBc?!G(sS$_2J-=~vuF6ESd-D~8Sem(nS?#a{l{Kt$IblzoN6`gRgZz`*ErC_WJ z{9k+M9$a($*THcA>BT}~{$K9WWMP8L!qTzo#sF23yOUF4?k~4F?Zg zGP6?n(r%m03*zdH0z(MgS|nuG&YvY~j%xBX%$?KUoqxZXRtoyS}>i>z#yu o?mAZ;{bzH~p`3_Dm6DAInhsgJ@rNtnFN5$s7U=!E7xw;t0Y|AB5C8xG literal 0 HcmV?d00001 diff --git a/assets/img/mye.png b/assets/img/mye.png new file mode 100644 index 0000000000000000000000000000000000000000..8987727e2e92227fe8bcfd0acabe76760919afe3 GIT binary patch literal 4482 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 MP|;Q{R5XA4KbP#WrT_o{ literal 0 HcmV?d00001 diff --git a/assets/img/netlove.png b/assets/img/netlove.png new file mode 100644 index 0000000000000000000000000000000000000000..6d259d18ab30654f418d788cefa332ea64b3ebb0 GIT binary patch literal 3761 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 FV1hzr8$bX6 literal 0 HcmV?d00001 diff --git a/assets/img/woman.png b/assets/img/woman.png new file mode 100644 index 0000000000000000000000000000000000000000..bd7ba9c639a9f368ad193a94fdd286be50f2a9c0 GIT binary patch literal 1927 zcmX|B4LFl)AD==|j<4+`^3ex!oDfQ#&}iNwuQMOjQPOfKy(|;G-kLf(M247UJ~#7C zlr>uVQ1g{(%-ZbB#E5XUharb(7r)l$8o-1()11Ob@qG`tnJ|u(S5DXYVFQyq$ESL zqeiX04kaZx3UV#`3yw(%=Do$hxnGdG^=&0M+SOO2*In)=&hn6C7_iGcB^eqWwIFN< zhhp7d0IF^2s?zJOI3&ylkq&$k=mHGRrkVDZ>=%$gF9UY@k3F<~@AKS5SqBG*I&UjM zsRIMVqvA{uiRhv2>?`sRXB-x00}p@!TKp3aFq^!i*zodg`pMuK7-;7yA-Ib(4-I4^ z#Tf?##7!MFTe~X3k^Os;%RtV-fgDf->;UApo>Jp?R1*$mZ+|`rFyoYf*PnP~U~6k@ zcJCj6FhCZh1J5tQ`lm%S&^GSy6GB~>o5CjMv`MCz&W%}La zPJ&!#0TJk<_qM{Qr}SrD9)P6#wqg&r@P~B|)s~kB_*!xcthfaL-u8D?0K=MFV9F^0 zTmTE}{`?)B678-kKp!Xx=JJ2cK)QAM{bY;YVpmbt&fX%RFyITOqtQ_VL;xHCS)hY@ zd+o;dI-nsC7Dx&tQfsR_CLw^a0^&UjpXR?Uv*$l?7AD4*7ep%Pl!SR+#yG&I1`jqB zPYL5j+FS$_TW)#1VlYAerh9&JZ(rpJQDe+-$HUJuIwZL;MBmRV{m*cVT|e!Qu^#HQ zK%!DiEYFiBd;cXWZ8eg8jH_;g&X8taFb$RGT_5{~Dn_#~F)y4<-1;Ub8dPx`uJw1$)m6U`@QYw{6 zNlD5iCCl1@X@ z9Y7{-y#_8P2aR*KD*|n_a3_M}V6ZI)>r0LQ;r2HeOx?yClyGQ7Rh?(Zl*a_#=d zHKd^dm0FTG+Q4>?`~Icv*2Uxy)0~=A>jodJaQJCh#BcV6=T%cpR%-M!jgaj8&B+61 zrXp-5-KAl6Fu2@|e_3$J2hS0`4sbLyd2*{*9pBCU{vmSuj#I+P=B0RrpJi0FrIinJ zX{OYdxVZD9E}N0B&b&Ltqg6OUmZoY$5z|^yRnJF=-F!@Cvti~M*8?A1|Gz2AG$?|k z5UjpJ(A}Wh=w}5J+LHW@kZ`$Wf*?6_Q^I!_!x@b#xZw3LK}sD-`Qdx@ zpQ?Qxc`tRU85iJhDj5|GQB@pM-edd)mV9q??FL9jX?WoVV|)&EBG)<~nIit2T|y6Y z&Tw}o!-sLJpFd~BSXCf&w~Zr|M-iuUp}ld$A?rcc)uwM=K3BOM19vD7wc>HIu7!jR z^c*gTQkpNV1Ty-v6GzfeD~FHgof9T#iRX(WFBfXo=t{ZWyiIMA8-(IvB395a=GQzD zHu=;alh0^*LX3>_j>V&z--Lb2i0q9sZL}H;Zs|Nug3k6P{wUR)iOPjwDQFW$UzP0A zxNLjPIZKoN<^`MR3ikIC7o+sA;eDA$G1bB0Yh~;NvaPX(WW<4F&lvF^@D>>j zn9)mPlj$cgXJWBOVMkz3)f59~1@b41lUrf~b#8==d8$FrhAa{zoMl!;SAtl6YhC`) zhhr8<(QY@)#ufTBN(iTMtIkB%A0}UgWvQi{UT*){bdm8Ymb8yy+)?%77xGU~OP z=JLZ!FLzavAKevG2EL3Z7MFF<7in2yihj=O5c!gkW;12AzeX5ksqAYZG+u@X(7hzw*>Iu*LPv z?F4C4r?V$fTRN7Bdph>$9&}~NETjwn@01WY{amZ!)SOwca3$u{ETn%eR^F;c(>FHQ zBS@t>;v#p2OIN2ug|6WeN?mvg?@PJ?_Ui}sPn!SD!6a1SeVx(>v-wt(cJl7Hn`|7* z>=QdKK6*%b8oJno=cFwU+lRtqe$Nh(aEP1YJ-(>21=Hop$;lOqe`(xyUiI3%x`|{n W(dep!eF**}FmEqE)MHP~pML|cq5L8M literal 0 HcmV?d00001 diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json index 3d00d81..257469f 100644 --- a/assets/langs/zh_CN.json +++ b/assets/langs/zh_CN.json @@ -14,7 +14,10 @@ "提示内容3": "3.若使用扫一扫功能,请对摄像头进行授权。", "扫一扫绑定": "扫一扫添加新设备", "蓝牙绑定": "蓝牙搜附近的设备", - "已关联体征监测设备": "已关联体征监测设备" + "已关联体征监测设备": "已关联体征监测设备", + "我的e护": "我的e护", + "云关爱": "云关爱", + "报告详情": "报告详情" }, "我的": { "个人信息": "个人信息", @@ -45,7 +48,30 @@ "信号":"最小信号强度", "搜索提示":"检索设备", "搜索":"搜索", - "匹配":"匹配出的外围设备" + "匹配":"匹配出的外围设备", + "信号强度":"信号强度", + "SN":"SN", + "蓝牙地址":"蓝牙地址", + "mac":"mac", + "网络":"网络", + "在线":"在线", + "离线":"离线", + "版本":"版本", + "默认设备名称":"未知设备", + "传感器":"传感器", + "可绑定":"可绑定", + "已被绑定":"已被绑定", + "双人版绑定标题":"该设备为双人版,请选择", + "绑定全部":"绑定全部", + "主设备":"主设备:", + "从设备":"从设备:", + "确定":"确定", + "取消":"取消", + "无法绑定":"无法绑定!", + "无法绑定1":"检测到该设备", + "无法绑定2":"已被绑定", + "无法绑定3":",绑定前请先进行解绑,有疑问请联系客服", + "知道了":"知道了" }, "登录页":{ "欢迎使用太和e护":"欢迎使用太和e护", @@ -76,6 +102,29 @@ "分享内容":"设备绑定成功后,如需对朋友或家人共享我的睡眠情况,可以进行立即分享,分享成功后,对方即可享受查看该设备权限,可以收到该设备的睡眠报告。", "立即分享":"立即分享", "返回":"返回首页 开启体验" + }, + "日期":{ + "取消":"取消", + "确定":"确定", + "年":"年", + "月":"月", + "日":"日" + }, + "wifi页":{ + "标题":"WIFI配置", + "跳过":"跳过", + "WLAN":"WLAN", + "未连接":"未连接", + "已连接":"已连接", + "可用WLAN":"可用WLAN", + "刷新":"刷新" + }, + "其他手机登录页":{ + "输入内容":"输入手机号码/邮箱", + "输入验证码":"输入验证码", + "获取验证码":"获取验证码", + "登录":"登录" + } } \ No newline at end of file diff --git a/lib/common/color/appConstants.dart b/lib/common/color/appConstants.dart index 49142ed..5cd8c8c 100644 --- a/lib/common/color/appConstants.dart +++ b/lib/common/color/appConstants.dart @@ -19,6 +19,7 @@ class AppConstants { double text_padding_up_dowm_p = 5.rpx; //段落文字上下间距 + double small_text_fontSize = 20.rpx; //普通文字字号 double normal_text_fontSize = 26.rpx; //普通文字字号 double title_text_fontSize = 30.rpx; //标题文字字号 } diff --git a/lib/common/util/Ble.dart b/lib/common/util/Ble.dart new file mode 100644 index 0000000..a1c02c4 --- /dev/null +++ b/lib/common/util/Ble.dart @@ -0,0 +1,1100 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:ef/ef.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; + +String findInput = ""; +double rssichange = -90; +Function? findCall; + +int mcuMax = 500; + +String myuuid = "00000001-0000-1000-8000-00805F9B34FB"; + +int closeTime = 20000; + +Map devices = Map(); + +StreamSubscription? subscription_adapterState; + +Map connectList = + Map(); + +bool isBleStart = false; + +StreamSubscription>? onScanResultsListen; + +StreamSubscription? + streamSubscription_onConnectionStateChangedEvent; + +Timer? showToastTimer; + +List permissionInfo = [ + ["位置权限说明", "获得位置信息,连接附近的蓝牙设备与推荐附近门店"], + ["蓝牙权限说明", "搜索链接附近的蓝牙设备"], + ["附近设备权限说明", "搜索链接附近的蓝牙设备"] +]; + +bool isQuanShiDevice(name) { + return "$name".contains("S4-ZM-M94-4") || "$name".contains("S4-ZM-N94-4") || "$name".contains("MHT-SWES-D"); +} + +bool isMHTSWES(name) { + return "$name".contains("MHT-SWES-H") || "$name".contains("MHT-SWES-M") || "$name".contains("MHT-SWES-S"); +} + +bleParse() { + findCall = null; + print("bleParse 执行了"); + showToastTimer?.cancel(); + if (FlutterBluePlus.isScanningNow) { + FlutterBluePlus.stopScan(); + } +} + +start(Function fun, {Function? bleOnCall}) async { + if (isBleStart) { + var isOk = await requestBluetoothPermission(); + if (isOk == true) { + findCall = fun; + bleOnCall?.call(); + bleOnCall = null; + } + print("ble start again"); + return; + } + print("ble start"); + FlutterBluePlus.setLogLevel(LogLevel.info, color: false); + streamSubscription_onConnectionStateChangedEvent?.cancel(); + streamSubscription_onConnectionStateChangedEvent = + FlutterBluePlus.events.onConnectionStateChanged.listen((event) { + print('${event.device} ${event.connectionState}'); + }); + if (await FlutterBluePlus.isSupported == false) { + print("Bluetooth not supported by this device"); + return; + } + + // handle bluetooth on & off + // note: for iOS the initial state is typically BluetoothAdapterState.unknown + // note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized + subscription_adapterState?.cancel(); + int callIndex = 0; + subscription_adapterState = + FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) async { + print(state); + print("蓝牙状态 $state"); + if (state == BluetoothAdapterState.on) { + showToastTimer?.cancel(); + // usually start scanning, connecting, etc + findCall = fun; + var isOk = await requestBluetoothPermission(); + if (isOk == true) { + bleOnCall?.call(); + bleOnCall = null; + } + } else { + // show an error to the user, etc + if (Platform.isIOS && + callIndex == 0 && + state == BluetoothAdapterState.unknown) { + callIndex++; + return; + } + if (Platform.isAndroid) { + showToast("请打开蓝牙开关"); + } + if (Platform.isIOS) { + showToast("请打开蓝牙开关且开启蓝牙权限"); + await showCustomConfirmAndCancelDialog( + Get.context!, "请在“设置-蓝牙”中打开蓝牙开关或者在“设置-APP”中找到对应APP开启蓝牙权限", + confirmName: "去设置") + .then((msg) async { + if (msg == "confirm") { + openAppSettings(); + } + }); + } + isBleStart = false; + showToastTimer?.cancel(); + showToastTimer = Timer.periodic(const Duration(seconds: 5), (t) { + if (t.tick > 3) { + t.cancel(); + } + if (Platform.isAndroid) { + showToast("请打开蓝牙开关"); + } + if (Platform.isIOS) { + showToast("请打开蓝牙开关且开启蓝牙权限"); + } + }); + } + callIndex++; + }); + + // turn on bluetooth ourself if we can + // for iOS, the user controls bluetooth enable/disable + if (Platform.isAndroid && + FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) { + showPermissionInfoDialog(Get.context!, permissionInfo); + FlutterBluePlus.turnOn().then((e) { + Get.back(); + }).catchError((e) { + Get.back(); + }); + } + + var timer = null; + onScanResultsListen?.cancel(); + onScanResultsListen = FlutterBluePlus.onScanResults.listen( + (List results) { + // print(results.length); + for (ScanResult result in results) { + // if (result.device.id.toString().contains("A3:76")) { + // print("$result"); + // } + Map d = { + "updateTime": DateTime.now().millisecondsSinceEpoch, + "name": result.device.advName, + "id": result.device.remoteId.str, + "rssi": result.rssi, + "device": result.device, + "connectable": result.advertisementData.connectable + }; + Map> m_d = result.advertisementData.manufacturerData; + m_d.keys.toList().forEach((v) { + if (v == 65517 && m_d[65517]?.length != 0) { + List a = [0, 0, ...?m_d[65517]]; + advertisDataFormatter(a, d); + } else if (v == 11125 && m_d[11125]?.length == 8) { + List a = [...?m_d[11125]]; + d['adData'] = {'deviceId': ab2str(a.sublist(2, 8)).toUpperCase()}; + } else if (m_d[v]?.length == 8 && isQuanShiDevice(d["name"])) { + List a = [...?m_d[v]]; + d['adData'] = {'deviceId': ab2str(a.sublist(2, 8)).toUpperCase()}; + } else if (m_d[v]?.length == 4 && isMHTSWES(d["name"])) { + ByteData bd = ByteData(2); + bd.setUint16(0, v, Endian.little); + List a = [bd.getUint8(0), bd.getUint8(1), ...?m_d[v]]; + d['adData'] = {'deviceId': ab2str(a).toUpperCase()}; + } else if (m_d[v]?.length == 6 && isMHTSWES(d["name"])) { + List a = [...?m_d[v]]; + d['adData'] = {'deviceId': ab2str(a).toUpperCase()}; + } + }); + devices[d['id']] = d; + // print('Device found: ${result.device.name}, ${result.device.id}'); + if (timer == null) { + timer = 1; + timer = Future.delayed(const Duration(microseconds: 300), () { + timer = null; + find(); + }); + } + } + }, + onError: (e) => print(e), + ); +} + +// Future locationCheck({bool isGetLocation = true}) async { +// // 先查看定位服务是否开启 +// bool b = await Geolocator.isLocationServiceEnabled(); +// if (b == false) { +// if (Platform.isAndroid) { +// showToast("请开启系统位置开关", closeTime: 5); +// await showCustomConfirmAndCancelDialog(Get.context!, "请开启系统位置开关", +// confirmName: "去设置") +// .then((msg) async { +// if (msg == "confirm") { +// // await Geolocator.openLocationSettings(); +// await openGeolocatorLocationSettingsAndWait(); +// b = await Geolocator.isLocationServiceEnabled(); +// } +// }); +// } +// if (Platform.isIOS) { +// showToast("请开启系统定位服务与定位权限", closeTime: 5); +// await showCustomConfirmAndCancelDialog(Get.context!, +// "请在“设置-隐私与安全性-定位服务”中开启定位服务开关或者在“设置-APP”中找到对应APP开启定位服务权限", +// confirmName: "去设置") +// .then((msg) async { +// if (msg == "confirm") { +// // await Geolocator.openAppSettings(); +// await openGeolocatorAppSettingsAndWait(); +// b = await Geolocator.isLocationServiceEnabled(); +// } +// }); +// } +// } + +// var permission = await Geolocator.checkPermission(); +// if (permission == LocationPermission.denied || +// permission == LocationPermission.deniedForever) { +// showPermissionInfoDialog(Get.context!, [permissionInfo[0]]); +// permission = await Geolocator.requestPermission().catchError((e) { +// Get.back(); +// }); +// Get.back(); +// if (permission == LocationPermission.deniedForever) { +// if (Platform.isAndroid) { +// await showCustomConfirmAndCancelDialog(Get.context!, "请开启位置权限(打开精确位置)", +// confirmName: "去设置") +// .then((msg) async { +// if (msg == "confirm") { +// // await Geolocator.openAppSettings(); +// await openGeolocatorAppSettingsAndWait(); +// } +// }); +// } +// if (Platform.isIOS) { +// await showCustomConfirmAndCancelDialog(Get.context!, +// "请在“设置-隐私与安全性-定位服务”中开启定位服务开关或者在“设置-APP”中找到对应APP开启定位服务权限", +// confirmName: "去设置") +// .then((msg) async { +// if (msg == "confirm") { +// // await Geolocator.openAppSettings(); +// await openGeolocatorAppSettingsAndWait(); +// } +// }); +// } +// } +// } + +// Position? position; +// if (isGetLocation) { +// if (b && +// permission != LocationPermission.denied && +// permission != LocationPermission.deniedForever) { +// try { +// position = await Geolocator.getCurrentPosition( +// locationSettings: +// const LocationSettings(timeLimit: Duration(seconds: 5))); +// print("$position"); +// } catch (e) { +// print("error $e"); +// } +// } +// } +// return position; +// } + +/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态, +/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回 +/// ------开始----- +Future openGeolocatorAppSettingsAndWait() async { + final geolocatorAppSettingsLifecycleState = + GeolocatorAppSettingsLifecycleEventHandler(); + + WidgetsBinding.instance.addObserver(geolocatorAppSettingsLifecycleState); + + // await Geolocator.openAppSettings(); + + await geolocatorAppSettingsLifecycleState.waitForResume(); + + WidgetsBinding.instance.removeObserver(geolocatorAppSettingsLifecycleState); + + print('AppSettings have been opened and user has returned'); +} + +class GeolocatorAppSettingsLifecycleEventHandler + extends WidgetsBindingObserver { + final Completer _completer = Completer(); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed && !_completer.isCompleted) { + _completer.complete(); + } + } + + Future waitForResume() => _completer.future; +} + +/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态, +/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回 +/// ------结束----- + +/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态, +/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回 +/// ------开始----- +Future openGeolocatorLocationSettingsAndWait() async { + final geolocatorLocationSettingsLifecycleState = + GeolocatorLocationSettingsLifecycleEventHandler(); + + WidgetsBinding.instance.addObserver(geolocatorLocationSettingsLifecycleState); + + // await Geolocator.openLocationSettings(); + + await geolocatorLocationSettingsLifecycleState.waitForResume(); + + WidgetsBinding.instance + .removeObserver(geolocatorLocationSettingsLifecycleState); + + print('LocationSettings have been opened and user has returned'); +} + +class GeolocatorLocationSettingsLifecycleEventHandler + extends WidgetsBindingObserver { + final Completer _completer = Completer(); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed && !_completer.isCompleted) { + _completer.complete(); + } + } + + Future waitForResume() => _completer.future; +} + +/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态, +/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回 +/// ------结束----- + +Future requestBluetoothPermission() async { + if (Platform.isIOS) { + PermissionStatus isBleGranted = await Permission.bluetooth.request(); + print('checkBlePermissions-ios, isBleGranted=$isBleGranted'); + if (isBleGranted.isGranted) { + startBluetoothScanning(); + return true; + } else { + showToast("蓝牙开关或蓝牙权限未开启,请开启蓝牙开关与蓝牙权限", closeTime: 7); + await showCustomConfirmAndCancelDialog( + Get.context!, "请“设置-蓝牙”中打开蓝牙开关或者在“设置-APP”中找到对应APP开启蓝牙权限", + confirmName: "去设置") + .then((msg) async { + if (msg == "confirm") { + openAppSettings(); + } + }); + return false; + } + } else if (Platform.isAndroid) { + // 检查蓝牙扫描权限 + String error = ""; + bool isShowDialog = false; + if (!await Permission.bluetoothScan.isGranted) { + if (!isShowDialog) { + isShowDialog = true; + showPermissionInfoDialog(Get.context!, permissionInfo); + } + PermissionStatus status = await Permission.bluetoothScan.request(); + if (!status.isGranted) { + error += "蓝牙扫描权限未开启,请开启附近设备权限"; + } + print("蓝牙扫描 $status"); + } + + // 检查蓝牙连接权限 + if (!await Permission.bluetoothConnect.isGranted) { + if (!isShowDialog) { + isShowDialog = true; + showPermissionInfoDialog(Get.context!, permissionInfo); + } + PermissionStatus status = await Permission.bluetoothConnect.request(); + if (!status.isGranted) { + if (error.isNotEmpty) { + error += "\n"; + } + error += "蓝牙连接权限未开启,请开启蓝牙权限"; + } + print("蓝牙连接 $status"); + } + + // 检查位置权限 + if (!await Permission.location.isGranted) { + if (!isShowDialog) { + isShowDialog = true; + showPermissionInfoDialog(Get.context!, permissionInfo); + } + //检查 + PermissionStatus status = await Permission.location.request(); + print("位置权限 $status"); + if (!status.isGranted) { + await showCustomConfirmAndCancelDialog(Get.context!, "请开启位置权限(打开精确位置)", + confirmName: "去设置") + .then((msg) async { + if (msg == "confirm") { + await openGeolocatorAppSettingsAndWait(); + // await Future.delayed(const Duration(seconds: 2)); + print('Proceeding with other operations'); + status = await Permission.location.request(); + } + }); + } + if (!status.isGranted) { + if (error.isNotEmpty) { + error += "\n"; + } + error += "位置权限未开启,请开启位置权限"; + } + } + + if (isShowDialog) { + Get.back(); + } + + if (await Permission.bluetoothScan.isGranted && + await Permission.bluetoothConnect.isGranted && + await Permission.location.isGranted) { + // bool b = await Geolocator.isLocationServiceEnabled(); + bool b =false; + if (b == false) { + await showCustomConfirmAndCancelDialog(Get.context!, "请开启系统位置开关", + confirmName: "去设置") + .then((msg) async { + if (msg == "confirm") { + // bool isOpen = await Geolocator.openLocationSettings(); + await openGeolocatorLocationSettingsAndWait(); + // await Future.delayed(const Duration(seconds: 2)); + print('Proceeding with other operations'); + // b = await Geolocator.isLocationServiceEnabled(); + } + }); + } + if (b) { + isBleStart = true; + startBluetoothScanning(); + return true; + } else { + showToast("系统位置开关未开启,请开启系统位置开关", closeTime: 7); + return false; + } + } else { + Timer(Duration.zero, () async { + showToast(error, closeTime: 7); + }); + return false; + } + } else { + showToast("当前系统不支持蓝牙,无法使用此功能", closeTime: 7); + return false; + } +} + +void find() { + int len = devices.length; + String reg = findInput.toLowerCase().replaceAll(RegExp("[::]"), ""); + List list = devices.values.toList(); + for (int i = 0; i < len; i++) { + Map d = list[i]; + bool flag = d['rssi'] >= rssichange; + if (flag) { + bool a = d['name'].toString().toLowerCase().contains(reg); + a = a || + d['id'] + .toString() + .toLowerCase() + .replaceAll(RegExp("[::]"), "") + .contains(reg); + // if(d.adData && d.adData.deviceId) { + // a = a || d.adData.deviceId.toLowerCase().replace(/[::]/g, "").indexOf(reg) > -1 + // } + // if(d.adData && d.adData.version && reg) { + // a = a || (d.adData.version + "").indexOf(reg) > -1 + // } + flag = flag && a; + } + if (flag) { + flag = flag && + DateTime.now().millisecondsSinceEpoch - d['updateTime'] < closeTime; + } + if (!flag) { + d['isClose'] = true; + } else { + d['isClose'] = false; + } + } + var result = list.where((item) => item['isClose'] == false).toList(); + if (result == null) { + findCall?.call([]); + } else { + // print(result); + result.sort((a, b) { + // print("${a['rssi']},${b['rssi']}"); + return b['rssi'] - a['rssi']; + }); + if (result.length > 0) { + findCall?.call(result.where((d) => d?['adData'] != null).toList()); + } else { + findCall?.call(result); + } + } +} + +String ab2str(List buffer) { + return buffer.map((x) => x.toRadixString(16).padLeft(2, '0')).join(''); +} + +void advertisDataFormatter(var a, item) { + Map obj = {}; + try { + if (a[2] == 1) { + obj['sn'] = a[3]; + obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase(); + obj['b'] = a[10]; + obj['h'] = a[11]; + obj['t'] = a[12]; + item['adData'] = obj; + } else if (a[2] == 2) { + obj['sn'] = a[3]; + obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase(); + obj['b'] = a[10]; + obj['h'] = a[11]; + obj['t'] = a[12]; + obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线'; + obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常'; + ByteData byteData = ByteData.sublistView( + Uint8List.fromList(a.sublist(14, 18).reversed.toList())); + obj['version'] = byteData.getUint32(0); + item['adData'] = obj; + } else if (a[2] == 3) { + List otherstr = []; + obj['sn'] = a[3]; + obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase(); + obj['b'] = a[10]; + obj['h'] = a[11]; + obj['t'] = a[12]; + obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线'; + obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常'; + + if ((a[13] & 4) == 4) { + otherstr.add('呼吸暂停'); + } + + if ((a[13] & 8) == 8 && (a[13] & 1) == 1) { + obj['isbed'] = '在床'; + } else { + obj['isbed'] = '离床'; + } + + if ((a[13] & 16) == 16) { + otherstr.add('授权过期'); + } + + if ((a[13] & 64) == 64) { + otherstr.add('设备休眠'); + } + + obj['other'] = otherstr.join('、'); + + ByteData byteData = ByteData.sublistView( + Uint8List.fromList(a.sublist(14, 18).reversed.toList())); + obj['version'] = byteData.getUint32(0); + + ByteData qsnData = + ByteData.sublistView(Uint8List.fromList(a.sublist(17, 19))); + obj['qsn'] = qsnData.getUint16(0) * 256 + obj['sn']; + + item['adData'] = obj; + } else if (a.length > 17) { + obj['sn'] = a[3]; + obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase(); + obj['b'] = a[10]; + obj['h'] = a[11]; + obj['t'] = a[12]; + obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线'; + obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常'; + + ByteData byteData = ByteData.sublistView( + Uint8List.fromList(a.sublist(14, 18).reversed.toList())); + obj['version'] = byteData.getUint32(0); + + item['adData'] = obj; + } + } catch (e) { + print(e); + } +} + +void startBluetoothScanning() async { + // 开始扫描附近的蓝牙设备 + if (FlutterBluePlus.isScanningNow) { + await FlutterBluePlus.stopScan(); + } + FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); +} + +getOneConnectedDeviceProp(id) { + return connectList[id]; +} + +void setOther(device, connectedDeviceProp, fun) async { + try { + List services = await device.discoverServices(); + print(services); + bool isNotify = false; + bool isWrite = false; + for (var service in services) { + if (connectedDeviceProp.connectedDevicePropType == + ConnectedDevicePropType.JunHe) { + if (service.uuid.str128.toUpperCase() != myuuid) { + continue; + } + } + // print("serviece $service"); + for (BluetoothCharacteristic element in service.characteristics) { + if (isNotify == false && element.properties.notify) { + await element.setNotifyValue(true); + print("setNotifyValue 完成"); + connectedDeviceProp.createLisetenReceive(element); + isNotify = true; + if (connectedDeviceProp.connectedDevicePropType == + ConnectedDevicePropType.JunHe) { + continue; + } + if (connectedDeviceProp.connectedDevicePropType == + ConnectedDevicePropType.MHT) { + continue; + } + } + if (isWrite == false && element.properties.write) { + connectedDeviceProp?.writeCharacteristic = element; + isWrite = true; + print("$element"); + } + } + } + if (!isWrite || !isNotify) { + if (connectedDeviceProp != null) { + disconnect(connectedDeviceProp!); + } + print("service 订阅失败 isWrite $isWrite isNotify $isNotify"); + fun['fail']?.call("service 订阅失败 isWrite $isWrite isNotify $isNotify"); + return; + } + print("service 注册完成"); + connectList[connectedDeviceProp.id] = connectedDeviceProp; + connectedDeviceProp.createListenState(); + if (connectedDeviceProp.connectedDevicePropType == + ConnectedDevicePropType.JunHe) { + connectedDeviceProp.heartbeat(); + } + print("回调成功"); + fun['success']?.call(connectedDeviceProp); + } catch (e) { + print("连接失败 执行失败回调 错误: $e"); + if (connectedDeviceProp != null) { + disconnect(connectedDeviceProp!); + } + print("连接失败 执行失败回调"); + fun['fail']?.call(e); + } +} + +// 连接设备 +void connectToDevice(fun) async { + BluetoothDevice device = fun['device']; + ConnectedDeviceProp? connectedDeviceProp = + getOneConnectedDeviceProp(device.remoteId.str); + if (connectedDeviceProp != null) { + disconnect(connectedDeviceProp); + Future.delayed(const Duration(seconds: 1), () { + connectToDevice(fun); + }); + return; + } + try { + print("connecting"); + await device.connect(timeout: const Duration(seconds: 8)); + print("device.connect success"); + ConnectedDevicePropType connectedDevicePropType = + ConnectedDevicePropType.JunHe; + if (isQuanShiDevice(device.advName)) { + connectedDevicePropType = ConnectedDevicePropType.QuanShi; + } else if (isMHTSWES(device.advName)) { + connectedDevicePropType = ConnectedDevicePropType.MHT; + } + connectedDeviceProp = ConnectedDeviceProp( + connectDevice: device, + fun: fun, + connectedDevicePropType: connectedDevicePropType); + if (Platform.isAndroid) { + await device.requestMtu(mcuMax); + } + Timer(const Duration(milliseconds: 1000), () { + setOther(device, connectedDeviceProp, fun); + }); + } catch (e) { + print("连接失败 执行失败回调 错误: $e"); + if (connectedDeviceProp != null) { + disconnect(connectedDeviceProp!); + } + print("连接失败 执行失败回调"); + fun['fail']?.call(e); + } +} + +void disconnect(ConnectedDeviceProp connectedDeviceProp) { + connectedDeviceProp.closeHeartBeat(); + connectList.remove(connectedDeviceProp.id); + connectedDeviceProp.closeConnectedDeviceProp(); +} + +void closeAll() { + findCall = null; + connectList.values.toList().forEach((element) { + disconnect(element); + }); +} + +enum ConnectedDevicePropType { JunHe, QuanShi, MHT } + +class ConnectedDeviceProp { + ConnectedDevicePropType connectedDevicePropType; + Timer? heartbeatTimer = null; + int _seq = 0; + var connectDevice; + var writeCharacteristic; + var listenState; + StreamSubscription>? lisetenReceive; + Map fun; + List receiveMethods = []; + List logList = []; + Function? logChange; + ConnectedDeviceProp( + {required this.connectDevice, + required Map this.fun, + this.connectedDevicePropType = ConnectedDevicePropType.JunHe}); + List receiveLogArr = []; + int deviceType = 2; + int encodeType = 2; + List sendArr = []; + double sendExecAverage = 100; + bool isClose = false; + + String get id { + return connectDevice.remoteId.str; + } + + int sum_ab(dv) { + ByteData sum = ByteData(1); + for (int i = 0; i < dv.buffer.lengthInBytes; i++) { + sum.setUint8(0, dv.getUint8(i) + sum.getUint8(0)); + } + return sum.getUint8(0); + } + + void heartbeat() { + closeHeartBeat(); + heartbeatTimer = Timer.periodic(const Duration(seconds: 8), (timer) { + ByteData dv = ByteData(4); + dv.setUint8(0, 4); + dv.setUint8(2, seq); + dv.setUint8(3, 5); + dv.setUint8(1, sum_ab(dv)); + writeBle(dv); + }); + } + + closeHeartBeat() { + if (heartbeatTimer != null) { + heartbeatTimer!.cancel(); + heartbeatTimer = null; + } + } + + ByteData str2ab_oneByte(String str, {int startLength = 0}) { + Uint8List utf8str = utf8.encode(str); + int len = utf8str.length + startLength; + ByteData buf2 = ByteData.sublistView(utf8str); + ByteData buf = ByteData(len); + for (int i = startLength; i < len; i++) { + buf.setUint8(i, buf2.getUint8(i - startLength)); + } + return buf; + } + + void write3OfString(sendDate, {Function? success, Function? fail}) { + ByteData dv = str2ab_oneByte(sendDate, startLength: 4); + int len = dv.buffer.lengthInBytes; + dv.setUint8(0, len); + dv.setUint8(2, seq); + dv.setUint8(3, 8 * 16 + 3); + dv.setUint8(1, sum_ab(dv)); + writeBle(dv, success: success, fail: fail); + } + + void writeBle(ByteData d, {Function? success, Function? fail}) { + Uint8List d_ = Uint8List.view(d.buffer); + if (sendArr.length == 0) { + write(d_, success, fail); + } + sendArr.insert(0, {"d": d_, "success": success, "fail": fail}); + } + + void write(Uint8List d, Function? success, Function? fail, {int exec = 100}) { + if (writeCharacteristic != null) { + // try { + // if (d[3] == 8 * 16 + 3) { + // print( + // "blewrite s = $sendExecAverage d = ${utf8.decode(d.sublist(4))}"); + // } else { + // print("ble last write d = ${d[3]}"); + // } + // } catch (e) { + // print("write logprint error $e"); + // } + writeCharacteristic.write(d, withoutResponse: true).then((e) { + // print("write success $e"); + if (connectedDevicePropType != ConnectedDevicePropType.JunHe) { + print("发送 $d"); + } + if (exec > 95) { + sendExecAverage = sendExecAverage + 0.5; + } + if (sendExecAverage > 99) { + sendExecAverage = 99; + } + if (sendArr.length > 0) { + sendArr.removeLast(); + Map last = sendArr.last; + write(last["d"], last["success"], last["fail"]); + } + success?.call(); + }).catchError((e) { + // print("exec = $exec , $e"); + if (exec < 0) { + print("$e"); + fail?.call(); + } + if (exec > -1 && isClose == false) { + int time = ((100.0 - sendExecAverage) * 5.0).toInt(); + if (exec < 80) { + time = (100 - exec) * 5; + sendExecAverage = exec * 1.0; + } else { + sendExecAverage = sendExecAverage - (100 - exec) * 0.1; + } + Timer(Duration(milliseconds: time), () { + write(d, success, fail, exec: exec - 1); + }); + } + }); + } + } + + void read6() { + ByteData dv = ByteData(4); + dv.setUint8(0, 4); + dv.setUint8(2, seq); + dv.setUint8(3, 6); + dv.setUint8(1, sum_ab(dv)); + writeBle(dv); + } + + addLog(String log) { + if (logList.length > 500) { + logList.removeRange(0, 50); + } + DateTime date = DateTime.now(); + String h = date.hour > 10 ? "${date.hour}" : "0${date.hour}"; + String m = date.minute > 10 ? "${date.minute}" : "0${date.minute}"; + String s = date.second > 10 ? "${date.second}" : "0${date.second}"; + logList.add({"time": "$h:$m:$s", "value": log}); + print("ble $id log: $log"); + if (logChange != null) { + logChange?.call(logList, log); + } + } + + createListenState() { + listenState = connectDevice.connectionState.listen((state) { + print('ble Device state $id $state'); + if (state == BluetoothConnectionState.disconnected) { + print('ble Device state $id disconnected'); + isClose = true; + disconnect(this); + fun['stateChange']?.call(state, this); + } + }); + } + + createLisetenReceive(BluetoothCharacteristic element) { + lisetenReceive = element.onValueReceived.listen((List value) { + if (connectedDevicePropType == ConnectedDevicePropType.JunHe) { + if (value.isEmpty) { + return; + } + bool isOk = sumCheck(value); + if (isOk) { + receiveMethods.forEach((m) { + m?.call(); + }); + yewuSwitch(value[3], value.sublist(4)); + } + } else { + print("onValueReceived $value"); + receiveLogArr.forEach((m) { + m(value); + }); + } + }); + } + + closeConnectedDeviceProp() { + isClose = true; + if (listenState != null) { + listenState?.cancel(); + } + if (lisetenReceive != null) { + lisetenReceive?.cancel(); + } + connectDevice?.disconnect(); + } + + int get seq { + int r = _seq % 256; + _seq++; + return r; + } + + bool sumCheck(List ab) { + ByteData dv = ByteData.sublistView(Uint8List.fromList(ab)); + + if (dv.getUint8(0) != ab.length) { + print("和校验失败:长度不对"); + return false; + } //和校验失败 + + if (sumList(ab) != dv.getUint8(1)) { + print("和校验失败: 校验失败"); + return false; + } + + return true; + } + + int sumList(List ab) { + ByteData dv = ByteData.sublistView(Uint8List.fromList(ab)); + ByteData sum = ByteData.sublistView(Uint8List(1)); + + sum.setUint8(0, dv.getUint8(0)); + + for (int i = 2; i < ab.length; i++) { + sum.setUint8(0, dv.getUint8(i) + sum.getUint8(0)); + } + + return sum.getUint8(0); + } + + List endLogValue = []; + Timer? endLogTimer; + + void yewuSwitch(int yewu, List abData) { + switch (yewu) { + case 7: + String error = ab2StrByType(abData); + print(error); + break; + + case 131: + List? logData; + if (abData.last != 10) { + int index = abData.lastIndexOf(10); + if (index == -1) { + index = abData.length; + endLogValue = [...endLogValue, ...abData.sublist(0, index)]; + } else { + logData = [...endLogValue, ...abData.sublist(0, index)]; + endLogValue = abData.sublist(index); + } + // if (index == -1) { + // index = abData.length; + // } + // if(endLogValue.isNotEmpty) { + // logData = [...endLogValue, ...abData.sublist(0, index)]; + // endLogValue = []; + // } else { + // logData = [...abData.sublist(0, index)]; + // } + // if(index != abData.length) { + // endLogValue = abData.sublist(index); + // } + } else { + int index = abData.length; + + if (endLogValue.isNotEmpty) { + logData = [...endLogValue, ...abData]; + } else { + logData = abData; + } + + endLogValue = []; + } + + if (endLogTimer != null) { + endLogTimer!.cancel(); + endLogTimer = null; + } + + if (endLogValue != null && endLogValue!.isNotEmpty) { + endLogTimer = Timer(Duration(milliseconds: 400), () { + String log = ab2StrByType(endLogValue!); + endLogValue = []; + addLog(log); + try { + receiveLogArr.forEach((m) { + m(log); + }); + } catch (e) { + print(e); + } + }); + } + + if (logData != null && logData.isNotEmpty) { + if (logData.length != 1 || logData[0] != 13) { + String log = ab2StrByType(logData!); + addLog(log); + try { + receiveLogArr.forEach((m) { + m(log); + }); + } catch (e) { + print(e); + } + } + } + // 处理逻辑 + break; + + case 132: + ByteData dv = ByteData.sublistView(Uint8List.fromList(abData)); + + for (int i = 0; i < abData.length;) { + int len = dv.getUint8(i); + yewuSwitch(dv.getUint8(i + 1), abData.sublist(i + 2, i + 1 + len)); + i = i + 1 + len; + } + + break; + + default: + break; + } + } + + String ab2StrByType(List abData) { + // Implement your logic for converting abData to String + String str = ""; + if (abData.isNotEmpty) { + try { + str = utf8.decode(abData); + } catch (e) { + str = "解析错误"; + } + } + return str; + } +} diff --git a/lib/component/home_page/SleepDataModuleWidget.dart b/lib/component/home_page/SleepDataModuleWidget.dart new file mode 100644 index 0000000..abaee63 --- /dev/null +++ b/lib/component/home_page/SleepDataModuleWidget.dart @@ -0,0 +1,145 @@ +import 'package:ef/base/widget/flutterflow/FlutterFlowTheme.dart'; +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; + +class SleepDataModuleWidget extends StatefulWidget { + const SleepDataModuleWidget({super.key}); + + @override + State createState() => _SleepDataModuleWidgetState(); +} + +class _SleepDataModuleWidgetState extends State { + @override + void setState(VoidCallback callback) { + super.setState(callback); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ThemeController themeController = Get.find(); + return Container( + width: MediaQuery.sizeOf(context).width * 0.27, + constraints: BoxConstraints( + minWidth: 200.rpx, + ), + decoration: BoxDecoration( + color: stringToColor("#313541"), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(18.rpx, 18.rpx, 18.rpx, 22.rpx), + child: Container( + decoration: BoxDecoration(), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '离床次数', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + // SizedBox( + // height: 21.rpx, + // ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '4', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 40.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 0, 0.rpx, 10.rpx), + child: Text( + '次', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: AppConstants().small_text_fontSize, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + ], + ), + Container( + width: MediaQuery.sizeOf(context).width * 0.07, + height: MediaQuery.sizeOf(context).height * 0.014, + constraints: BoxConstraints( + minWidth: 43.rpx, + minHeight: 36.rpx, + ), + decoration: BoxDecoration(), + child: FFButtonWidget( + onPressed: () { + print('Button pressed ...'); + }, + text: '异常', + options: FFButtonOptions( + height: 40.rpx, + padding: + EdgeInsetsDirectional.fromSTEB(0.rpx, 0, 0.rpx, 0), + color: stringToColor("#FF7159"), + textStyle: + FlutterFlowTheme.of(context).titleSmall.override( + fontFamily: 'Inter Tight', + color: themeController.currentColor.sc3, + letterSpacing: 0.0, + fontSize: 15.rpx, + ), + elevation: 0, + borderRadius: BorderRadius.circular(8.rpx), + ), + ), + ), + ], + ), + Text( + '正常值:0~2', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: AppConstants().small_text_fontSize, + letterSpacing: 0.0, + color: Colors.grey), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/component/home_page/SleepDateWidget.dart b/lib/component/home_page/SleepDateWidget.dart new file mode 100644 index 0000000..38e2e14 --- /dev/null +++ b/lib/component/home_page/SleepDateWidget.dart @@ -0,0 +1,136 @@ +import 'package:ef/base/widget/flutterflow/FlutterFlowTheme.dart'; +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; + +class SleepDateWidget extends StatefulWidget { + const SleepDateWidget({super.key}); + + @override + State createState() => _SleepDateWidgetState(); +} + +class _SleepDateWidgetState extends State { + @override + Widget build(BuildContext context) { + ThemeController themeController = Get.find(); + return ClickableContainer( + backgroundColor: Colors.transparent, // 原 BoxDecoration 为空 + highlightColor: + themeController.currentColor.sc3.withOpacity(0.1), // 自定义点击波纹颜色 + borderRadius: AppConstants().normal_container_radius, // 原来没设置圆角 + padding: EdgeInsets.zero, + onTap: () { + print("今日评分卡片点击"); + }, + child: Container( + width: MediaQuery.sizeOf(context).width * 0.19, + constraints: BoxConstraints( + minWidth: 143.rpx, + ), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(10.rpx, 25.rpx, 10.rpx, 22.rpx), + child: Container( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 14.rpx), + child: Text( + '今日', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: AppConstants().title_text_fontSize, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 33.rpx), + child: Text( + '07/15', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 20.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 16.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '70', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 48.rpx, + letterSpacing: 0.0, + color: stringToColor("#00C1AA")), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 16.rpx, 0, 0.rpx), + child: Text( + '分', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: AppConstants().small_text_fontSize, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + ], + ), + ), + Container( + width: 0.2.rpx, + height: 2.4.rpx, + constraints: BoxConstraints( + minWidth: 123.rpx, + minHeight: 47.rpx, + ), + child: FFButtonWidget( + onPressed: () { + print('合格按钮点击'); + }, + text: '合格', + options: FFButtonOptions( + height: 40.rpx, + padding: + EdgeInsetsDirectional.fromSTEB(16.rpx, 0, 16.rpx, 0), + iconPadding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0), + color: stringToColor("#00C1AA"), + textStyle: + FlutterFlowTheme.of(context).titleSmall.override( + fontFamily: 'Inter Tight', + color: Colors.white, + letterSpacing: 0.0, + ), + elevation: 0, + borderRadius: BorderRadius.circular(50.rpx), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/component/tool/ClickableContainer.dart b/lib/component/tool/ClickableContainer.dart new file mode 100644 index 0000000..ac5f1f4 --- /dev/null +++ b/lib/component/tool/ClickableContainer.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class ClickableContainer extends StatelessWidget { + final Color backgroundColor; // 容器背景色 + final Color highlightColor; // 点击时背景色 + final EdgeInsetsGeometry padding; // 内部间距 + final VoidCallback onTap; // 点击回调 + final Widget child; // 子组件 + final double borderRadius; // 容器圆角(可选,默认为0) + + const ClickableContainer({ + Key? key, + required this.backgroundColor, + required this.highlightColor, + required this.padding, + required this.onTap, + required this.child, + this.borderRadius = 0, // 默认无圆角 + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Material( + color: Colors.transparent, + child: Ink( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: InkWell( + borderRadius: BorderRadius.circular(borderRadius), + onTap: onTap, + splashColor: highlightColor.withOpacity(0.3), // 点击时的波纹效果 + child: Padding( + padding: padding, + child: child, // 内容自适应 + ), + ), + ), + ), + ); + } +} diff --git a/lib/component/tool/CustomCard.dart b/lib/component/tool/CustomCard.dart index a5b4118..1c6975d 100644 --- a/lib/component/tool/CustomCard.dart +++ b/lib/component/tool/CustomCard.dart @@ -5,7 +5,6 @@ class CustomCard extends StatefulWidget { final VoidCallback onTap; // 点击回调 final List colors; // 背景颜色列表 final Widget child; // 子组件 - final String title; // 标题 final bool enableAnimation; // 是否启用动画效果 final bool enableGradient; // 是否启用渐变 @@ -15,7 +14,6 @@ class CustomCard extends StatefulWidget { required this.onTap, required this.colors, required this.child, - required this.title, this.enableAnimation = true, // 默认启用动画效果 this.enableGradient = true, // 默认启用渐变效果 }) : super(key: key); diff --git a/lib/component/tool/FrostedDialog.dart b/lib/component/tool/FrostedDialog.dart new file mode 100644 index 0000000..d77971b --- /dev/null +++ b/lib/component/tool/FrostedDialog.dart @@ -0,0 +1,37 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class FrostedDialog extends StatelessWidget { + final Widget child; + final double blurSigma; + final Color barrierColor; + + const FrostedDialog({ + super.key, + required this.child, + this.blurSigma = 5.0, + this.barrierColor = const Color.fromRGBO(0, 0, 0, 0.5), + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + // 毛玻璃背景 + BackdropFilter( + filter: ImageFilter.blur(sigmaX: blurSigma, sigmaY: blurSigma), + child: Container( + color: Colors.transparent, + ), + ), + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + backgroundColor: Colors.transparent, // 背景透明,由 child 自己决定 + child: child, + ), + ], + ); + } +} diff --git a/lib/controller/device/blueteeth_bind_controller.dart b/lib/controller/device/blueteeth_bind_controller.dart index adf8380..573fdce 100644 --- a/lib/controller/device/blueteeth_bind_controller.dart +++ b/lib/controller/device/blueteeth_bind_controller.dart @@ -5,7 +5,24 @@ part 'blueteeth_bind_controller.g.dart'; // 由json_serializable自动生成的 @JsonSerializable() class BlueteethBindModel { - int read = 1;//是否不再提示教程 0 不再提示 1 需要提示 + int? read = 1; //是否不再提示教程 0 不再提示 1 需要提示 + double? singal = -70; //扫描信号强度 + + List? devicelist = []; //蓝牙扫描到的设备数据列表 + List? blelist = []; //蓝牙扫描到的设备数据列表 + List? wifiList = []; + + List bindArr = ["", "", ""]; + + String connectedWifiName = ""; + + int connectedRssi = 0; + + String deviceName = ""; + + bool? deviceIndex0 = true; + bool? deviceIndex1 = false; + bool? deviceIndex2 = false; BlueteethBindModel(); @@ -27,5 +44,23 @@ class BlueteethBindModel { class BlueteethBindController extends GetControllerEx { BlueteethBindController() { attr = GetModel(BlueteethBindModel()).obs; - } -} \ No newline at end of file + } + + void updateDeviceStatus() { + // try { + + // } catch (e) { + // print(e); + // EasyDartModule.logger.info("向后端请求设备绑定状态报错了:$e"); + // } finally { + // EasyDartModule.logger.info("向后端请求设备绑定状态"); + // } + } + + Future bindDevice(d) async { + print("绑定参数:$d"); + await Future.delayed(Duration(seconds: 1)); + // return ApiService.request + // .post("/api/device/info/bind", data: formdata.FormData.fromMap(d)); + } +} diff --git a/lib/controller/login/login_controller.dart b/lib/controller/login/login_controller.dart new file mode 100644 index 0000000..410168d --- /dev/null +++ b/lib/controller/login/login_controller.dart @@ -0,0 +1,175 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +part 'login_controller.g.dart'; + +@JsonSerializable() +class LoginModel { + //版本id + int? loginStyle = 1; //1.密码登录 2.短信登录 + String? account = '17649984946'; //账户 + String? password = 'wyf123,.'; //密码 + + String? phone; //手机号 + String? code; //验证码 + + String? register_code; + + bool? showPd = true; + + int? forceLogin = 0; + + bool? isIos; //是否为ios设备 + + bool? isWeChatNotInstalled; //是否安装微信 + + bool? register_agree = false; //是否同意协议 + + LoginModel(); + + static LoginModel fromJson(Map json) => + _$LoginModelFromJson(json); + Map toJson() => _$LoginModelToJson(this); +} + +class LoginController extends GetControllerEx { + // 初始化实例 + // final Fluwx fluwx = Fluwx(); + // 微信监听返回值 + // FluwxCancelable? fluwxCancelable; + + // final UserRepository repository = UserRepository(); + LoginController() { + attr = GetModel(LoginModel()).obs; + } + + //登录 + Future login(BuildContext context) async { + // return ''; + String message = ''; + String account = ''; + String password = ''; + // if (model.loginStyle == 1) { + // //账号登录 + // if (model.account == null || model.account!.isEmpty) { + // message = '账户不能为空'; + // showToast(message); + // return message; + // } + // if (model.password == null || model.password!.isEmpty) { + // message = '密码不能为空'; + // showToast(message); + // return message; + // } + // account = model.account!; + // password = model.password!; + // } + // if (model.loginStyle == 2) { + // //账号登录 + // if (model.phone == null || model.phone!.isEmpty) { + // message = '手机号不能为空'; + + // showToast(message); + // return message; + // } + // if (!MyUtils.isValidPhoneNumber(model.phone!)) { + // message = '请输入正确的手机号'; + + // showToast(message); + // return message; + // } + // if (model.code == null || model.code!.isEmpty) { + // message = '验证码不能为空'; + + // showToast(message); + // return message; + // } + // account = model.phone!; + // password = model.code!; + // } + // RegisterController registerController = Get.find(); + // if (registerController.model.register_agree == null || + // registerController.model.register_agree != true) { + // message = "需要同意协议"; + // showToast(message); + // return message; + // } + // message = await repository.login( + // model.loginStyle!, account, password, model.forceLogin); + // model.forceLogin = 0; + + return message; + } + + Future getCode(BuildContext context) async { + String message = ""; + if (model.register_agree == null || model.register_agree != true) { + // message = "需要同意协议"; + // showToast(message); + return message; + } + if (model.phone == null || model.phone!.isEmpty) { + message = "请输入手机号"; + // showToast(message); + return message; + } + if (!MyUtils.isValidPhoneNumber(model.phone!)) { + message = '请输入正确的手机号'; + showToast(message); + return message; + } + // message = await repository.sendRegisterCode(model.phone!); + if (message.isNotEmpty) { + showToast(message ?? "发送失败,请稍后再试!"); + return "发送失败,请稍后再试!"; + } else { + showToast("发送验证码成功!", color: color_success); + } + return ''; + } + + //微信登录 + // Future wxLoginSendAuth() async { + // /* + // 1、目前移动应用上微信登录只提供原生的登录方式,需要用户安装微信客户端才能配合使用。 + // 2、对于Android应用,建议总是显示微信登录按钮,当用户手机没有安装微信客户端时,请引导用户下载安装微信客户端。 + // 3、对于iOS应用,考虑到iOS应用商店审核指南中的相关规定,建议开发者接入微信登录时,先检测用户手机是否已安装微信客户端 + // (使用sdk中isWXAppInstalled函数 ),对未安装的用户隐藏微信登录按钮,只提供其他登录方式(比如手机号注册登录、游客登录等) + // */ + // if (isAndroid) { + // bool isWeChatInstalled = await fluwx.isWeChatInstalled; + // debugPrint('is wechat installed: $isWeChatInstalled'); + // if (!isWeChatInstalled) { + // showToast("请先安装微信APP,再使用微信登录"); + // return; + // } + // } + // fluwx + // .authBy( + // which: NormalAuth( + // scope: 'snsapi_userinfo', + // state: 'wechat_sdk_zhmht_wxlogin', + // )) + // .then((data) { + // //返回true表示成功或者false表示失败,这边没有意义从login_controller页面构造函数监听中去处理 + // debugPrint('msg:$data'); + // }); + // } + + //退出登录 + // Future logout() async { + // await repository.logout(); + // } + + // loginByWechatCode(String code) async { + // return await repository.loginByWechatCode(code); + // } + + //注销账号 + // deletedAccount() async { + // return await repository.deletedAccount(); + // } +} diff --git a/lib/controller/login/login_controller.g.dart b/lib/controller/login/login_controller.g.dart new file mode 100644 index 0000000..05c1dc7 --- /dev/null +++ b/lib/controller/login/login_controller.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_controller.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LoginModel _$LoginModelFromJson(Map json) => LoginModel() + ..loginStyle = (json['loginStyle'] as num?)?.toInt() + ..account = json['account'] as String? + ..password = json['password'] as String? + ..phone = json['phone'] as String? + ..code = json['code'] as String? + ..register_code = json['register_code'] as String? + ..showPd = json['showPd'] as bool? + ..forceLogin = (json['forceLogin'] as num?)?.toInt() + ..isIos = json['isIos'] as bool? + ..isWeChatNotInstalled = json['isWeChatNotInstalled'] as bool?; + +Map _$LoginModelToJson(LoginModel instance) => + { + 'loginStyle': instance.loginStyle, + 'account': instance.account, + 'password': instance.password, + 'phone': instance.phone, + 'code': instance.code, + 'register_code': instance.register_code, + 'showPd': instance.showPd, + 'forceLogin': instance.forceLogin, + 'isIos': instance.isIos, + 'isWeChatNotInstalled': instance.isWeChatNotInstalled, + }; diff --git a/lib/controller/main_bottom/global_controller.dart b/lib/controller/main_bottom/global_controller.dart index 8772ee5..6373562 100644 --- a/lib/controller/main_bottom/global_controller.dart +++ b/lib/controller/main_bottom/global_controller.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'package:EasyDartModule/EasyDartModule.dart'; import 'package:ef/ef.dart'; import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; class GlobalModel { List deviceList = []; @@ -138,16 +140,11 @@ class GlobalController extends GetControllerEx { deviceUpdateTimerCreated() { if (getDeviceListTimer == null) { getDeviceListTimer = Timer.periodic(const Duration(seconds: 10), (t) { - if (userInfoController.model.token != null) { - - } + if (userInfoController.model.token != null) {} }); } } - - - getDeviceGroupName(device) { return "${device['roomName']}/${device["deviceType"]?["name"]}/${device["name"]}"; } @@ -169,4 +166,49 @@ class GlobalController extends GetControllerEx { model.deviceType = rs.where((d) => d["page"] != null).toList(); updateAll(); } + + getDeviceList({int time = 1}) async { + await EasyDartModule.dio.get("/api/device/info/list").then((d) { + Map d_ = {}; + d.data?["data"]?.forEach((item) { + if (d_[item["roomName"]] == null) { + d_[item["roomName"]] = []; + } + d_[item["roomName"]].add(item); + }); + List res_ = []; + d_.keys.forEach((key) { + res_.addAll(d_[key]); + }); + model.deviceList = res_; + if (model.deviceMain != null && model.deviceMain["mac"] != null) { + bool isClose = false; + model.deviceList.forEach((item) { + if (item["mac"] == model.deviceMain["mac"]) { + model.deviceMain = item; + isClose = true; + updateAll(); + } + }); + if (!isClose) { + model.deviceMain = {}; + updateAll(); + showCustomConfirmAndCancelDialog(Get.context!, "设备已经被解绑,是否回到主界面?") + .then((e) { + if (e == "confirm") { + Get.find().model.currentIndex = 0; + Get.offAllNamed("/mianPageBottomChange"); + } + }); + } + } else { + updateAll(); + } + }).catchError((e) { + print("$e"); + if (time > 0) { + getDeviceList(time: time - 1); + } + }); + } } diff --git a/lib/controller/person/person_controller.dart b/lib/controller/person/person_controller.dart new file mode 100644 index 0000000..e7c93e7 --- /dev/null +++ b/lib/controller/person/person_controller.dart @@ -0,0 +1,33 @@ +import 'package:ef/ef.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'person_controller.g.dart'; // 由json_serializable自动生成的部分 + +@JsonSerializable() +class PersonModel { + int read = 1; + + DateTime? birthday;//是否不再提示教程 0 不再提示 1 需要提示 + + PersonModel(); + + // 从JSON反序列化时的异常处理 + + factory PersonModel.fromJson(Map json) { + try { + return _$PersonModelFromJson(json); + } catch (e) { + // 在实际应用中,应该有更细致的异常处理策略和错误日志 + return PersonModel(); // 或者返回一个带有错误信息的特定DeviceInfoModel实例 + } + } + + // 序列化为JSON时的异常处理 + Map toJson() => _$PersonModelToJson(this); +} + +class PersonController extends GetControllerEx { + PersonController() { + attr = GetModel(PersonModel()).obs; + } +} \ No newline at end of file diff --git a/lib/controller/person/person_controller.g.dart b/lib/controller/person/person_controller.g.dart new file mode 100644 index 0000000..004da7e --- /dev/null +++ b/lib/controller/person/person_controller.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person_controller.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PersonModel _$PersonModelFromJson(Map json) => PersonModel() + ..read = (json['read'] as num).toInt() + ..birthday = json['birthday'] == null + ? null + : DateTime.parse(json['birthday'] as String); + +Map _$PersonModelToJson(PersonModel instance) => + { + 'read': instance.read, + 'birthday': instance.birthday?.toIso8601String(), + }; diff --git a/lib/controller/time/countdown_controller.dart b/lib/controller/time/countdown_controller.dart new file mode 100644 index 0000000..7840138 --- /dev/null +++ b/lib/controller/time/countdown_controller.dart @@ -0,0 +1,33 @@ +import 'dart:async'; +import 'package:get/get.dart'; + +class CountdownController extends GetxController { + var countdown = 0.obs; + Timer? timer; + + @override + void onInit() { + super.onInit(); + } + + void startCountdown(int seconds) { + timer?.cancel(); // 取消之前的定时器 + countdown.value = seconds; + timer = Timer.periodic(Duration(seconds: 1), (timer) { + int elapsed = timer.tick; + int remaining = seconds - elapsed; + if (remaining > 0) { + countdown.value = remaining; + } else { + countdown.value = 0; + timer.cancel(); + } + }); + } + + @override + void onClose() { + timer?.cancel(); + super.onClose(); + } +} diff --git a/lib/controller/user_info_controller.dart b/lib/controller/user_info_controller.dart index 38d05f3..6c4e7e5 100644 --- a/lib/controller/user_info_controller.dart +++ b/lib/controller/user_info_controller.dart @@ -1,19 +1,7 @@ -import 'dart:io'; - import 'package:ef/ef.dart'; - -import 'package:get_storage/get_storage.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:path/path.dart' as p; -import 'package:uuid/uuid.dart'; -import 'package:vbvs_app/common/color/app_uri_status.dart'; -import 'package:vbvs_app/common/util/CommonVariables.dart'; - -import 'package:vbvs_app/common/util/MyUtils.dart'; -import 'package:vbvs_app/model/api_response.dart'; import 'package:vbvs_app/model/user_data.dart'; - part 'user_info_controller.g.dart'; @JsonSerializable() @@ -34,9 +22,13 @@ class UserInfoModel { User? superbase_user; String? img_bucket = 'user'; - int? login = 0; + int? login = 1; //0未登录 1 登录 - bool? register_agree = false; //注册协议 + + + int? deviceBindNum = 0; //绑定设备数量 + + int? loginPhone = 0;//0 本机号码 1其他手机号 UserInfoModel(); static UserInfoModel fromJson(Map json) => diff --git a/lib/controller/user_info_controller.g.dart b/lib/controller/user_info_controller.g.dart index f8dc0b9..2243d6a 100644 --- a/lib/controller/user_info_controller.g.dart +++ b/lib/controller/user_info_controller.g.dart @@ -19,8 +19,7 @@ UserInfoModel _$UserInfoModelFromJson(Map json) => ..deviceModel = json['deviceModel'] as String? ..appVersion = json['appVersion'] as String? ..img_bucket = json['img_bucket'] as String? - ..login = (json['login'] as num?)?.toInt() - ..register_agree = json['register_agree'] as bool?; + ..login = (json['login'] as num?)?.toInt(); Map _$UserInfoModelToJson(UserInfoModel instance) => { @@ -34,5 +33,4 @@ Map _$UserInfoModelToJson(UserInfoModel instance) => 'appVersion': instance.appVersion, 'img_bucket': instance.img_bucket, 'login': instance.login, - 'register_agree': instance.register_agree, }; diff --git a/lib/main.dart b/lib/main.dart index f3e0f22..5e83c29 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,9 +4,12 @@ import 'package:flutter/services.dart'; import 'package:get_storage/get_storage.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/login/login_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart'; +import 'package:vbvs_app/controller/person/person_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/controller/time/countdown_controller.dart'; import 'package:vbvs_app/language/AppLanguage.dart'; import 'package:vbvs_app/model/CustomThemeColor.dart'; import 'package:vbvs_app/model/user_data.dart'; @@ -25,6 +28,7 @@ Future main() async { // await GetStorage.init(); // 初始化登录 await initLogin(); + await initLog(); // 检查网络 // Checknetwork.checkNetwork(); // 微信开放平台注册 @@ -41,6 +45,10 @@ Future main() async { // runApp(const MyApp()); } +initLog() { + +} + Future initLogin() async { // 初始化控制器 Get.put(UserInfoController()); @@ -155,6 +163,9 @@ class MyApp extends StatelessWidget { Get.put(GlobalController()), Get.lazyPut(() => MainPageController()), Get.lazyPut(() => BlueteethBindController()), + Get.lazyPut(() => PersonController()), + Get.lazyPut(() => CountdownController()), + Get.lazyPut(() => LoginController()), ])); }); } diff --git a/lib/model/BleDeviceData.dart b/lib/model/BleDeviceData.dart new file mode 100644 index 0000000..b4ad163 --- /dev/null +++ b/lib/model/BleDeviceData.dart @@ -0,0 +1,35 @@ +class BleDeviceData { + final int type; // 协议版本 + final int sn; // 广播包序号低8位 + final String deviceId; // 设备唯一地址(6字节) + final int bre; // 呼吸 + final int ht; // 心率 + final int active; // 体动等级 + final int flag; // 设备属性 + final int version; // 软件版本 + final int qsn; // 广播帧序列号高16位 + int? status; // 设备状态 + String? name;//设备名称 + int? rssi; + String? mac;//mac地址 + + BleDeviceData({ + required this.type, + required this.sn, + required this.deviceId, + required this.bre, + required this.ht, + required this.active, + required this.flag, + required this.version, + required this.qsn, + }); + + int get fullSeq => qsn * 256 + sn; + + bool get isOnline => (flag & 0x01) != 0; + bool get sensorError => (flag & 0x02) != 0; + bool get apnea => (flag & 0x04) != 0; + bool get inBed => (flag & 0x08) != 0; + bool get expired => (flag & 0x10) != 0; +} diff --git a/lib/pages/common/selectDialog.dart b/lib/pages/common/selectDialog.dart new file mode 100644 index 0000000..c5e0411 --- /dev/null +++ b/lib/pages/common/selectDialog.dart @@ -0,0 +1,773 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; + + +getOnePicker(context, List arr, int checkIndex, Function onSelectedItemChanged, + {bool looping = false}) { + return CupertinoPicker( + key: UniqueKey(), + useMagnifier: false, + itemExtent: 80.rpx, + magnification: 1, + diameterRatio: 3, + squeeze: 1, + looping: looping, + scrollController: FixedExtentScrollController(initialItem: checkIndex), + selectionOverlay: Container(), + onSelectedItemChanged: (int value) { + // print("$value"); + onSelectedItemChanged.call(value); + }, + children: [ + ...List.generate(arr.length, (index) { + return Container( + alignment: Alignment.center, + width: 400.rpx, + decoration: BoxDecoration( + border: Border( + bottom: index != arr.length + ? BorderSide( + color: stringToColor("#8D95B0"), + ) + : BorderSide.none, + ), + ), + child: Text("${arr[index]}", + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx)), + ); + }) + ], + ); +} + +Future showDateSelectionDialog(BuildContext context, + {required DateTime checkDate, + Function? checkChange, + String title = "选择生日"}) { + Color checkColor = stringToColor("#D3B684"); + List years = [], months = [], days = []; + var days_select = [].obs; + int day_len = 31; + int year = DateTime.now().year; + for (var i = 0; i < 100; i++) { + years.insert(0, year - i); + } + for (var i = 1; i < 13; i++) { + months.add(i); + } + for (var i = 1; i < 32; i++) { + days.add(i); + } + int yearIndex = years.lastIndexOf(checkDate.year); + int monthIndex = months.lastIndexOf(checkDate.month); + day_len = DateTime.fromMillisecondsSinceEpoch( + DateTime(years[yearIndex], months[monthIndex] + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = days.sublist(0, day_len); + int dayIndex = days.lastIndexOf(checkDate.day); + return showDialog( + // barrierColor: stringToColor("#000320"), + context: context, + barrierDismissible: true, // 点击对话框外部可关闭 + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, // 控制弹窗距离顶部的位置 + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: stringToColor("#182B7C"), + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(30.rpx, 10.rpx, 30.rpx, 90.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.centerLeft, + height: 60.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "$title", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + closeIconWhite + ], + ), + ), + Container( + height: 240.rpx, + margin: EdgeInsets.only(top: 60.rpx, bottom: 60.rpx), + padding: EdgeInsets.only(left: 30.rpx, right: 30.rpx), + child: Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 40.rpx, right: 30.rpx), + child: getOnePicker(context, years, yearIndex, + (d) { + yearIndex = d; + dayIndex = 0; + day_len = DateTime.fromMillisecondsSinceEpoch( + DateTime(years[yearIndex], + months[monthIndex] + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = days.sublist(0, day_len); + }), + ), + ), + Container( + child: Text( + "年", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 30.rpx, right: 30.rpx), + child: getOnePicker(context, months, monthIndex, + (d) { + monthIndex = d; + dayIndex = 0; + day_len = DateTime.fromMillisecondsSinceEpoch( + DateTime(years[yearIndex], + months[monthIndex] + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = days.sublist(0, day_len); + }), + ), + ), + Container( + child: Text( + "月", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 30.rpx, right: 40.rpx), + child: Obx( + () { + // print("${dayIndex} ${day_len}"); + return getOnePicker( + context, + days_select, + dayIndex, + (d) { + dayIndex = d; + }, + ); + }, + ), + ), + ), + Container( + child: Text( + "日", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ], + ), + ), + InkWell( + onTap: () { + checkChange?.call(DateTime(years[yearIndex], + months[monthIndex], days[dayIndex])); + Get.back(); + }, + child: Container( + height: 68.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + color: stringToColor("#D3B684"), + borderRadius: BorderRadius.circular(10.rpx), + ), + child: Text( + "确定", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ) + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} + +Future showDayTimeSelectionDialog(BuildContext context, + {required List dayTimeArr, Function? checkChange, String title = ""}) { + Color checkColor = stringToColor("#D3B684"); + List hours = [], minutes = []; + for (var i = 0; i < 24; i++) { + hours.add(i); + } + for (var i = 0; i < 60; i++) { + minutes.add(i); + } + int hoursIndex = hours.lastIndexOf(dayTimeArr[0]); + int minutesIndex = minutes.lastIndexOf(dayTimeArr[1]); + return showDialog( + // barrierColor: stringToColor("#000320"), + context: context, + barrierDismissible: true, // 点击对话框外部可关闭 + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, // 控制弹窗距离顶部的位置 + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: stringToColor("#182B7C"), + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(30.rpx, 10.rpx, 30.rpx, 90.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.centerLeft, + height: 60.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "$title", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + closeIconWhite + ], + ), + ), + Container( + height: 240.rpx, + margin: EdgeInsets.only(top: 60.rpx, bottom: 60.rpx), + padding: EdgeInsets.only(left: 30.rpx, right: 30.rpx), + child: Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 40.rpx, right: 30.rpx), + child: getOnePicker(context, hours, hoursIndex, + (d) { + hoursIndex = d; + }), + ), + ), + Container( + child: Text( + "时", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 30.rpx, right: 30.rpx), + child: getOnePicker( + context, minutes, minutesIndex, (d) { + minutesIndex = d; + }), + ), + ), + Container( + child: Text( + "分", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ], + ), + ), + InkWell( + onTap: () { + checkChange?.call( + [hours[hoursIndex], minutes[minutesIndex]]); + Get.back(); + }, + child: Container( + height: 68.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + color: stringToColor("#D3B684"), + borderRadius: BorderRadius.circular(10.rpx), + ), + child: Text( + "确定", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ) + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} + +Future showOneSelectionDialog(BuildContext context, + {required List arr, + int checkIndex = 0, + Function? checkChange, + String title = "选择性别"}) { + Color checkColor = stringToColor("#D3B684"); + return showDialog( + // barrierColor: stringToColor("#000320"), + context: context, + barrierDismissible: true, // 点击对话框外部可关闭 + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, // 控制弹窗距离顶部的位置 + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: stringToColor("#182B7C"), + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(30.rpx, 10.rpx, 30.rpx, 90.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.centerLeft, + height: 60.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "$title", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + closeIconWhite + ], + ), + ), + Container( + height: 240.rpx, + alignment: Alignment.center, + margin: EdgeInsets.only(top: 60.rpx, bottom: 60.rpx), + padding: EdgeInsets.only(left: 30.rpx, right: 30.rpx), + child: Container( + width: 400.rpx, + child: + getOnePicker(context, arr, checkIndex, (index) { + checkIndex = index; + }), + )), + InkWell( + onTap: () { + checkChange?.call(checkIndex); + Get.back(); + }, + child: Container( + height: 68.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + color: stringToColor("#D3B684"), + borderRadius: BorderRadius.circular(10.rpx), + ), + child: Text( + "确定", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ) + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} + +enum ConfirmDialogIcon { + none, + danger, + success, + warn; + + get uname { + String v = ""; + switch (this) { + case ConfirmDialogIcon.danger: + v = "danger"; + break; + case ConfirmDialogIcon.success: + v = "success"; + break; + case ConfirmDialogIcon.warn: + v = "warn"; + break; + case ConfirmDialogIcon.none: + v = ""; + break; + } + return v; + } +} + +Future showCustomConfirmDialog(BuildContext context, String name, + {String btnName = "确定", + ConfirmDialogIcon icon = ConfirmDialogIcon.warn}) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Container( + width: 660.rpx, + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + alignment: Alignment.centerRight, + child: closeIcon, + ), + SizedBox(height: 60.rpx), + if ("${icon.uname}".isNotEmpty) + Center( + child: Container( + margin: EdgeInsets.only(bottom: 39.rpx), + width: 50.rpx, + height: 50.rpx, + child: Image.asset("assets/images/toast/${icon.uname}.png"), + ), + ), + Center( + child: Text( + '${name}', + style: TextStyle(fontSize: 16), + ), + ), + SizedBox(height: 20.rpx), + Container( + margin: EdgeInsets.only(top: 50.rpx, bottom: 40.rpx), + alignment: Alignment.center, + child: InkWell( + onTap: () { + Get.back(result: "confirm"); + }, + child: Container( + width: 260.rpx, + height: 60.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: stringToColor("#D3B684")), + child: Text( + '$btnName', + style: TextStyle(color: Colors.white, fontSize: 30.rpx), + ), + ), + ), + ) + ], + ), + ), + ); + }, + ); +} + + +Future showCustomConfirmAndCancelDialog(BuildContext context, String name, + {String confirmName = "确定", + String cancelName = "取消", + ConfirmDialogIcon icon = ConfirmDialogIcon.warn}) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Container( + width: 660.rpx, + padding: EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + alignment: Alignment.centerRight, + child: closeIcon, + ), + SizedBox(height: 40.rpx), + if ("${icon.uname}".isNotEmpty) + Center( + child: Container( + margin: EdgeInsets.only(bottom: 39.rpx), + width: 50.rpx, + height: 50.rpx, + child: Image.asset("assets/images/toast/${icon.uname}.png"), + ), + ), + Center( + child: Text( + '${name}', + style: TextStyle(fontSize: 16), + ), + ), + SizedBox(height: 20.rpx), + Container( + margin: EdgeInsets.only(top: 50.rpx, bottom: 40.rpx), + alignment: Alignment.center, + child: InkWell( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + Get.back(result: "cancel"); + }, + child: Container( + width: 200.rpx, + height: 60.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all(color: Colors.black12)), + child: Text( + '$cancelName', + style: + TextStyle(color: Colors.black, fontSize: 30.rpx), + ), + ), + ), + SizedBox( + width: 80.rpx, + ), + InkWell( + onTap: () { + Get.back(result: "confirm"); + }, + child: Container( + width: 200.rpx, + height: 60.rpx, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: stringToColor("#D3B684")), + child: Text( + '$confirmName', + style: + TextStyle(color: Colors.white, fontSize: 30.rpx), + ), + ), + ) + ], + )), + ) + ], + ), + ), + ); + }, + ); +} + +//权限说明弹窗 +void showPermissionInfoDialog(BuildContext context, List data) { + showDialog( + context: context, + barrierDismissible: false, // 点击对话框外部可关闭 + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + top: 30.rpx, // 控制弹窗距离顶部的位置 + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: Colors.white, + insetPadding: EdgeInsets.fromLTRB(0, 0, 0, 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Container( + constraints: BoxConstraints(maxHeight: 500.rpx), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...List.generate(data.length, (index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${data[index][0]}", + style: TextStyle( + fontSize: 30.rpx, + color: stringToColor("#333333"), + ), + ), + SizedBox( + height: 4.rpx, + ), + Text( + "${data[index][1]}", + style: TextStyle( + fontSize: 26.rpx, + color: stringToColor("#A4AABC"), + ), + ), + if (index != data.length - 1) + SizedBox( + height: 18.rpx, + ), + ], + ); + }), + ], + ), + ), + ), + ), + ), + ), + ], + ); + }, + ); +} diff --git a/lib/pages/device_bind/bind_device_success.dart b/lib/pages/device_bind/bind_device_success.dart new file mode 100644 index 0000000..626e40d --- /dev/null +++ b/lib/pages/device_bind/bind_device_success.dart @@ -0,0 +1,320 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; + +class BindDeviceSuccess extends StatefulWidget { + const BindDeviceSuccess({super.key}); + + @override + State createState() => _EPageState(); +} + +class _EPageState extends State { + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + BlueteethBindController blueteethBindController = Get.find(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, bodySize) => GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgNoImg.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, // 加上这一行 + appBar: AppBar( + backgroundColor: stringToColor("#242835"), + // backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + iconTheme: IconThemeData(color: Colors.white), + titleSpacing: 0, + // leading: returnIconButtom, + title: Container( + // color: Colors.grey, + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + /// 居中标题 + Text( + '绑定成功.标题'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + + /// 左边返回按钮 + Positioned( + left: 0, + child: returnIconButtom, + ), + ], + ), + ), + + actions: [], + centerTitle: false, + ), + + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(113.rpx, 0, 113.rpx, 0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 74.rpx, 0, 0), + child: Container( + width: 124.rpx, + height: 124.rpx, + // width: double.infinity, + decoration: BoxDecoration(), + child: SvgPicture.asset( + 'assets/img/icon/tick.svg', + fit: BoxFit.cover, + color: Colors.white, + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 42.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '绑定成功.绑定成功'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 48.rpx, + letterSpacing: 0.0, + color: Colors.white, + ), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 265.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '绑定成功.分享标题'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: Colors.white, + ), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 48.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: Text( + '绑定成功.分享内容'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: Colors.white, + ), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 110.rpx, 0, 0), + child: CustomCard( + borderRadius: + AppConstants().button_container_radius, // 圆角半径 + onTap: () { + print('Button pressed ...'); + Get.toNamed("/deviceType"); + }, + colors: [ + // 渐变色 + stringToColor("45D989"), // 左侧渐变色 + stringToColor("00C1AA"), // 右侧渐变色 + ], + child: Container( + width: MediaQuery.sizeOf(context).width * 0.66, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + 'assets/img/icon/share.svg', + width: 25.rpx, + height: 25.rpx, // 如果 SVG 中没有固定颜色,可以这样设置 + color: Colors.white, // 设置图标颜色为白色 + ), + Text( + '绑定成功.立即分享'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + color: Colors.white, // 文字颜色 + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, // 自定义字体大小 + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 17.rpx)), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 18.rpx, 0, 0), + child: CustomCard( + borderRadius: + AppConstants().button_container_radius, // 圆角半径 + onTap: () { + Get.offAllNamed("/mianPageBottomChange"); + }, + colors: [ + // 渐变色 + stringToColor("45D989"), // 左侧渐变色 + stringToColor("00C1AA"), // 右侧渐变色 + ], + + child: Container( + width: MediaQuery.sizeOf(context).width * 0.66, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '绑定成功.返回'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + color: Colors.white, // 文字颜色 + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, // 自定义字体大小 + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 17.rpx)), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildDeviceCard(BuildContext context, + {required String title, required String imageUrl, required String type}) { + return CustomCard( + borderRadius: 20.rpx, // 圆角大小 + onTap: () { + if (type != null) { + if (type == '1') { + Get.toNamed("/blueteethDevice"); + } + } + }, + // colors: [Colors.white.withOpacity(0.06)], // 背景色 + colors: [stringToColor("#242835")], // 背景色 + child: Container( + width: double.infinity, + height: MediaQuery.sizeOf(context).height * 0.135, + constraints: BoxConstraints( + minHeight: 220.rpx, + ), + padding: EdgeInsetsDirectional.fromSTEB(77.rpx, 0, 21.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFC2CED7), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8.rpx), + child: Image.asset( + imageUrl, + width: 212.rpx, + height: 168.rpx, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/device_bind/blueteeth_device_page.dart b/lib/pages/device_bind/blueteeth_device_page.dart new file mode 100644 index 0000000..d653d24 --- /dev/null +++ b/lib/pages/device_bind/blueteeth_device_page.dart @@ -0,0 +1,1059 @@ +import 'dart:async'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/model/BleDeviceData.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart'; +import 'package:vbvs_app/common/util/Ble.dart' as ble; + +class BlueteethDevicePage extends StatefulWidget { + int tid = -1; + BlueteethDevicePage({super.key, this.tid = -1}); + + @override + State createState() => _EPageState(); +} + +class _EPageState extends State { + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + BlueteethBindController blueteethBindController = Get.find(); + late FlutterBluePlus flutterBlue; // 声明 flutterBlue 实例 + List scanResults = []; // 存储扫描到的设备 + bool isScanning = false; // 扫描状态控制 + Timer? _timer; // 定时器,用于重复扫描 + bool _isDialogShowing = false; + + var currentConnectedDeviceProp; + var connectDeviceCurrent = null; + List bleDevice = []; + String currentMsg = "寻找设备中..."; + Timer? connectTimer; + bool isFind = false; + List bindArrBackup = []; + List bindArr = ["", "", ""]; + + @override + void initState() { + super.initState(); + flutterBlue = FlutterBluePlus(); // 初始化flutterBlue实例 + _checkBluetoothPermission(); // 检查蓝牙权限 + } + + // 检查蓝牙权限 + Future _checkBluetoothPermission() async { + PermissionStatus bluetoothStatus = await Permission.bluetooth.status; + PermissionStatus locationStatus = await Permission.location.status; + + if (bluetoothStatus.isGranted && locationStatus.isGranted) { + // 权限已授予,开始扫描 + _startScanning(); + _startPeriodicScan(); // 开始定时扫描 + } else { + // 权限未授予,请求权限 + _requestBluetoothPermission(); + } + } + + Future _requestBluetoothPermission() async { + // Android 13+ 使用新的蓝牙权限 + Map statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, // Android 12 及以下仍需要位置权限来进行扫描 + ].request(); + + bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true && + statuses[Permission.bluetoothConnect]?.isGranted == true && + statuses[Permission.location]?.isGranted == true; + + if (allGranted) { + // 用户授予了权限,开始扫描 + _startScanning(); + _startPeriodicScan(); + } else { + // 权限被拒绝,提示用户 + _showPermissionDeniedDialog(); + } + } + + // 显示权限被拒绝的提示 + void _showPermissionDeniedDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text("权限提示"), + content: Text("应用需要蓝牙和位置权限才能扫描设备。请授予权限。"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text("确定"), + ), + ], + ); + }, + ); + } + + // 开始扫描蓝牙设备 + void _startScanning() async { + var bluetoothState = await FlutterBluePlus.isOn; + if (!bluetoothState && !_isDialogShowing) { + _isDialogShowing = true; + await _showBluetoothNotEnabledDialog(); + _isDialogShowing = false; + return; + } + + if (!isScanning) { + setState(() { + isScanning = true; + }); + + await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + // FlutterBluePlus.scanResults.listen((results) { + // // 使用 controller 中设定的最小信号强度过滤 + // final signalThreshold = blueteethBindController.model.singal!; + // final filteredResults = results + // .where((r) => + // r.rssi > signalThreshold && + // r.advertisementData.localName == "AITH-V2") + // .toList(); + // setState(() { + // scanResults = filteredResults; + // print("过滤后设备数量:${filteredResults.length}"); + // blueteethBindController.model.devicelist = filteredResults; + // blueteethBindController.updateAll(); + // if (filteredResults.isNotEmpty) { + // for (var r in filteredResults) { + // print("设备名: ${r.device.name}, RSSI: ${r.rssi}"); + // } + // } else { + // print("没有找到符合条件的设备"); + // } + // }); + // }); + + FlutterBluePlus.scanResults.listen((results) { + final signalThreshold = blueteethBindController.model.singal!; + final filteredResults = results + .where((r) => + r.rssi > signalThreshold && + r.advertisementData.localName == "AITH-V2" && + r.advertisementData.manufacturerData.containsKey(0xFFED)) + .toList(); + + // 解析数据 + final parsedDeviceList = []; + + for (var r in filteredResults) { + try { + List rawData = r.advertisementData.manufacturerData[0xFFED]!; + BleDeviceData deviceData = parseBleData(rawData); + deviceData.name = r.advertisementData.advName; + deviceData.rssi = r.rssi; + deviceData.mac = deviceData.deviceId.replaceAll(':', ''); + parsedDeviceList.add(deviceData); + } catch (e) { + print("设备数据解析失败: $e"); + } + } + + setState(() { + scanResults = filteredResults; + + // 存入 controller 中 + blueteethBindController.model.blelist = filteredResults; + blueteethBindController.model.devicelist = parsedDeviceList; + blueteethBindController.updateDeviceStatus(); + blueteethBindController.updateAll(); + + // print("过滤并解析后设备数量:${parsedDeviceList.length}"); + // for (var device in parsedDeviceList) { + // print( + // "设备: ${device.deviceId}, HR: ${device.ht}, BRE: ${device.bre}, Seq: ${device.fullSeq}"); + // } + + // if (parsedDeviceList.isEmpty) { + // print("没有符合条件的 BLE 广播数据"); + // } + }); + }); + + // 等待扫描完成 + await Future.delayed(Duration(seconds: 10)); + await FlutterBluePlus.stopScan(); + + setState(() { + isScanning = false; + }); + + print("扫描完成"); + } + } + + // 定时每10秒进行一次扫描 + void _startPeriodicScan() { + _timer = Timer.periodic(Duration(seconds: 10), (timer) { + if (!isScanning) { + _startScanning(); // 调用扫描函数 + } + }); + } + + // 停止扫描 + void _stopScanning() { + if (isScanning) { + FlutterBluePlus.stopScan(); // 停止扫描 + setState(() { + isScanning = false; // 更新扫描状态 + }); + } + } + + // 停止定时扫描 + void _stopPeriodicScan() { + _timer?.cancel(); + } + + @override + void dispose() { + super.dispose(); + _stopPeriodicScan(); // 停止定时扫描 + _stopScanning(); // 确保离开页面时停止扫描 + } + + connectToDevice(device, {int time = 5}) { + ble.connectToDevice( + { + "device": device, + 'success': (ble.ConnectedDeviceProp deviceProp) { + if (deviceProp.connectedDevicePropType == + ble.ConnectedDevicePropType.JunHe) { + currentConnectedDeviceProp = deviceProp; + deviceProp.write3OfString("blog enable"); + deviceProp.write3OfString("blog rlmax=128"); + Timer(const Duration(microseconds: 100), () async { + String log = ""; + Function logAdd = (l) { + log += l; + }; + deviceProp.receiveLogArr.add(logAdd); + deviceProp.encodeType = 1; + deviceProp.deviceType = 1; + Timer.periodic(const Duration(milliseconds: 300), (timer) async { + if (timer.tick > 20) { + ble.disconnect(currentConnectedDeviceProp); + failSelectDialog(); + timer.cancel(); + } + if (log.contains("GB2312") || log.contains("UTF-8")) { + timer.cancel(); + if (log.contains('CHARSET:UTF-8')) { + deviceProp.encodeType = 2; + } + if (log.contains('TARGET:ESPXX')) { + deviceProp.deviceType = 2; + } + log = ""; + bool isSuccess = false; + for (var i = 0; i < 4; i++) { + deviceProp.write3OfString("at+system info"); + await Future.delayed(const Duration(milliseconds: 400)); + RegExp regExp = RegExp(r"Target Mac:(\S*)"); + RegExpMatch? regExpMatch = regExp.firstMatch(log); + if (regExpMatch != null && regExpMatch.group(1) != null) { + String? mac = regExpMatch.group(1); + if (mac?.length == 12 && mac != "000000000000") { + bindArr[2] = "$mac".toUpperCase(); + } + isSuccess = true; + break; + } + } + deviceProp.receiveLogArr.remove(logAdd); + print("$bindArr"); + RegExp regExp = RegExp( + r"WIFI CONNECTED INFO:SSID=([^\t\n]*)\s*,RSSI=(\S*)\s*,"); + RegExpMatch? regExpMatch = regExp.firstMatch(log); + if (regExpMatch != null && log.contains("Status=connect")) { + blueteethBindController.model.connectedWifiName = + regExpMatch.group(1) ?? ""; + if (int.tryParse("${regExpMatch.group(2)}") != null) { + blueteethBindController.model.connectedRssi = + int.parse("${regExpMatch.group(2)}"); + } + blueteethBindController.updateAll(); + } + ble.bleParse(); + if (bindArr[0] != null && + bindArr[0] != "" && + bindArr[1] != "") { + setState(() { + currentMsg = "绑定中..."; + }); + blueteethBindController.bindDevice({ + "tid": widget.tid, + "name": blueteethBindController.model.deviceName, + "mac": bindArr[0], + "macA": bindArr[1], + "macB": bindArr[2] + }).then((d) { + blueteethBindController.model.bindArr = bindArr; + globalController.getDeviceList(); + LoadingDialog.hide(); + showCustomConfirmDialog(context, "设备添加成功!", + btnName: "打开WIFI配置", + icon: ConfirmDialogIcon.success) + .then((d) { + if (d == "confirm") { + Get.offAndToNamed("/wifi", arguments: deviceProp); + } + }); + }).catchError((d) { + print("$d"); + currentMsg = "绑定失败: ${d.message}"; + ble.disconnect(currentConnectedDeviceProp); + failSelectDialog(title: "${d.message}"); + }); + } else { + LoadingDialog.hide(); + Get.offAndToNamed("/wifi", arguments: deviceProp); + } + } else { + deviceProp.read6(); + } + }); + }); + } else if (deviceProp.connectedDevicePropType == + ble.ConnectedDevicePropType.QuanShi) { + List receive = []; + Function fun = (d) { + receive.add(d); + }; + deviceProp.receiveLogArr.add(fun); + List head = [ + 255, + 255, + 255, + 255, + 1, + 0, + 12, + 17, + ]; + Timer.periodic(const Duration(seconds: 1), (timer) { + if (timer.tick > 20) { + timer.cancel(); + currentMsg = "错误:未能获取到MAC"; + failSelectDialog(); + } + deviceProp.write( + Uint8List.fromList([ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x01, + 0x00, + 0x0C, + 0x0B, + 0x0F, + 0x23, + 0x04 + ]), + null, + null); + if (receive.length > 0) { + receive.forEach((data) { + if (data.length != 17) { + return; + } + bool r = true; + for (var i = 0; i < head.length; i++) { + if (head[i] != data[i]) { + r = false; + } + } + if (r == false) { + return; + } + bindArr[1] = ble.ab2str(data.sublist(9, 15)).toUpperCase(); + timer.cancel(); + deviceProp.receiveLogArr.remove(fun); + blueteethBindController.model.deviceName = + deviceProp.connectDevice?.advName; + ble.disconnect(deviceProp); + toFindJunhe(); + }); + } + }); + } else { + List receive = []; + Function fun = (d) { + receive.add(d); + }; + deviceProp.receiveLogArr.add(fun); + List head = [255, 255, 255, 255, 0x00, 0x08, 0x40, 0x01]; + Timer.periodic(const Duration(seconds: 1), (timer) { + if (timer.tick > 20) { + timer.cancel(); + currentMsg = "错误:未能获取到MAC"; + failSelectDialog(); + } + deviceProp.write( + Uint8List.fromList([ + 255, + 255, + 255, + 255, + 0x00, + 0x03, + 0x40, + 0x01, + 0x01, + 0x00, + 0x45, + 0xfd + ]), + null, + null); + if (receive.length > 0) { + receive.forEach((data) { + if (data.length != 17) { + return; + } + bool r = true; + for (var i = 0; i < head.length; i++) { + if (head[i] != data[i]) { + r = false; + } + } + if (r == false) { + return; + } + + bindArr[1] = ble.ab2str(data.sublist(8, 14)).toUpperCase(); + print("$bindArr"); + timer.cancel(); + deviceProp.receiveLogArr.remove(fun); + blueteethBindController.model.deviceName = + deviceProp.connectDevice?.advName; + ble.disconnect(deviceProp); + toFindJunhe(); + }); + } + }); + } + }, + 'fail': (e) { + print(e); + if (time > 0) { + connectToDevice(device, time: time - 1); + } else { + currentMsg = "蓝牙无法连接上设备"; + failSelectDialog(title: currentMsg); + } + } + }, + ); + } + + isBind() { + return !(blueteethBindController.model.bindArr[1]?.length == 12); + } + + failSelectDialog({String title = ""}) { + LoadingDialog.hide(); + setState(() {}); + showCustomConfirmAndCancelDialog( + context, title == "" ? (isBind() ? "绑定失败" : "连接失败") : title, + confirmName: "重试", cancelName: "返回") + .then((d) { + if (d == "confirm") { + if (connectDeviceCurrent != null) { + ble.bleParse(); + ble.start((List d) { + setState(() { + bleDevice = d; + }); + }, bleOnCall: () { + LoadingDialog.show("连接中...\n靠近设备2米内", + icon: + isBind() ? LoadingDialogIcon.ble : LoadingDialogIcon.wifi); + setState(() { + currentMsg = "连接设备中..."; + }); + connectToDevice(connectDeviceCurrent); + }); + } else { + bleExec(); + } + } else if (d == "cancel") { + Get.back(); + } + }); + } + + bleExec() { + ble.bleParse(); + connectTimer?.cancel(); + int index = 0; + bool isCloseLoadingDialog = false; + isFind = false; + blueteethBindController.model.bindArr = bindArrBackup; + bindArr = ["", "", ""]; + ble.start((List d) { + setState(() { + bleDevice = d; + }); + if (isBind()) { + if (isCloseLoadingDialog == false && + bleDevice.indexWhere((item) { + if (widget.tid == 1) { + return ble.isQuanShiDevice(item["name"]); + } else { + return ble.isMHTSWES(item["name"]); + } + }) != + -1) { + isCloseLoadingDialog = true; + LoadingDialog.hide(); + } + } + }, bleOnCall: () { + if (isBind()) { + LoadingDialog.show("搜索蓝牙设备中...\n请打开蓝牙开关、定位开关\n与设备距离在2米内", + icon: isBind() ? LoadingDialogIcon.ble : LoadingDialogIcon.wifi); + Timer.periodic(const Duration(seconds: 1), (t) { + if (t.tick > 15) { + t.cancel(); + isCloseLoadingDialog = true; + LoadingDialog.hide(); + showCustomConfirmAndCancelDialog(context, "未发现设备", + confirmName: "重试", cancelName: "返回") + .then((d) { + if (d == "confirm") { + bleExec(); + } else if (d == "cancel") { + Get.back(); + } + }); + } else { + if (isCloseLoadingDialog == true) { + t.cancel(); + } + } + }); + return; + } + LoadingDialog.show( + "${isBind() ? "绑定中...\n与设备距离在2米内" : "连接中...\n请打开蓝牙开关、定位开关\n与设备距离在2米内"}", + icon: isBind() ? LoadingDialogIcon.ble : LoadingDialogIcon.wifi); + connectTimer = Timer.periodic(const Duration(seconds: 1), (t) { + index++; + if (index > 15) { + connectTimer = null; + t.cancel(); + failSelectDialog(); + } + var d = bleDevice; + if (d != null && d.length > 0) { + if (isBind()) { + var deviceble = d.firstWhere((item) { + bool isFF = false; + if (widget.tid == 1) { + isFF = ble.isQuanShiDevice(item["name"]); + } else { + isFF = ble.isMHTSWES(item["name"]); + } + if (isFF) { + isFF = globalController.model.deviceList.indexWhere( + (d) => d["mac"] == item["adData"]["deviceId"]) == + -1 + ? true + : false; + } + return isFF; + }, orElse: () => null); + if (!isFind && deviceble != null) { + print("quanshidevice"); + isFind = true; + setState(() { + currentMsg = "连接设备中..."; + }); + t.cancel(); + connectToDevice(deviceble["device"]); + bindArr[0] = deviceble["adData"]["deviceId"]; + } + } else { + var deviceble = d.firstWhere( + (item) => + item["adData"]["deviceId"] == + blueteethBindController.model.bindArr[1], + orElse: () => null); + if (!isFind && deviceble != null) { + print("junhedevice"); + isFind = true; + t.cancel(); + setState(() { + currentMsg = "连接设备中..."; + }); + connectToDevice(deviceble["device"]); + bindArr[1] = deviceble["adData"]["deviceId"]; + } + } + } + }); + }); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) => GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgNoImg.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, // 加上这一行 + appBar: AppBar( + iconTheme: IconThemeData(color: Colors.white), + backgroundColor: stringToColor("#242835"), + automaticallyImplyLeading: false, + titleSpacing: 0, + title: Container( + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + Text( + '蓝牙绑定.标题'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + Positioned( + left: 0, + child: returnIconButtom, + ), + ], + ), + ), + actions: [], + centerTitle: false, + ), + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFF242835), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 30.rpx, 0, 30.rpx), + child: Text( + '蓝牙绑定.扫描蓝牙设备中…'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFE8EEF3), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFF242835), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 21.rpx, 5.rpx, 21.rpx, 5.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '最小信号强度', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFEEF2F5), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Expanded( + child: Obx(() { + return Slider( + activeColor: Color(0xFF1FCC9B), + inactiveColor: + FlutterFlowTheme.of(context).alternate, + min: -100, + max: 50, + value: blueteethBindController.model.singal!, + onChanged: (newValue) { + newValue = double.parse( + newValue.toStringAsFixed(0)); + blueteethBindController.model.singal = + newValue; + blueteethBindController.updateAll(); + }, + ); + }), + ), + Obx(() { + return Text( + '${blueteethBindController.model.singal!.toInt()}', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFE4E8EB), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ); + }), + ].divide(SizedBox(width: 30.rpx)), + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 35.rpx, 0, 35.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0, 0), + child: Container( + width: 25.rpx, + height: 25.rpx, + // width: double.infinity, + decoration: BoxDecoration(), + child: SvgPicture.asset( + 'assets/img/icon/query.svg', + fit: BoxFit.cover, + color: Colors.black, + ), + ), + ), + Expanded( + child: Container( + width: 100.rpx, + height: 100.rpx, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context) + .secondaryBackground, + ), + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: TextFormField( + autofocus: false, + obscureText: false, + decoration: InputDecoration( + isDense: true, + labelStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + hintText: '蓝牙绑定.搜索提示'.tr, + hintStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + filled: true, + fillColor: + FlutterFlowTheme.of(context) + .secondaryBackground, + ), + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + cursorColor: + FlutterFlowTheme.of(context) + .primaryText, + // validator: _model + // .textControllerValidator + // .asValidator(context), + ), + ), + ), + ), + ].divide(SizedBox(width: 6.rpx)), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 26.rpx, 0, 0, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + height: 50.rpx, + child: VerticalDivider( + thickness: 2.rpx, + color: FlutterFlowTheme.of(context) + .alternate, + ), + ), + Text( + '搜索', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 26.rpx)), + ), + ), + ], + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 60.rpx, 0, 32.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(19.rpx, 0, 0, 0), + child: Text( + '匹配出的外围设备(${blueteethBindController.model.devicelist!.length})', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: Colors.white), + ), + ), + ), + ), + Expanded( + child: Container( + width: double.infinity, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + ...blueteethBindController.model.blelist! + .map((device) => + SingleBlueteethDeviceCompoentWidget( + // device: device, + bleDevice: device, + )) + .toList() + .divide(SizedBox(height: 30.rpx)) + .addToEnd(SizedBox(height: 30.rpx)), + ], + ), + ), + ), + ), + ].divide(SizedBox(height: 30.rpx)), + ), + ), + ), + ), + ), + ), + ); + } + + _showBluetoothNotEnabledDialog() async { + await showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text("蓝牙未开启"), + content: Text("请先打开蓝牙再进行设备扫描"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text("知道了"), + ), + ], + ), + ); + } + + toFindJunhe() { + bool isSuccess = false; + int i = 0; + Timer.periodic(const Duration(seconds: 1), (t) async { + i++; + if (isSuccess) { + return; + } + if (i > 8) { + if (!isSuccess) { + currentMsg = "错误:未找到关联设备"; + failSelectDialog(title: "绑定失败:未找到关联设备"); + } + t.cancel(); + return; + } + bleDevice.forEach((item) { + if (isSuccess) { + return; + } + if (item['adData']['deviceId'] == bindArr[1]) { + isSuccess = true; + t.cancel(); + setState(() { + currentMsg = "寻找关联设备中..."; + }); + connectToDevice(item["device"]); + } + }); + }); + } +} + +BleDeviceData parseBleData(List data) { + if (data.length < 18) { + throw Exception('BLE广播数据长度不足18字节'); + } + + int type = data[0]; + int sn = data[1]; + + // 设备唯一ID (6字节),格式化为 MAC 地址样式 + String deviceId = + List.generate(6, (i) => data[2 + i].toRadixString(16).padLeft(2, '0')) + .join(":") + .toUpperCase(); + + int bre = data[8]; + int ht = data[9]; + int active = data[10]; + int flag = data[11]; + + // version 是4字节 uint,大端字节序 + int version = + (data[12] << 24) | (data[13] << 16) | (data[14] << 8) | data[15]; + + // qsn 是2字节 ushort,大端字节序 + int qsn = (data[16] << 8) | data[17]; + + return BleDeviceData( + type: type, + sn: sn, + deviceId: deviceId, + bre: bre, + ht: ht, + active: active, + flag: flag, + version: version, + qsn: qsn, + ); +} diff --git a/lib/pages/device_bind/componnet/DoubleBlueteethDeviceCompoentWidget.dart b/lib/pages/device_bind/componnet/DoubleBlueteethDeviceCompoentWidget.dart new file mode 100644 index 0000000..4664d0c --- /dev/null +++ b/lib/pages/device_bind/componnet/DoubleBlueteethDeviceCompoentWidget.dart @@ -0,0 +1,294 @@ +import 'package:ef/base/widget/flutterflow/FlutterFlowTheme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; + +class DoubleBlueteethDeviceCompoentWidget extends StatefulWidget { + const DoubleBlueteethDeviceCompoentWidget({super.key}); + + @override + State createState() => + _DoubleBlueteethDeviceCompoentWidgetState(); +} + +class _DoubleBlueteethDeviceCompoentWidgetState + extends State { + @override + void setState(VoidCallback callback) { + super.setState(callback); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFF242835), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 0, 52.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + 'AITH-V3', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '信号强度:-36dBm', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + 'SN:12', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF5F9FD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 40.rpx)), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + '蓝牙地址:48:CA:43:B1:B3:B2', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + 'mac:48CA43B1B3B0', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '网络:在线', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '传感器:', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + '可绑定', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFF1AD2B5), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + ].divide(SizedBox(width: 145.rpx)), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + '版本:02409.0301', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + Divider( + thickness: 2.rpx, + color: FlutterFlowTheme.of(context).alternate, + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + 'AITH-V3', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '信号强度:-36dBm', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + 'SN:12', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF5F9FD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 40.rpx)), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + '蓝牙地址:48:CA:43:B1:B3:B2', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + 'mac:48CA43B1B3B0', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '网络:在线', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '传感器:', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + '可绑定', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFF1AD2B5), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + ].divide(SizedBox(width: 145.rpx)), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Text( + '版本:02409.0301', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ].divide(SizedBox(height: 37.rpx)), + ), + ), + ); + } +} diff --git a/lib/pages/device_bind/componnet/FancyCircleCheckbox.dart b/lib/pages/device_bind/componnet/FancyCircleCheckbox.dart new file mode 100644 index 0000000..fec9480 --- /dev/null +++ b/lib/pages/device_bind/componnet/FancyCircleCheckbox.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class FancyCircleCheckbox extends StatefulWidget { + final bool value; + final ValueChanged onChanged; + final Color borderColor; + final Color fillColor; + + const FancyCircleCheckbox({ + Key? key, + required this.value, + required this.onChanged, + this.borderColor = Colors.white, + this.fillColor = Colors.blue, + }) : super(key: key); + + @override + State createState() => _FancyCircleCheckboxState(); +} + +class _FancyCircleCheckboxState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: Duration(milliseconds: 200), + vsync: this, + ); + _scaleAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + ); + + if (widget.value) { + _controller.forward(); + } + } + + @override + void didUpdateWidget(covariant FancyCircleCheckbox oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.value != oldWidget.value) { + widget.value ? _controller.forward() : _controller.reverse(); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => widget.onChanged(!widget.value), + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: widget.value + ? widget.borderColor + : widget.borderColor.withOpacity(0.5), + width: 1, + ), + ), + child: ScaleTransition( + scale: _scaleAnimation, + child: Container( + margin: EdgeInsets.all(5), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.fillColor, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart b/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart new file mode 100644 index 0000000..84dab75 --- /dev/null +++ b/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart @@ -0,0 +1,184 @@ +import 'package:ef/base/widget/flutterflow/FlutterFlowTheme.dart'; +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/model/BleDeviceData.dart'; +import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; + +class SingleBlueteethDeviceCompoentWidget extends StatefulWidget { + // final BleDeviceData device; + final bleDevice; + + const SingleBlueteethDeviceCompoentWidget({ + super.key, + // required this.device, + required this.bleDevice, + }); + + @override + State createState() => + _SingleBlueteethDeviceCompoentWidgetState(); +} + +class _SingleBlueteethDeviceCompoentWidgetState + extends State { + @override + Widget build(BuildContext context) { + var bleDevice = widget.bleDevice; + List rawData = + widget.bleDevice.advertisementData.manufacturerData[0xFFED]!; + BleDeviceData deviceData = parseBleData(rawData); + deviceData.name = widget.bleDevice.advertisementData.advName; + deviceData.rssi = widget.bleDevice.rssi; + deviceData.mac = deviceData.deviceId.replaceAll(':', ''); + BleDeviceData device = deviceData; + + ThemeController themeController = Get.find(); + return ClickableContainer( + backgroundColor: themeController.currentColor.sc5, + highlightColor: Colors.white, + borderRadius: 20.rpx, + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 0, 52.rpx), + onTap: () async { + //todo 请求绑定 + print(device.mac); + // try { + // await bleDevice.device.connect(autoConnect: false); + // List services = + // await bleDevice.device.discoverServices(); + // BluetoothCharacteristic? wifiListChar; + // BluetoothCharacteristic? wifiSsidChar; + // BluetoothCharacteristic? wifiPasswordChar; + // BluetoothCharacteristic? startProvisionChar; + + // for (BluetoothService service in services) { + // for (BluetoothCharacteristic c in service.characteristics) { + // if (c.uuid.toString() == 'YOUR_WIFI_LIST_UUID') { + // wifiListChar = c; + // } else if (c.uuid.toString() == 'YOUR_WIFI_SSID_UUID') { + // wifiSsidChar = c; + // } else if (c.uuid.toString() == 'YOUR_WIFI_PWD_UUID') { + // wifiPasswordChar = c; + // } else if (c.uuid.toString() == 'YOUR_WIFI_START_UUID') { + // startProvisionChar = c; + // } + // } + // } + // } catch (e) { + // print("蓝牙连接失败"); + // } + // + // Get.toNamed('/wifiPage', arguments: bleDevice.device); + showBindDoubleDialog(context,[]); + // showHaveBindDialog(context); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + device.name ?? '蓝牙绑定.默认设备名称'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + Row( + children: [ + Text( + "蓝牙绑定.信号强度".tr + ':${device.rssi ?? '-'}dBm', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + SizedBox(width: 40.rpx), + Text( + "蓝牙绑定.SN".tr + ':${device.sn ?? '-'}', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF5F9FD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + Text( + "蓝牙绑定.蓝牙地址".tr + ':${device.deviceId ?? '-'}', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + "蓝牙绑定.mac".tr + ':${device.mac ?? '-'}', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Row( + children: [ + Text( + "蓝牙绑定.网络".tr + + ':${device.isOnline == true ? '蓝牙绑定.在线'.tr : '蓝牙绑定.离线'.tr}', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + SizedBox(width: 145.rpx), + Row( + children: [ + Text( + "蓝牙绑定.传感器".tr + ":", + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFEBF2F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + Text( + device.status == 1 ? '蓝牙绑定.可绑定'.tr : '蓝牙绑定.已被绑定'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFF1AD2B5), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ], + ), + ], + ), + Text( + '版本:${device.version ?? '-'}', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(height: 37.rpx)), + ), + ); + } +} diff --git a/lib/pages/device_bind/componnet/bind_dialog.dart b/lib/pages/device_bind/componnet/bind_dialog.dart new file mode 100644 index 0000000..3db176f --- /dev/null +++ b/lib/pages/device_bind/componnet/bind_dialog.dart @@ -0,0 +1,324 @@ +import 'dart:ui'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/component/tool/FrostedDialog.dart'; +import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/model/BleDeviceData.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/FancyCircleCheckbox.dart'; + +void showBindDoubleDialog(BuildContext context, List devices) { + ThemeController themeController = Get.find(); + BlueteethBindController blueteethBindController = Get.find(); + + blueteethBindController.model.deviceIndex0 = true; + blueteethBindController.model.deviceIndex1 = false; + blueteethBindController.model.deviceIndex2 = false; + + showDialog( + context: context, + barrierDismissible: true, + barrierColor: Colors.black.withOpacity(0.5), // 建议加个背景模糊色 + builder: (BuildContext context) { + return FrostedDialog( + blurSigma: 3.0, + child: Container( + decoration: BoxDecoration( + color: themeController.currentColor.sc17, + borderRadius: BorderRadius.circular(20.0), + ), + padding: EdgeInsetsDirectional.fromSTEB(60.rpx, 0, 60.rpx, 0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.656, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 标题 + Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 93.rpx, 0, 0), + child: Text( + '蓝牙绑定.双人版绑定标题'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + ), + // 全选 + _buildCheckboxRow( + context, + title: '蓝牙绑定.绑定全部'.tr, + value: () => blueteethBindController.model.deviceIndex0!, + onChanged: (v) { + if (!blueteethBindController.model.deviceIndex0!) { + blueteethBindController.model.deviceIndex0 = v; + blueteethBindController.model.deviceIndex1 = !v; + blueteethBindController.model.deviceIndex2 = !v; + blueteethBindController.updateAll(); + } + }, + ), + // 主设备 + _buildCheckboxRow( + context, + title: '蓝牙绑定.主设备'.tr, + value: () => blueteethBindController.model.deviceIndex1!, + onChanged: (v) { + if (!blueteethBindController.model.deviceIndex1!) { + blueteethBindController.model.deviceIndex1 = v; + blueteethBindController.model.deviceIndex0 = !v; + blueteethBindController.model.deviceIndex2 = !v; + blueteethBindController.updateAll(); + } + }, + ), + // 从设备 + _buildCheckboxRow( + context, + title: '蓝牙绑定.从设备'.tr, + value: () => blueteethBindController.model.deviceIndex2!, + onChanged: (v) { + if (!blueteethBindController.model.deviceIndex2!) { + blueteethBindController.model.deviceIndex2 = v; + blueteethBindController.model.deviceIndex0 = !v; + blueteethBindController.model.deviceIndex1 = !v; + blueteethBindController.updateAll(); + } + }, + ), + // 确定按钮 + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 100.rpx, 0, 0), + child: _buildActionButton( + context, + text: '蓝牙绑定.确定'.tr, + onTap: () { + Get.back(); + }, + ), + ), + // 取消按钮 + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx), + child: _buildActionButton( + context, + text: '蓝牙绑定.取消'.tr, + onTap: () { + Get.back(); + }, + ), + ), + ], + ), + ), + ), + ); + }, + ); +} + + + +void showHaveBindDialog(BuildContext context) { + ThemeController themeController = Get.find(); + + showDialog( + context: context, + barrierDismissible: true, + barrierColor: Colors.black.withOpacity(0.5), + builder: (BuildContext context) { + return FrostedDialog( + blurSigma: 3.0, + child: Container( + decoration: BoxDecoration( + color: themeController.currentColor.sc17, + borderRadius: BorderRadius.circular(20.0), + ), + padding: EdgeInsetsDirectional.fromSTEB(60.rpx, 0, 60.rpx, 0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.656, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 93.rpx, 0.rpx, 0.rpx), + child: Text( + '蓝牙绑定.无法绑定'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 70.rpx, 0.rpx, 56.rpx), + child: RichText( + text: TextSpan(children: [ + TextSpan( + text: "蓝牙绑定.无法绑定1".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + TextSpan( + text: "蓝牙绑定.无法绑定2".tr, + style: TextStyle( + color: themeController.currentColor.sc10, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + TextSpan( + text: "蓝牙绑定.无法绑定3".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + ]), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx), + child: CustomCard( + borderRadius: AppConstants().button_container_radius, + onTap: () { + Get.back(); + }, + colors: [ + themeController.currentColor.sc1, + themeController.currentColor.sc2, + ], + child: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '蓝牙绑定.知道了'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + color: Colors.white, + fontFamily: 'Inter', + fontSize: + AppConstants().normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + width: 17.rpx, + )), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); +} + + +Widget _buildCheckboxRow( + BuildContext context, { + required String title, + required bool Function() value, + required void Function(bool) onChanged, +}) { + ThemeController themeController = Get.find(); + return Padding( + padding: EdgeInsetsDirectional.fromSTEB(60.rpx, 64.rpx, 0.rpx, 0.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Obx(() => FancyCircleCheckbox( + borderColor: themeController.currentColor.sc3, + fillColor: themeController.currentColor.sc2, + value: value(), + onChanged: onChanged, + )), + Text( + title, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: AppConstants().normal_text_fontSize, + color: themeController.currentColor.sc3, + ), + ), + ].divide(SizedBox(width: 21.rpx)), + ), + ); +} + +Widget _buildActionButton( + BuildContext context, { + required String text, + required VoidCallback onTap, +}) { + ThemeController themeController = Get.find(); + return CustomCard( + borderRadius: AppConstants().button_container_radius, + onTap: onTap, + colors: [ + themeController.currentColor.sc1, + themeController.currentColor.sc2, + ], + child: Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + text, + style: FlutterFlowTheme.of(context).bodyMedium.override( + color: Colors.white, + fontFamily: 'Inter', + fontSize: AppConstants().normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(width: 17.rpx)), + ), + ), + ); +} diff --git a/lib/pages/device_bind/device_type.dart b/lib/pages/device_bind/device_type.dart index 48a52e1..b13fd5f 100644 --- a/lib/pages/device_bind/device_type.dart +++ b/lib/pages/device_bind/device_type.dart @@ -1,12 +1,15 @@ import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/component/tool/FrostedDialog.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; class DeviceTypePage extends StatefulWidget { @@ -20,6 +23,7 @@ class _EPageState extends State { GlobalController globalController = Get.find(); UserInfoController userInfoController = Get.find(); BlueteethBindController blueteethBindController = Get.find(); + ThemeController themeController = Get.find(); @override void initState() { @@ -31,79 +35,84 @@ class _EPageState extends State { context: context, barrierDismissible: false, builder: (BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - backgroundColor: Colors.transparent, + return FrostedDialog( child: Padding( - padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), - child: Container( - width: double.infinity, - constraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * 0.656, - ), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Container( - width: double.infinity, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.656, + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Container( + width: double.infinity, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 26.rpx, 0, 0), + child: Container( + width: MediaQuery.sizeOf(context).width, + decoration: BoxDecoration( + color: Color(0xFFFBF5D5), + borderRadius: BorderRadius.circular( + AppConstants() + .normal_container_radius), // 圆角半径 + ), + child: Padding( padding: EdgeInsetsDirectional.fromSTEB( - 0, 26.rpx, 0, 0), - child: Container( - width: MediaQuery.sizeOf(context).width, - decoration: BoxDecoration( - color: Color(0xFFFBF5D5), - borderRadius: BorderRadius.circular( - AppConstants() - .normal_container_radius), // 圆角半径 - ), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 25.rpx, 25.rpx, 25.rpx, 25.rpx), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Icon( - Icons.volume_mute, - color: - FlutterFlowTheme.of(context) - .primaryText, - size: 30.rpx, + 25.rpx, 25.rpx, 25.rpx, 25.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0, 8.rpx, 0, 0), + child: Container( + width: 25.rpx, + height: 25.rpx, + // width: double.infinity, + decoration: BoxDecoration(), + child: SvgPicture.asset( + 'assets/img/icon/sound.svg', + fit: BoxFit.fill, + color: themeController + .currentColor.sc8, ), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '绑定引导.说明标题'.tr, - style: FlutterFlowTheme.of( - context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, - fontWeight: - FontWeight.w500, - color: - Colors.orange), - ), - Text( - '绑定引导.说明正文'.tr, - style: FlutterFlowTheme.of( - context) + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '绑定引导.说明标题'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + fontWeight: + FontWeight.w500, + color: Colors.orange), + ), + Text( + '绑定引导.说明正文'.tr, + style: + FlutterFlowTheme.of(context) .bodyMedium .override( fontFamily: 'Inter', @@ -111,162 +120,161 @@ class _EPageState extends State { .normal_text_fontSize, letterSpacing: 0.0, ), - ), - ].divide(SizedBox( - height: AppConstants() - .text_padding_up_dowm_p)), ), - ), - ].divide(SizedBox(width: 20.rpx)), + ].divide(SizedBox( + height: AppConstants() + .text_padding_up_dowm_p)), + ), ), - ), + ].divide(SizedBox(width: 20.rpx)), ), ), - Container( - width: double.infinity, - height: (MediaQuery.sizeOf(context).width) * - 0.13, - constraints: BoxConstraints( - minHeight: 200.rpx, - ), - child: ClipRRect( - borderRadius: - BorderRadius.circular(20.rpx), - // child: Image.network( - // 'https://picsum.photos/seed/861/600', - // fit: BoxFit.cover, - // ), - child: Image.asset( - "assets/img/help_op.png", - fit: BoxFit.cover, - ), - ), - ), - ].divide(SizedBox(height: 25.rpx)), + ), ), - ), + Container( + width: double.infinity, + height: + (MediaQuery.sizeOf(context).width) * 0.13, + constraints: BoxConstraints( + minHeight: 200.rpx, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.rpx), + // child: Image.network( + // 'https://picsum.photos/seed/861/600', + // fit: BoxFit.cover, + // ), + child: Image.asset( + "assets/img/help_op.png", + fit: BoxFit.cover, + ), + ), + ), + ].divide(SizedBox(height: 25.rpx)), ), ), - Flexible( - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0, 210.rpx, 0, 0), - child: Container( - width: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: double.infinity, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Theme( - data: ThemeData( - checkboxTheme: CheckboxThemeData( - visualDensity: - VisualDensity.compact, - materialTapTargetSize: - MaterialTapTargetSize - .shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(64), - ), - ), - unselectedWidgetColor: - Color(0xFFD3D3D3), + ), + ), + Flexible( + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 210.rpx, 0, 0), + child: Container( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: double.infinity, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Theme( + data: ThemeData( + checkboxTheme: CheckboxThemeData( + visualDensity: + VisualDensity.compact, + materialTapTargetSize: + MaterialTapTargetSize + .shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(64), ), - child: Obx( - () => Checkbox( - value: userInfoController - .model.register_agree ?? - false, - onChanged: (newValue) async { - userInfoController.model - .register_agree = - newValue; - userInfoController - .updateAll(); - // 获取设备信息,需要用户点击确认隐私协议与用户协议选择框时才能获取 - // if (newValue == true) { - // Deviceconfig - // .initPlatformState(); - // } - }, - side: BorderSide( - width: 1.5, - color: FlutterFlowTheme.of( - context) - .secondaryText, - ), - activeColor: - stringToColor("#16C89F"), - checkColor: - FlutterFlowTheme.of(context) - .info, - ), - )), - Text( - '绑定引导.不再提示'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - color: Colors.white), + ), + unselectedWidgetColor: + Color(0xFFD3D3D3), ), - ].divide(SizedBox(width: 22.rpx)), - ), - ), - CustomCard( - borderRadius: 50.rpx, - onTap: () async { - await Future.delayed( - Duration(milliseconds: 500)); - Get.back(); // 关闭当前弹窗或页面 - }, - colors: [ - //todo 颜色 - stringToColor("45D989"), - stringToColor("00C1AA") - ], // 单色背景也用渐变写法 - title: '', - child: Container( - width: MediaQuery.sizeOf(context).width * - 0.66, - height: - MediaQuery.sizeOf(context).height * - 0.055, - constraints: BoxConstraints( - minWidth: 500.rpx, - minHeight: 90.rpx, - ), - alignment: Alignment.center, // 居中对齐 - child: Text( - '绑定引导.跳过'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( + child: Obx( + () => Checkbox( + value: blueteethBindController + .model.read == + 1 + ? false + : true, + onChanged: (newValue) async { + blueteethBindController + .model.read = + newValue == true ? 0 : 1; + blueteethBindController + .updateAll(); + // 获取设备信息,需要用户点击确认隐私协议与用户协议选择框时才能获取 + // if (newValue == true) { + // Deviceconfig + // .initPlatformState(); + // } + }, + side: BorderSide( + width: 1.5, + color: + FlutterFlowTheme.of(context) + .secondaryText, + ), + activeColor: + stringToColor("#16C89F"), + checkColor: + FlutterFlowTheme.of(context) + .info, + ), + )), + Text( + '绑定引导.不再提示'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( fontFamily: 'Inter', fontSize: 26.rpx, letterSpacing: 0.0, - ), - ), + color: Colors.white), ), - ) - ].divide(SizedBox(height: 42.rpx)), + ].divide(SizedBox(width: 22.rpx)), + ), ), - ), + CustomCard( + borderRadius: 50.rpx, + onTap: () async { + await Future.delayed( + Duration(milliseconds: 500)); + Get.back(); // 关闭当前弹窗或页面 + }, + colors: [ + //todo 颜色 + stringToColor("45D989"), + stringToColor("00C1AA") + ], // 单色背景也用渐变写法 + + child: Container( + width: + MediaQuery.sizeOf(context).width * 0.66, + height: + MediaQuery.sizeOf(context).height * 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + alignment: Alignment.center, // 居中对齐 + child: Text( + '绑定引导.跳过'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ) + ].divide(SizedBox(height: 42.rpx)), ), ), - ], + ), ), - ), - )); + ], + ), + ), + )); }, ); } @@ -275,7 +283,7 @@ class _EPageState extends State { @override Widget build(BuildContext context) { - int read = blueteethBindController.model.read; + int read = blueteethBindController.model.read!; if (blueteethBindController.model.read == 1) { //需要弹窗显示教程 } @@ -378,11 +386,12 @@ class _EPageState extends State { if (type == '1') { Get.toNamed("/blueteethDevice"); } + if (type == '2') { + Get.toNamed("/wifiPage"); + } } }, - // colors: [Colors.white.withOpacity(0.06)], // 背景色 colors: [stringToColor("#242835")], // 背景色 - title: title, child: Container( width: double.infinity, height: MediaQuery.sizeOf(context).height * 0.135, diff --git a/lib/pages/device_bind/wifi_page.dart b/lib/pages/device_bind/wifi_page.dart new file mode 100644 index 0000000..fe7801b --- /dev/null +++ b/lib/pages/device_bind/wifi_page.dart @@ -0,0 +1,502 @@ +import 'dart:async'; + +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/Ble.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/person/person_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/common/util/Ble.dart' as ble; +import 'package:vbvs_app/pages/common/selectDialog.dart'; + +class WifiPage extends StatefulWidget { + // ble.ConnectedDeviceProp connectedDeviceProp; + BluetoothDevice bluetoothDevice; + // WifiPage({super.key, required this.connectedDeviceProp}); + WifiPage({super.key, required this.bluetoothDevice}); + + @override + State createState() => _WifiPageState(); +} + +class _WifiPageState extends State { + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + BlueteethBindController blueteethBindController = Get.find(); + PersonController personController = Get.find(); + ThemeController themeController = Get.find(); + late ble.ConnectedDeviceProp connectedDeviceProp; + + @override + void initState() { + super.initState(); + connectToDevice(widget.bluetoothDevice); + // connectedDeviceProp = widget.connectedDeviceProp; + Timer(const Duration(microseconds: 100), () { + getWifiList(); + }); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, bodySize) => GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgNoImg.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, // 加上这一行 + appBar: AppBar( + backgroundColor: stringToColor("#242835"), + // backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + iconTheme: IconThemeData(color: Colors.white), + titleSpacing: 0, + // leading: returnIconButtom, + title: Container( + // color: Colors.grey, + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + /// 居中标题 + Text( + 'wifi页.标题'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + + /// 左边返回按钮 + Positioned( + left: 0, + child: returnIconButtom, + ), + Positioned( + right: 20.rpx, + child: CustomCard( + borderRadius: 20.rpx, + onTap: () async { + Get.offAllNamed("/bindDeviceSuccess"); + }, + colors: [ + stringToColor("#45D989"), + stringToColor("#00C1AA"), + ], + child: Container( + width: 100.rpx, + height: 60.rpx, + alignment: Alignment.center, + padding: EdgeInsetsDirectional.fromSTEB( + 16.rpx, 0, 16.rpx, 0), + child: Text( + 'wifi页.跳过'.tr, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Inter Tight', + color: Colors.white, + letterSpacing: 0.0, + ), + ), + ), + ), + ), + ], + ), + ), + + actions: [], + centerTitle: false, + ), + + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Color(0xFF242835), + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "wifi页.WLAN".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: + AppConstants().title_text_fontSize, + ), + ), + Text( + "wifi页.未连接".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: + AppConstants().normal_text_fontSize, + ), + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.rpx, 25.rpx, 0.rpx, 0.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular(20.rpx), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '可用WLAN', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '6503', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Icon( + Icons.wifi_outlined, + size: 30.rpx, + color: themeController + .currentColor.sc3, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '6503', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Icon( + Icons.wifi_outlined, + size: 30.rpx, + color: themeController + .currentColor.sc3, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '6503', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Icon( + Icons.wifi_outlined, + size: 30.rpx, + color: themeController + .currentColor.sc3, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '6503', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Icon( + Icons.wifi_outlined, + size: 30.rpx, + color: themeController + .currentColor.sc3, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '6503', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Icon( + Icons.wifi_outlined, + size: 30.rpx, + color: themeController + .currentColor.sc3, + ), + ], + ), + ].divide(SizedBox(height: 67.rpx)), + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.arrow_back, + color: themeController.currentColor.sc3, + size: 30.rpx, + ), + Text( + '刷新', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3), + ), + ].divide(SizedBox(width: 26.rpx)), + ), + ].divide(SizedBox(height: 65.rpx)), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildDeviceCard(BuildContext context, + {required String title, required String imageUrl, required String type}) { + return CustomCard( + borderRadius: 20.rpx, // 圆角大小 + onTap: () { + if (type != null) { + if (type == '1') { + Get.toNamed("/blueteethDevice"); + } + } + }, + // colors: [Colors.white.withOpacity(0.06)], // 背景色 + colors: [stringToColor("#242835")], // 背景色 + + child: Container( + width: double.infinity, + height: MediaQuery.sizeOf(context).height * 0.135, + constraints: BoxConstraints( + minHeight: 220.rpx, + ), + padding: EdgeInsetsDirectional.fromSTEB(77.rpx, 0, 21.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFC2CED7), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8.rpx), + child: Image.asset( + imageUrl, + width: 212.rpx, + height: 168.rpx, + ), + ), + ], + ), + ), + ); + } + + getWifiList({int time = 3}) { + // LoadingDialog.show("扫描WIFI列表中...", icon: LoadingDialogIcon.wifi); + try { + var device = widget.bluetoothDevice; + String log = ""; + Function logAdd = (l) { + log += l; + }; + connectedDeviceProp.receiveLogArr.add(logAdd); + connectedDeviceProp.write3OfString("wscan scan", success: () { + Timer.periodic(const Duration(milliseconds: 1000), (timer) async { + if (timer.tick > 8) { + timer.cancel(); + connectedDeviceProp.receiveLogArr.remove(logAdd); + LoadingDialog.hide(); + if (time > 0) { + getWifiList(time: time - 1); + } + } + Iterable a = RegExp( + r'ITEM:SSID=([^\t]*)\s*RSSI=(\S*)\s*(,\s*auth\s*=\s*(\S*))?') + .allMatches(log); + if (a.isEmpty == false) { + await Future.delayed(const Duration(milliseconds: 500)); + a = RegExp( + r'ITEM:SSID=([^\t]*)\s*RSSI=(\S*)\s*(,\s*auth\s*=\s*(\S*))?') + .allMatches(log); + List arr = []; + for (RegExpMatch one in a) { + arr.add({"name": one[1], "num": one[2], "auth": one[4]}); + } + LoadingDialog.hide(); + blueteethBindController.model.wifiList = arr; + blueteethBindController.updateAll(); + connectedDeviceProp.receiveLogArr.remove(logAdd); + timer.cancel(); + checkIsCalibration(); + } + }); + }, fail: () { + connectedDeviceProp.receiveLogArr.remove(logAdd); + LoadingDialog.hide(); + }); + } catch (e) { + print(e); + } + } + + checkIsCalibration() { + // if (controller.model.bindArr[0] == "" || + // controller.model.bindArr[0] == null) { + // return; + // } + // if (controller.model.bindArr[2] == "" || + // controller.model.bindArr[2] == null) { + // return; + // } + // if (controller.model.connectedWifiName == "" || + // controller.model.connectedWifiName == null) { + // return; + // } + showCustomConfirmAndCancelDialog(context, "是否进行设备校准?", confirmName: "去校准") + .then((d) async { + // if (d == "confirm") { + // await Get.offAndToNamed("/calibration", arguments: [ + // controller.model.bindArr[1], + // controller.model.bindArr[2] + // ]); + // Get.find().getDeviceList(); + // } + }); + } +} diff --git a/lib/pages/login/login.dart b/lib/pages/login/login.dart index c27e3d1..923d8e1 100644 --- a/lib/pages/login/login.dart +++ b/lib/pages/login/login.dart @@ -7,6 +7,7 @@ import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/controller/login/login_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; @@ -20,6 +21,7 @@ class LoginPage extends StatefulWidget { class _EPageState extends State { GlobalController globalController = Get.find(); UserInfoController userInfoController = Get.find(); + LoginController loginController = Get.find(); @override Widget build(BuildContext context) { @@ -75,36 +77,6 @@ class _EPageState extends State { child: Column( mainAxisSize: MainAxisSize.max, children: [ - // ClickableContainer( - // backgroundColor: Colors.transparent, // 容器背景色为透明 - // highlightColor: Colors.pink, // 点击时背景色也为透明 - // padding: EdgeInsets.all(0), // 没有额外的内边距 - // onTap: () { - // // 你可以在这里定义点击事件的回调,比如关闭页面等 - // print('关闭按钮被点击'); - // }, - // borderRadius: 0, // 没有圆角 - // child: Container( - // // color: Colors.red, - // // width: double.infinity, // 使容器宽度充满父容器 - // child: Align( - // alignment: - // AlignmentDirectional(-1, 0), // 左对齐 - // child: Padding( - // padding: EdgeInsetsDirectional.fromSTEB( - // 0, 66.rpx, 0, 0), - // child: SvgPicture.asset( - // 'assets/img/icon/close.svg', - // width: 25.rpx, - // height: 25 - // .rpx, // 如果 SVG 中没有固定颜色,使用 color 设置 - // color: Colors.white, // 这里设置了颜色 - // ), - // ), - // ), - // ), - // ), - Align( alignment: AlignmentDirectional(-1, 0), child: Padding( @@ -190,16 +162,20 @@ class _EPageState extends State { borderRadius: AppConstants() .button_container_radius, // 圆角半径 onTap: () { - print('Button pressed ...'); - // Get.toNamed("/deviceType"); + bool agree = loginController + .model.register_agree!; + if (!agree) { + print('未授权 ...'); + } else { + print('已授权 ...'); + } }, colors: [ //todo 颜色 stringToColor("45D989"), stringToColor("00C1AA") ], // 渐变色是同一个色,也可以根据需要调整 - title: - '首页.蓝牙绑定'.tr, // 可选,虽然这个 title 没用,但可以作为调试用 + child: Container( width: // MediaQuery.sizeOf(context).width * 0.66, @@ -215,13 +191,6 @@ class _EPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ - // Icon( - // Icons.arrow_back, - // color: FlutterFlowTheme.of(context) - // .primaryText, - // size: 28.rpx, - // ), - Text( '登录页.本机号码一键登录/注册'.tr, style: FlutterFlowTheme.of(context) @@ -242,39 +211,6 @@ class _EPageState extends State { ), ), ), - // ClickableContainer( - // backgroundColor: Colors.transparent, // 背景色(透明) - // highlightColor: stringToColor( - // "#FF6347"), // 点击时的背景色,可以根据需求设置颜色 - // padding: EdgeInsetsDirectional.fromSTEB( - // 0, 32.rpx, 0, 32.rpx), // 内部间距 - // onTap: () { - // print('点击了“其他手机号码”'); // 点击后的回调事件 - // // 这里可以放置点击后的逻辑,比如导航等 - // }, - // borderRadius: 0.rpx, // 可选的圆角参数,默认是 20.rpx - // child: Align( - // alignment: AlignmentDirectional(-1, 0), - // child: Container( - // width: double.infinity, - // decoration: BoxDecoration(), - // child: Align( - // alignment: AlignmentDirectional(0, 0), - // child: Text( - // '登录页.其他手机号码'.tr, - // style: FlutterFlowTheme.of(context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // fontSize: 26.rpx, - // letterSpacing: 0.0, - // color: stringToColor("#FFFFFF"), - // ), - // ), - // ), - // ), - // ), - // ), SizedBox( height: 20.rpx, ), @@ -285,6 +221,7 @@ class _EPageState extends State { 16.rpx, 10.rpx, 16.rpx, 10.rpx), onTap: () { print('点击了容器'); + Get.toNamed("/otherLoginPage"); }, child: Text( '登录页.其他手机号码'.tr, // 子组件内容 @@ -306,14 +243,6 @@ class _EPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ - // SvgPicture.asset( - // 'assets/img/icon/tick.svg', - // width: 30.rpx, - // height: 30.rpx, // 如果 SVG 中没有固定颜色,可以这样设置 - // //todo 颜色 - // // color: Colors.white, - // color: Colors.white, - // ), Theme( data: ThemeData( checkboxTheme: CheckboxThemeData( @@ -332,13 +261,13 @@ class _EPageState extends State { ), child: Obx( () => Checkbox( - value: userInfoController + value: loginController .model.register_agree ?? false, onChanged: (newValue) async { - userInfoController.model + loginController.model .register_agree = newValue; - userInfoController.updateAll(); + loginController.updateAll(); // 获取设备信息,需要用户点击确认隐私协议与用户协议选择框时才能获取 // if (newValue == true) { // Deviceconfig @@ -358,7 +287,6 @@ class _EPageState extends State { .info, ), )), - Expanded( child: Padding( padding: diff --git a/lib/pages/login/other_login.dart b/lib/pages/login/other_login.dart new file mode 100644 index 0000000..0a98278 --- /dev/null +++ b/lib/pages/login/other_login.dart @@ -0,0 +1,801 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/controller/login/login_controller.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/controller/time/countdown_controller.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; + +class OtherLoginPage extends StatefulWidget { + const OtherLoginPage({super.key}); + + @override + State createState() => _OtherLoginPageState(); +} + +class _OtherLoginPageState extends State { + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + ThemeController themeController = Get.find(); + CountdownController countdownController = Get.find(); + LoginController loginController = Get.find(); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, bodysize) => GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgImage.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + top: true, + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(75.rpx, 0.rpx, 75.rpx, 0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + SizedBox( + height: 66.rpx, + ), + ClickableContainer( + backgroundColor: Colors.transparent, // 容器背景色 + highlightColor: Colors.green, // 点击时的背景色 + padding: + EdgeInsets.zero, // 这里去掉外部的 padding,避免影响点击范围 + onTap: () { + Get.back(); + }, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.rpx, 10.rpx, 16.rpx, 10.rpx), + child: SvgPicture.asset( + 'assets/img/icon/arrow_left.svg', + width: 25.rpx, + height: 25.rpx, // 如果 SVG 中没有固定颜色,使用 color 设置 + color: Colors.white, // 这里设置了颜色 + ), + ), + ), + ], + ), + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Align( + alignment: AlignmentDirectional(-1, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 141.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '登录页.欢迎使用太和e护'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 48.rpx, + letterSpacing: 0.0, + //todo 颜色 + color: stringToColor("#FFFFFF"), + ), + ), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(-1, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 15.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '登录页.科技睡眠 洞悉万千'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + //todo 颜色 + color: stringToColor("#FFFFFF"), + ), + ), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(-1, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 95.rpx, 0, 0), + child: Container( + width: double.infinity, + height: bodysize.maxHeight * 0.056, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + AppConstants() + .button_container_radius), + border: Border.all( + color: Colors.white, + width: 1.rpx, + ), + ), + constraints: BoxConstraints( + minHeight: 90.rpx, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 35.rpx, 0, 35.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + child: Align( + alignment: + AlignmentDirectional(-1, 0), + child: TextFormField( + autofocus: false, + obscureText: false, + decoration: InputDecoration( + isDense: true, + labelStyle: FlutterFlowTheme + .of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + hintText: '其他手机登录页.输入内容'.tr, + hintStyle: FlutterFlowTheme + .of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc4, + ), + enabledBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + focusedBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + errorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of( + context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of( + context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + filled: false, + fillColor: FlutterFlowTheme + .of(context) + .secondaryBackground, + ), + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + cursorColor: + FlutterFlowTheme.of( + context) + .primaryText, + // validator: _model + // .textControllerValidator + // .asValidator(context), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(-1, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 19.rpx, 0, 0), + child: Container( + height: bodysize.maxHeight * 0.056, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + AppConstants() + .button_container_radius), + border: Border.all( + color: Colors.white, + width: 1.rpx, + ), + ), + constraints: BoxConstraints( + minHeight: 90.rpx, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 35.rpx, 0, 35.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + child: Align( + alignment: + AlignmentDirectional(-1, 0), + child: TextFormField( + autofocus: false, + obscureText: false, + decoration: InputDecoration( + isDense: true, + labelStyle: FlutterFlowTheme + .of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + hintText: + '其他手机登录页.输入验证码'.tr, + hintStyle: FlutterFlowTheme + .of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc4, + ), + enabledBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + focusedBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + errorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of( + context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of( + context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular( + 8.rpx), + ), + filled: true, + fillColor: + Colors.transparent, + ), + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + cursorColor: + FlutterFlowTheme.of( + context) + .primaryText, + // validator: _model + // .textControllerValidator + // .asValidator(context), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 26.rpx, 0, 0, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + height: 50.rpx, + child: VerticalDivider( + thickness: 2.rpx, + color: themeController + .currentColor.sc7, + ), + ), + // Text( + // countdownController.countdown + // .value == + // 0 + // ? '其他手机登录页.获取验证码'.tr + // : '${countdownController.countdown.value}秒', + // style: TextStyle( + // fontFamily: 'Readex Pro', + // color: themeController + // .currentColor.sc7, + // fontSize: AppConstants() + // .title_text_fontSize, + // letterSpacing: 0, + // ), + // ), + Obx(() { + final CountdownController + countdownController = + Get.find< + CountdownController>(); + return InkWell( + onTap: () async { + if (countdownController + .countdown + .value != + 0) { + return; + } + + String msg = + await loginController + .getCode(context); + if (msg.isNotEmpty) { + return; + } + countdownController + .countdown + .value == + 0 + ? countdownController + .startCountdown( + AppConstants + .code_time) + : null; + }, + child: Container( + alignment: + Alignment.center, + constraints: + BoxConstraints( + minWidth: 150.rpx, + ), + child: Text( + countdownController + .countdown + .value == + 0 + ? '获取验证码' + : '${countdownController.countdown.value}秒', + style: TextStyle( + fontFamily: + 'Readex Pro', + color: + Color(0xFF333333), + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0, + ), + ), + ), + ); + }), + ].divide(SizedBox(width: 26.rpx)), + ), + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 35.rpx, 0, 0), + child: CustomCard( + borderRadius: AppConstants() + .button_container_radius, // 圆角半径 + onTap: () { + bool agree = + loginController.model.register_agree!; + if (!agree) { + print('未授权 ...'); + } else { + print('已授权 ...'); + } + }, + colors: [ + //todo 颜色 + stringToColor("45D989"), + stringToColor("00C1AA") + ], // 渐变色是同一个色,也可以根据需要调整 + + child: Container( + width: + // MediaQuery.sizeOf(context).width * 0.66, + bodysize.maxWidth, + height: MediaQuery.sizeOf(context).height * + 0.055, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + '其他手机登录页.登录'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + //todo 颜色 + color: Colors.white, + fontFamily: 'Inter', + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox( + width: 17.rpx, + )), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 25.rpx, 83.rpx, 25.rpx, 50.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Theme( + data: ThemeData( + checkboxTheme: CheckboxThemeData( + visualDensity: + VisualDensity.compact, + materialTapTargetSize: + MaterialTapTargetSize + .shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(64), + ), + ), + unselectedWidgetColor: + Color(0xFFD3D3D3), + ), + child: Obx( + () => Checkbox( + value: loginController + .model.register_agree ?? + false, + onChanged: (newValue) async { + loginController.model + .register_agree = newValue; + loginController.updateAll(); + // 获取设备信息,需要用户点击确认隐私协议与用户协议选择框时才能获取 + // if (newValue == true) { + // Deviceconfig + // .initPlatformState(); + // } + }, + side: BorderSide( + width: 1.5, + color: + FlutterFlowTheme.of(context) + .secondaryText, + ), + activeColor: + stringToColor("#FF9F66"), + checkColor: + FlutterFlowTheme.of(context) + .info, + ), + )), + Expanded( + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.rpx, 10.rpx, 0.rpx, 0.rpx), + child: Container( + width: bodysize.maxWidth, + constraints: BoxConstraints( + minWidth: 500.rpx, + minHeight: 90.rpx, + ), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '登录页.协议1'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: Colors + .white, // 可以调整为你想要的颜色 + ), + ), + TextSpan( + text: '登录页.协议2'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: stringToColor( + "#FF9F66"), + ), + ), + TextSpan( + text: '登录页.协议3'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: Colors + .white, // 可以调整为你想要的颜色 + ), + ), + TextSpan( + text: '登录页.协议4'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: stringToColor( + "#FF9F66"), + ), + ), + TextSpan( + text: '登录页.协议5'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: Colors + .white, // 可以调整为你想要的颜色 + ), + ), + TextSpan( + text: '登录页.协议6'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + fontSize: 26.rpx, + color: stringToColor( + "#FF9F66"), + ), + ), + ], + ), + ), + ), + ), + ), + ].divide(SizedBox(width: 18.rpx)), + ), + ), + ), + ], + ), + ), + ), + Container( + width: double.infinity, + height: MediaQuery.sizeOf(context).height * 0.136, + constraints: BoxConstraints( + minHeight: 220.rpx, + ), + decoration: BoxDecoration(), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0, 0, 36.rpx), + child: Text( + '登录页.其他登录方式'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + //todo 颜色 + color: stringToColor("#FFFFFF"), + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 91.rpx, + height: 91.rpx, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Image.asset( + "assets/img/wechat.png", + width: 30.rpx, + height: 30.rpx, + ), + ), + Container( + width: 91.rpx, + height: 91.rpx, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Image.asset( + "assets/img/tel.png", + width: 30.rpx, + height: 30.rpx, + ), + ), + Container( + width: 91.rpx, + height: 91.rpx, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Image.asset( + "assets/img/google.png", + width: 30.rpx, + height: 30.rpx, + ), + ), + ].divide(SizedBox(width: 35.rpx)), + ), + ], + ), + ), + ], + ), + ), + ), + ), + )), + ); + } +} diff --git a/lib/pages/main_bottom/home_page.dart b/lib/pages/main_bottom/home_page.dart index 577d3d1..8ac38df 100644 --- a/lib/pages/main_bottom/home_page.dart +++ b/lib/pages/main_bottom/home_page.dart @@ -5,6 +5,8 @@ import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/home_page/SleepDataModuleWidget.dart'; +import 'package:vbvs_app/component/home_page/SleepDateWidget.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/component/tool/CustomCard.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; @@ -53,6 +55,7 @@ class _HomePageState extends State { mainAxisSize: MainAxisSize.max, children: [ Padding( + //用户信息 padding: EdgeInsetsDirectional.fromSTEB( AppConstants().content_left_right_padding, 0, @@ -64,36 +67,60 @@ class _HomePageState extends State { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - CustomCard( - borderRadius: 20.rpx, - onTap: () async { - Get.toNamed("/loginPage"); - }, - title: '首页.登录' - .tr, // 虽然 title 传入了,但当前组件里没用它(可忽略或用于调试) - colors: [ - themeController.currentColor.sc1, - themeController.currentColor.sc2, - ], - child: Container( - width: 100.rpx, - height: 60.rpx, - alignment: Alignment.center, - padding: EdgeInsetsDirectional.fromSTEB( - 16.rpx, 0, 16.rpx, 0), - child: Text( - '首页.登录'.tr, - style: FlutterFlowTheme.of(context) - .titleSmall - .override( - fontFamily: 'Inter Tight', - color: themeController - .currentColor.sc19, - letterSpacing: 0.0, - ), + Obx(() { + return Visibility( + visible: + userInfoController.model.login == 0, + child: CustomCard( + borderRadius: 20.rpx, + onTap: () async { + Get.toNamed("/loginPage"); + }, + colors: [ + themeController.currentColor.sc1, + themeController.currentColor.sc2, + ], + child: Container( + width: 100.rpx, + height: 60.rpx, + alignment: Alignment.center, + padding: EdgeInsetsDirectional.fromSTEB( + 16.rpx, 0, 16.rpx, 0), + child: Text( + '首页.登录'.tr, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Inter Tight', + color: themeController + .currentColor.sc19, + letterSpacing: 0.0, + ), + ), + ), ), - ), - ), + ); + }), + Obx(() { + return Visibility( + visible: + userInfoController.model.login == 1, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "飞行的鱼", + style: TextStyle(color: Colors.white), + ), + Text( + "嘉兴 晴", + style: TextStyle(color: Colors.white), + ), + ], + ), + ); + }), SvgPicture.asset( 'assets/img/icon/add.svg', width: 39.rpx, @@ -106,6 +133,7 @@ class _HomePageState extends State { ), ), Padding( + //绑定数量 padding: EdgeInsetsDirectional.fromSTEB( 19.rpx, 34.rpx, 0, 21.rpx), child: ClickableContainer( @@ -116,42 +144,67 @@ class _HomePageState extends State { }, padding: EdgeInsetsDirectional.fromSTEB( 0.rpx, 10.rpx, 0, 10.rpx), - child: Container( - child: Row( - mainAxisSize: MainAxisSize.min, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '首页.已关联体征监测设备'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: - AppConstants().title_text_fontSize, - letterSpacing: 0.0, - //todo 颜色 - color: themeController.currentColor.sc3, + Container( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '首页.已关联体征监测设备'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + //todo 颜色 + color: themeController + .currentColor.sc3, + ), + ), + Text( + '0', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + color: themeController + .currentColor.sc8, + ), + ), + ].divide(SizedBox( + width: 6.rpx, + )), + )), + Obx(() { + return Visibility( + visible: userInfoController + .model.deviceBindNum! > + 0, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 8.rpx, 0.rpx), + child: SvgPicture.asset( + 'assets/img/icon/arrow_right.svg', + width: 14.rpx, + height: 14.rpx, // 如果 SVG 中没有固定颜色,可以这样设置 + color: Colors.white, ), - ), - Text( - '0', - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: - AppConstants().title_text_fontSize, - letterSpacing: 0.0, - color: themeController.currentColor.sc8, - ), - ), - ].divide(SizedBox( - width: 6.rpx, - )), - )), + ), + ); + }), + ], + ), ), ), Container( + //未绑定布局 width: MediaQuery.sizeOf(context).width, height: MediaQuery.sizeOf(context).height * 0.277, constraints: BoxConstraints( @@ -178,8 +231,7 @@ class _HomePageState extends State { themeController.currentColor.sc1, themeController.currentColor.sc2, ], - title: - '首页.蓝牙绑定'.tr, // 可选,虽然这个 title 没用,但可以作为调试用 + child: Container( width: MediaQuery.sizeOf(context).width * 0.66, @@ -229,8 +281,7 @@ class _HomePageState extends State { themeController.currentColor.sc1, themeController.currentColor.sc2, ], // 渐变色是同一个色,也可以根据需要调整 - title: - '首页.蓝牙绑定'.tr, // 可选,虽然这个 title 没用,但可以作为调试用 + child: Container( width: MediaQuery.sizeOf(context).width * 0.66, @@ -278,6 +329,7 @@ class _HomePageState extends State { ), ), Padding( + //未绑定标语 padding: EdgeInsetsDirectional.fromSTEB(0, 26.rpx, 0, 0), child: Container( @@ -374,6 +426,213 @@ class _HomePageState extends State { ), ), ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 26.rpx, 0, 0), + child: Container( + // color: Colors.red, + width: bodySize.maxWidth, + height: bodySize.maxHeight * 0.107, + constraints: BoxConstraints( + minHeight: 240.rpx, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClickableContainer( + backgroundColor: + themeController.currentColor.sc5, + highlightColor: + themeController.currentColor.sc3, + borderRadius: + AppConstants().normal_container_radius, + padding: + EdgeInsets.zero, // 原始Container没有padding + onTap: () { + // 点击逻辑放这里 + }, + child: Container( + width: bodySize.maxWidth * 0.445, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + SizedBox(height: 32.rpx), + Container( + width: 120.rpx, + height: 120.rpx, + child: Image.asset( + "assets/img/netlove.png", + fit: BoxFit.cover, + ), + ), + Text( + "首页.我的e护".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + ), + ), + SizedBox(height: 32.rpx), + ], + ), + ), + ), + ClickableContainer( + backgroundColor: + themeController.currentColor.sc5, + highlightColor: + themeController.currentColor.sc3, + borderRadius: + AppConstants().normal_container_radius, + padding: EdgeInsets + .zero, // 原本的Container没有 padding,这里设置为 zero + onTap: () { + // TODO: 替换为你需要的点击事件逻辑 + print("云关爱 被点击"); + }, + child: SizedBox( + width: bodySize.maxWidth * 0.445, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + width: 120.rpx, + height: 120.rpx, + child: Image.asset( + "assets/img/mye.png", + fit: BoxFit.cover, + ), + ), + Text( + "首页.云关爱".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + ), + ), + ] + .addToStart(SizedBox(height: 32.rpx)) + .addToEnd(SizedBox(height: 32.rpx)), + ), + ), + ), + ], + ), + ), + ), + // Generated code for this Container Widget... + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 25.rpx, 0, 25.rpx), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular( + AppConstants().normal_container_radius), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: double.infinity, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '次卧/1201/李小北', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + '首页.报告详情'.tr, + style: + FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0, 6.rpx, 0, 0.rpx), + child: SvgPicture.asset( + 'assets/img/icon/arrow_right.svg', + width: 14.rpx, + height: 14 + .rpx, // 如果 SVG 中没有固定颜色,可以这样设置 + color: Colors.white, + ), + ), + ].divide(SizedBox(width: 22.rpx)), + ), + ], + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + SleepDateWidget(), + SleepDateWidget(), + SleepDateWidget(), + SleepDateWidget(), + SleepDateWidget(), + SleepDateWidget(), + ].divide(SizedBox( + width: 20.rpx, + )), + ), + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + SleepDataModuleWidget(), + SleepDataModuleWidget(), + SleepDataModuleWidget(), + SleepDataModuleWidget(), + SleepDataModuleWidget(), + ].divide(SizedBox( + width: 14.rpx)), // ✅ 这里加了 .rpx + ), + ), + ), + ], + ), + ), + ), + ) ], ), ), diff --git a/lib/pages/main_bottom/main_page_bottom_change.dart b/lib/pages/main_bottom/main_page_bottom_change.dart index e1dc519..de47fd5 100644 --- a/lib/pages/main_bottom/main_page_bottom_change.dart +++ b/lib/pages/main_bottom/main_page_bottom_change.dart @@ -90,47 +90,56 @@ class MainPageBottomChange extends GetView { floatingActionButton: Container(), ); } else { - return Scaffold( - backgroundColor: Colors.transparent, - body: arr[controller.model.currentIndex], - floatingActionButtonAnimator: - FloatingActionButtonAnimator.noAnimation, - floatingActionButtonLocation: - FloatingActionButtonLocation.centerDocked, - bottomNavigationBar: Theme( - data: ThemeData( - splashFactory: NoSplash.splashFactory, - highlightColor: Colors.transparent), - child: BottomNavigationBar( - unselectedItemColor: themeController.currentColor.sc4, - selectedItemColor: themeController.currentColor.sc1, - backgroundColor: themeController.currentColor.sc5, - selectedFontSize: 26.rpx, - unselectedFontSize: 26.rpx, - type: BottomNavigationBarType.fixed, - currentIndex: controller.model.currentIndex, - onTap: (index) { - Future.delayed(const Duration(milliseconds: 500), () { - if (controller.model.currentIndex != 1) { - globalController.model.hideBottomNavigationBar = false; - globalController.updateAll(); - } - }); - controller.model.currentIndex = index; - controller.updateAll(); - }, - items: [ - getBottomNavigationBarItem("assets/img/menu/home.svg", - "assets/img/menu/n_home.svg", "菜单.首页".tr), - // getBottomNavigationBarItem("assets/img/menu/report.svg", - // "assets/img/menu/n_report.svg", "菜单.报告".tr), - getBottomNavigationBarItem("assets/img/menu/e.svg", - "assets/img/menu/n_e.svg", "菜单.小e".tr), - getBottomNavigationBarItem("assets/img/menu/message.svg", - "assets/img/menu/n_message.svg", "菜单.消息".tr), - getBottomNavigationBarItem("assets/img/menu/mine.svg", - "assets/img/menu/n_mine.svg", "菜单.我的".tr), - ], + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgImage.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + body: arr[controller.model.currentIndex], + floatingActionButtonAnimator: + FloatingActionButtonAnimator.noAnimation, + floatingActionButtonLocation: + FloatingActionButtonLocation.centerDocked, + bottomNavigationBar: Theme( + data: ThemeData( + splashFactory: NoSplash.splashFactory, + highlightColor: Colors.transparent), + child: BottomNavigationBar( + unselectedItemColor: themeController.currentColor.sc4, + selectedItemColor: themeController.currentColor.sc1, + backgroundColor: themeController.currentColor.sc5, + selectedFontSize: 26.rpx, + unselectedFontSize: 26.rpx, + type: BottomNavigationBarType.fixed, + currentIndex: controller.model.currentIndex, + onTap: (index) { + Future.delayed(const Duration(milliseconds: 500), () { + if (controller.model.currentIndex != 1) { + globalController.model.hideBottomNavigationBar = + false; + globalController.updateAll(); + } + }); + controller.model.currentIndex = index; + controller.updateAll(); + }, + items: [ + getBottomNavigationBarItem("assets/img/menu/home.svg", + "assets/img/menu/n_home.svg", "菜单.首页".tr), + // getBottomNavigationBarItem("assets/img/menu/report.svg", + // "assets/img/menu/n_report.svg", "菜单.报告".tr), + getBottomNavigationBarItem("assets/img/menu/e.svg", + "assets/img/menu/n_e.svg", "菜单.小e".tr), + getBottomNavigationBarItem("assets/img/menu/message.svg", + "assets/img/menu/n_message.svg", "菜单.消息".tr), + getBottomNavigationBarItem("assets/img/menu/mine.svg", + "assets/img/menu/n_mine.svg", "菜单.我的".tr), + ], + ), ), ), ); diff --git a/lib/pages/person/person_page.dart b/lib/pages/person/person_page.dart new file mode 100644 index 0000000..e29fa35 --- /dev/null +++ b/lib/pages/person/person_page.dart @@ -0,0 +1,619 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/CustomCard.dart'; +import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; +import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; +import 'package:vbvs_app/controller/person/person_controller.dart'; +import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/pages/person/select_time.dart'; + +class PersonPage extends StatefulWidget { + const PersonPage({super.key}); + + @override + State createState() => _EPageState(); +} + +class _EPageState extends State { + GlobalController globalController = Get.find(); + UserInfoController userInfoController = Get.find(); + BlueteethBindController blueteethBindController = Get.find(); + PersonController personController = Get.find(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, bodySize) => GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgNoImg.png'), // 本地图片 + fit: BoxFit.fill, // 填满整个 Container + ), + ), + child: Scaffold( + backgroundColor: Colors.transparent, // 加上这一行 + appBar: AppBar( + backgroundColor: stringToColor("#242835"), + // backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + iconTheme: IconThemeData(color: Colors.white), + titleSpacing: 0, + // leading: returnIconButtom, + title: Container( + // color: Colors.grey, + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, + children: [ + /// 居中标题 + Text( + '人员资料.标题'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + + /// 左边返回按钮 + Positioned( + left: 0, + child: returnIconButtom, + ), + Positioned( + right: 20.rpx, + child: CustomCard( + borderRadius: 20.rpx, + onTap: () async { + Get.offAllNamed("/bindDeviceSuccess"); + }, + + colors: [ + stringToColor("#45D989"), + stringToColor("#00C1AA"), + ], + child: Container( + width: 100.rpx, + height: 60.rpx, + alignment: Alignment.center, + padding: EdgeInsetsDirectional.fromSTEB( + 16.rpx, 0, 16.rpx, 0), + child: Text( + '人员资料.保存'.tr, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Inter Tight', + color: Colors.white, + letterSpacing: 0.0, + ), + ), + ), + ), + ), + ], + ), + ), + + actions: [], + centerTitle: false, + ), + + body: SafeArea( + top: true, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 141.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: Color(0xFFF3EDED), + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: TextFormField( + // controller: _model.textController1, + // focusNode: _model.textFieldFocusNode1, + autofocus: false, + obscureText: false, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + labelStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: Colors.white), + hintText: '人员资料.名字输入提示'.tr, + hintStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context).error, + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context).error, + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + filled: true, + ), + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: Colors.white), + textAlign: TextAlign.center, + cursorColor: + FlutterFlowTheme.of(context).primaryText, + // validator: _model.textController1Validator + // .asValidator(context), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 90.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Image.asset( + "assets/img/man.png", + fit: BoxFit.cover, + ), + ), + ), + Text( + '男', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Colors.white, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(height: 14.rpx)), + ), + Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Image.asset( + "assets/img/woman.png", + fit: BoxFit.cover, + ), + ), + ), + Text( + '女', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Colors.white, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(height: 14.rpx)), + ), + ].divide(SizedBox(width: 170.rpx)), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 50.rpx, 70.rpx, 0), + // child: Container( + // width: double.infinity, + // height: 100.rpx, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(50.rpx), + // border: Border.all( + // color: Color(0xFFF3EDED), + // ), + // ), + // child: Align( + // alignment: AlignmentDirectional(0, 0), + // child: Obx( + // () => Container( + // width: double.infinity, + // height: + // MediaQuery.sizeOf(context).height * 0.064, + // decoration: BoxDecoration(), + // child: InkWell( + // onTap: () { + // // 触摸收起键盘 + // FocusScope.of(context) + // .requestFocus(FocusNode()); + // Future.delayed( + // const Duration(milliseconds: 250), () { + // // 延迟执行的代码 + // showDateSelectionDialog(context, + // checkDate: + // personController.model.birthday ?? + // DateTime.now(), + // checkChange: (DateTime d) { + // personController.model.birthday = d; + // personController.updateAll(); + // print("$d"); + // }).then((d) { + // // Timer(Duration.zero, () { + // // FocusScope.of(context).unfocus(); + // // }); + // }); + // }); + // }, + // child: Container( + // constraints: + // BoxConstraints(minWidth: 200.rpx), + // child: Text( + // personController.model.birthday != null + // ? DateFormat("yyyy年MM月dd日").format( + // personController.model.birthday!) + // : '人员资料.生日输入提示'.tr, + // textAlign: TextAlign.right, + // style: FlutterFlowTheme.of(context) + // .bodyMedium + // .override( + // fontFamily: 'Readex Pro', + // color: Colors.white, + // fontSize: 30.rpx, + // letterSpacing: 0, + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ), + + child: Container( + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all(color: Color(0xFFF3EDED)), + ), + child: InkWell( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + Future.delayed(Duration(milliseconds: 250), () { + showDateSelectionDialog( + context, + checkDate: personController.model.birthday ?? + DateTime.now(), + checkChange: (DateTime d) { + personController.model.birthday = d; + personController.updateAll(); + }, + ); + }); + }, + child: Center( + child: Text( + personController.model.birthday != null + ? DateFormat("yyyy年MM月dd日").format( + personController.model.birthday!) + : '人员资料.生日输入提示'.tr, + textAlign: TextAlign.right, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: personController.model.birthday != + null + ? Colors.white + : Colors.grey, + fontSize: + AppConstants().normal_text_fontSize, + letterSpacing: 0, + ), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 18.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: Color(0xFFF3EDED), + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: TextFormField( + // controller: _model.textController3, + // focusNode: _model.textFieldFocusNode3, + autofocus: false, + obscureText: false, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + labelStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + ), + hintText: '人员资料.体重输入提示'.tr, + hintStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + color: personController.model.birthday != + null + ? Colors.white + : Colors.grey, + fontSize: + AppConstants().normal_text_fontSize, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context).error, + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context).error, + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8.rpx), + ), + filled: true, + ), + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: Colors.white, + ), + textAlign: TextAlign.center, + cursorColor: + FlutterFlowTheme.of(context).primaryText, + // validator: _model.textController3Validator + // .asValidator(context), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 117.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '人员资料.疾病标题'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFF3F4F5), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 70.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0, 152.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.rpx), + border: Border.all( + color: Color(0xFFE9E3E3), + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 8.rpx, 0, 0), + child: Icon( + Icons.arrow_back, + color: Color(0xFFE4EBF0), + size: 24.rpx, + ), + ), + Expanded( + child: Text( + '人员资料.提示'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFEEF3F8), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ].divide(SizedBox(width: 23.rpx)), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildDeviceCard(BuildContext context, + {required String title, required String imageUrl, required String type}) { + return CustomCard( + borderRadius: 20.rpx, // 圆角大小 + onTap: () { + if (type != null) { + if (type == '1') { + Get.toNamed("/blueteethDevice"); + } + } + }, + // colors: [Colors.white.withOpacity(0.06)], // 背景色 + colors: [stringToColor("#242835")], // 背景色 + + child: Container( + width: double.infinity, + height: MediaQuery.sizeOf(context).height * 0.135, + constraints: BoxConstraints( + minHeight: 220.rpx, + ), + padding: EdgeInsetsDirectional.fromSTEB(77.rpx, 0, 21.rpx, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFC2CED7), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8.rpx), + child: Image.asset( + imageUrl, + width: 212.rpx, + height: 168.rpx, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/person/select_time.dart b/lib/pages/person/select_time.dart new file mode 100644 index 0000000..073374f --- /dev/null +++ b/lib/pages/person/select_time.dart @@ -0,0 +1,290 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; + +Future showDateSelectionDialog(BuildContext context, + {required DateTime checkDate, Function? checkChange, String title = "生日"}) { + Color checkColor = stringToColor("#D3B684"); + List years = [], months = [], days = []; + var days_select = [].obs; + int day_len = 31; + int year = DateTime.now().year; + for (var i = 0; i < 100; i++) { + years.insert(0, year - i); + } + for (var i = 1; i < 13; i++) { + months.add(i); + } + for (var i = 1; i < 32; i++) { + days.add(i); + } + int yearIndex = years.lastIndexOf(checkDate.year); + int monthIndex = months.lastIndexOf(checkDate.month); + day_len = DateTime.fromMillisecondsSinceEpoch( + DateTime(years[yearIndex], months[monthIndex] + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = days.sublist(0, day_len); + int dayIndex = days.lastIndexOf(checkDate.day); + ThemeController themeController = Get.find(); + return showDialog( + // barrierColor: stringToColor("#000320"), + context: context, + barrierDismissible: true, // 点击对话框外部可关闭 + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, // 控制弹窗距离顶部的位置 + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: stringToColor("#242835"), + // backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular( + AppConstants().normal_container_radius), + topRight: Radius.circular( + AppConstants().normal_container_radius), + bottomLeft: Radius.circular(0.rpx), + bottomRight: Radius.circular(0.rpx), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + color: themeController.currentColor.sc5, + alignment: Alignment.centerLeft, + height: 80.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + InkWell( + child: Text( + "日期.取消".tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: AppConstants() + .normal_text_fontSize), + ), + onTap: () { + Get.back(); + }, + ), + Text( + "$title", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: + AppConstants().title_text_fontSize), + ), + // closeIconWhite, + InkWell( + child: Text( + "日期.确定".tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: AppConstants() + .normal_text_fontSize), + ), + onTap: () { + checkChange?.call(DateTime(years[yearIndex], + months[monthIndex], days[dayIndex])); + Get.back(); + }, + ) + ], + ), + ), + Container( + height: 240.rpx, + margin: EdgeInsets.only(top: 60.rpx, bottom: 60.rpx), + padding: EdgeInsets.only(left: 30.rpx, right: 30.rpx), + child: Container( + child: Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 40.rpx, right: 30.rpx), + child: getOnePicker(context, years, yearIndex, + (d) { + yearIndex = d; + dayIndex = 0; + day_len = + DateTime.fromMillisecondsSinceEpoch( + DateTime( + years[yearIndex], + months[monthIndex] + + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = + days.sublist(0, day_len); + }), + ), + ), + Container( + child: Text( + "年", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 30.rpx, right: 30.rpx), + child: getOnePicker( + context, months, monthIndex, (d) { + monthIndex = d; + dayIndex = 0; + day_len = + DateTime.fromMillisecondsSinceEpoch( + DateTime( + years[yearIndex], + months[monthIndex] + + 1) + .millisecondsSinceEpoch - + 1000) + .day; + days_select.value = + days.sublist(0, day_len); + }), + ), + ), + Container( + child: Text( + "月", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 30.rpx, right: 40.rpx), + child: Obx( + () { + // print("${dayIndex} ${day_len}"); + return getOnePicker( + context, + days_select, + dayIndex, + (d) { + dayIndex = d; + }, + ); + }, + ), + ), + ), + Container( + child: Text( + "日", + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} + +getOnePicker(context, List arr, int checkIndex, Function onSelectedItemChanged, + {bool looping = false}) { + return CupertinoPicker( + key: UniqueKey(), + useMagnifier: false, + itemExtent: 80.rpx, + magnification: 1, + diameterRatio: 3, + squeeze: 1, + looping: looping, + scrollController: FixedExtentScrollController(initialItem: checkIndex), + selectionOverlay: Container(), + onSelectedItemChanged: (int value) { + // print("$value"); + onSelectedItemChanged.call(value); + }, + children: [ + ...List.generate(arr.length, (index) { + return Container( + alignment: Alignment.center, + width: 400.rpx, + decoration: BoxDecoration( + // border: Border( + // bottom: index != arr.length + // ? BorderSide( + // color: stringToColor("#8D95B0"), + // ) + // : BorderSide.none, + // ), + ), + child: Text("${arr[index]}", + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: Colors.white, + letterSpacing: 0, + fontSize: 30.rpx)), + ); + }) + ], + ); +} diff --git a/lib/routers/routers.dart b/lib/routers/routers.dart index 55b7209..30b63f8 100644 --- a/lib/routers/routers.dart +++ b/lib/routers/routers.dart @@ -2,7 +2,9 @@ import 'package:flutter/cupertino.dart'; import 'package:vbvs_app/pages/device_bind/bind_device_success.dart'; import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart'; import 'package:vbvs_app/pages/device_bind/device_type.dart'; +import 'package:vbvs_app/pages/device_bind/wifi_page.dart'; import 'package:vbvs_app/pages/login/login.dart'; +import 'package:vbvs_app/pages/login/other_login.dart'; import 'package:vbvs_app/pages/main_bottom/e_page.dart'; import 'package:vbvs_app/pages/main_bottom/home_page.dart'; import 'package:vbvs_app/pages/main_bottom/main_page_bottom_change.dart'; @@ -26,6 +28,9 @@ var routes = { "/blueteethDevice": (contxt) => BlueteethDevicePage(), "/personPage": (contxt) => PersonPage(), "/bindDeviceSuccess": (contxt) => BindDeviceSuccess(), + // "/wifiPage": (contxt, {arguments}) => WifiPage(connectedDeviceProp: arguments), + "/wifiPage": (contxt, {arguments}) => WifiPage(bluetoothDevice: arguments), + "/otherLoginPage": (contxt) => OtherLoginPage(), }; //2、配置onGenerateRoute 固定写法 这个方法也相当于一个中间件,这里可以做权限判断 diff --git a/pubspec.yaml b/pubspec.yaml index 0d85cb2..5e7383d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: lottie: ^3.2.0 flutter_blue_plus: ^1.35.3 permission_handler: ^12.0.0+1 + # geolocator: ^13.0.4 dev_dependencies: