status_bar.dart 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import 'dart:async';
  2. import 'package:connectivity_plus/connectivity_plus.dart';
  3. import 'package:fis_common/http/options.dart';
  4. import 'package:fis_jsonrpc/http_pool.dart';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:vitalapp/architecture/network_connectivity.dart';
  8. import 'package:vitalapp/global.dart';
  9. import 'package:vitalapp/pages/help/help_dialog.dart';
  10. import 'package:fis_common/logger/logger.dart';
  11. import 'package:vitalapp/rpc.dart';
  12. import 'package:wifi_iot/wifi_iot.dart';
  13. class HeaderStatusBar extends StatelessWidget {
  14. const HeaderStatusBar({super.key});
  15. @override
  16. Widget build(BuildContext context) {
  17. return Row(
  18. mainAxisSize: MainAxisSize.min,
  19. crossAxisAlignment: CrossAxisAlignment.center,
  20. mainAxisAlignment: MainAxisAlignment.center,
  21. children: [
  22. _NetStatusWidget(),
  23. const SizedBox(width: 2),
  24. IconButton(
  25. onPressed: () {
  26. HelpDialog.show();
  27. },
  28. padding: EdgeInsets.all(0),
  29. icon: const Icon(Icons.help, size: 36, color: Colors.white),
  30. ),
  31. ],
  32. );
  33. }
  34. }
  35. class _NetStatusWidget extends StatefulWidget {
  36. @override
  37. State<StatefulWidget> createState() => _NetStatusWidgetState();
  38. }
  39. class _NetStatusWidgetState extends State<_NetStatusWidget> {
  40. Timer? _waitTimer;
  41. bool _isOnline = false;
  42. bool _isServerConnectable = true;
  43. @override
  44. void initState() {
  45. super.initState();
  46. _isOnline = kIsOnline;
  47. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  48. netChecker.onlineChangedEvent.addListener(_onlineChanged);
  49. if (_isOnline) {
  50. _checkServerConnectable();
  51. }
  52. });
  53. }
  54. @override
  55. void dispose() {
  56. netChecker.onlineChangedEvent.removeListener(_onlineChanged);
  57. super.dispose();
  58. }
  59. @override
  60. Widget build(BuildContext context) {
  61. return Tooltip(
  62. triggerMode: TooltipTriggerMode.tap,
  63. message: _buildTipsText(),
  64. child: Column(
  65. mainAxisAlignment: MainAxisAlignment.center,
  66. children: [
  67. _buildIcon(),
  68. _buildWifiName(),
  69. ],
  70. ),
  71. );
  72. }
  73. String _buildTipsText() {
  74. if (_isOnline) {
  75. if (_isServerConnectable) {
  76. return "网络已连接";
  77. } else {
  78. return "无法连接服务器";
  79. }
  80. } else {
  81. if (_waitTimer == null) {
  82. return "网络已断开";
  83. } else {
  84. return "网络等待中";
  85. }
  86. }
  87. }
  88. Widget _buildIcon() {
  89. if (_isOnline) {
  90. if (!_isServerConnectable) {
  91. return Icon(Icons.wifi_off_outlined, size: 36, color: Colors.red);
  92. }
  93. if (netChecker.status == ConnectivityResult.mobile) {
  94. return _NetworkIcon4G();
  95. }
  96. return Icon(Icons.wifi, size: 36, color: Colors.green);
  97. }
  98. if (_waitTimer == null) {
  99. return Icon(Icons.wifi_off_outlined, size: 36, color: Colors.red);
  100. } else {
  101. return SizedBox(width: 36, height: 36, child: _NetWaitingWidget());
  102. }
  103. }
  104. Widget _buildWifiName() {
  105. if (netChecker.status != ConnectivityResult.wifi) {
  106. return const SizedBox();
  107. }
  108. return _WifiNameWidget();
  109. }
  110. void _onlineChanged(_, e) {
  111. if (_isOnline != e) {
  112. setState(() {
  113. _isOnline = e;
  114. });
  115. }
  116. if (e) {
  117. _stopWaitNetRecover();
  118. _checkServerConnectable();
  119. } else {
  120. _waitNetRecover();
  121. }
  122. }
  123. void _waitNetRecover() {
  124. if (_waitTimer != null) {
  125. return;
  126. }
  127. logger.i("HeaderStatusBar - Wait network start.");
  128. print("HeaderStatusBar - Wait network start.");
  129. _waitTimer = Timer(const Duration(milliseconds: 5 * 1000), () {
  130. setState(() {
  131. _waitTimer = null;
  132. });
  133. logger.i("HeaderStatusBar - Wait network done.");
  134. print("HeaderStatusBar - Wait network done.");
  135. });
  136. }
  137. void _stopWaitNetRecover() {
  138. if (_waitTimer != null) {
  139. _waitTimer?.cancel();
  140. _waitTimer = null;
  141. logger.i("HeaderStatusBar - Wait network abort.");
  142. print("HeaderStatusBar - Wait network abort.");
  143. }
  144. }
  145. Future<void> _checkServerConnectable() async {
  146. final result = await _tryConnectRpcOnce();
  147. setState(() {
  148. _isServerConnectable = result;
  149. });
  150. }
  151. /// 检验服务有效性
  152. Future<bool> _tryConnectRpcOnce() async {
  153. print("${netChecker.status} _tryConnectRpcOnce: ${DateTime.now()}");
  154. try {
  155. final reqJson =
  156. '{"jsonrpc":"2.0","method":"VerifyAccountAsync","params":[{"UserName": "abc123"}],"id":1}';
  157. var response = await jrpcHttpPool.getClient(
  158. rpc.currentHostAddress,
  159. // timeout: 1000, // 设置了但没生效
  160. headers: {'content-type': "text/plain"},
  161. ).post(
  162. '/IVitalLoginService',
  163. data: reqJson,
  164. options: FHttpScopedOptions(timeout: 1000),
  165. );
  166. print(response);
  167. return true;
  168. } catch (e) {
  169. print("Http unknown error:${e}");
  170. }
  171. return false;
  172. }
  173. }
  174. class _NetWaitingWidget extends StatefulWidget {
  175. @override
  176. State<_NetWaitingWidget> createState() => _NetWaitingWidgetState();
  177. }
  178. class _NetWaitingWidgetState extends State<_NetWaitingWidget> {
  179. static final _color = Colors.orange.shade200;
  180. bool _isShow = false;
  181. Timer? _timer;
  182. @override
  183. void initState() {
  184. super.initState();
  185. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  186. _timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) {
  187. setState(() {
  188. _isShow = !_isShow;
  189. });
  190. });
  191. });
  192. }
  193. @override
  194. void dispose() {
  195. _timer?.cancel();
  196. _timer = null;
  197. super.dispose();
  198. }
  199. @override
  200. Widget build(BuildContext context) {
  201. return Stack(
  202. children: [
  203. Icon(
  204. Icons.wifi,
  205. size: 36,
  206. color: _isShow ? _color : _color.withOpacity(.4),
  207. ),
  208. const Center(
  209. child: Padding(
  210. padding: EdgeInsets.all(8),
  211. child: CircularProgressIndicator(
  212. strokeWidth: 4,
  213. color: Colors.white,
  214. ),
  215. ),
  216. ),
  217. ],
  218. );
  219. }
  220. }
  221. class _NetworkIcon4G extends StatelessWidget {
  222. @override
  223. Widget build(BuildContext context) {
  224. return Expanded(
  225. child: Column(
  226. children: [
  227. Transform.translate(
  228. offset: Offset(-4, 10),
  229. child: Text(
  230. "4G",
  231. style: TextStyle(
  232. fontSize: 18,
  233. color: Colors.white,
  234. ),
  235. ),
  236. ),
  237. Transform.translate(
  238. offset: Offset(0, -6),
  239. child: Transform.scale(
  240. scaleX: 1.2,
  241. child: Icon(
  242. Icons.signal_cellular_alt_rounded,
  243. size: 36,
  244. color: Colors.green,
  245. ),
  246. ),
  247. ),
  248. ],
  249. ),
  250. );
  251. }
  252. }
  253. class _WifiNameWidget extends StatelessWidget {
  254. @override
  255. Widget build(BuildContext context) {
  256. return FutureBuilder(
  257. builder: (context, snapshot) {
  258. if (snapshot.hasData &&
  259. snapshot.data != null &&
  260. snapshot.data!.isNotEmpty) {
  261. return Container(
  262. constraints: BoxConstraints(maxWidth: 120),
  263. child: Text(
  264. _breakWord(snapshot.data!),
  265. style: TextStyle(
  266. fontSize: 16,
  267. color: Colors.white,
  268. ),
  269. overflow: TextOverflow.ellipsis,
  270. ),
  271. );
  272. }
  273. return const SizedBox();
  274. },
  275. future: WiFiForIoTPlugin.getSSID(),
  276. );
  277. }
  278. /// 插入零宽空格
  279. static String _breakWord(String word) {
  280. if (word.isEmpty) return word;
  281. String breakWord = ' ';
  282. for (var element in word.runes) {
  283. breakWord += String.fromCharCode(element);
  284. breakWord += '\u200B';
  285. }
  286. return breakWord;
  287. }
  288. }