From f6af40701c051733de1f4607bbcc6fd2dd3d177a Mon Sep 17 00:00:00 2001 From: WoNiu Date: Fri, 5 Apr 2024 18:09:40 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/login/login_page.dart | 96 ++++++++ lib/login/login_socket_utils.dart | 156 +++++++++++++ .../widget/account_number_login_widget.dart | 108 +++++++++ lib/login/widget/login_text_field_widget.dart | 210 ++++++++++++++++++ lib/main.dart | 28 +-- lib/windows/windows_main_page.dart | 23 +- pubspec.yaml | 4 +- 7 files changed, 608 insertions(+), 17 deletions(-) create mode 100644 lib/login/login_page.dart create mode 100644 lib/login/login_socket_utils.dart create mode 100644 lib/login/widget/account_number_login_widget.dart create mode 100644 lib/login/widget/login_text_field_widget.dart diff --git a/lib/login/login_page.dart b/lib/login/login_page.dart new file mode 100644 index 0000000..f383164 --- /dev/null +++ b/lib/login/login_page.dart @@ -0,0 +1,96 @@ + +import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:common/utils/platform_utils.dart'; +import 'package:common/utils/toast_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:web_synchronization_tool/login/login_socket_utils.dart'; +import 'package:web_synchronization_tool/login/widget/account_number_login_widget.dart'; +import 'package:web_synchronization_tool/windows/windows_main_page.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../windows/socket_tool.dart'; + + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + @override + void initState() { + super.initState(); + + initWindow(); + + LoginSocketUtils.getInstance().connect(); + + } + + initWindow(){ + //允许调整窗口大小 + windowManager.setResizable(false); + + const double width = 800; + const double height = 600; + //设置最小大小 + const windowSize = Size(width, height); + windowManager.setSize(windowSize); + appWindow.minSize = windowSize; + windowManager.center(); + windowManager.focus(); + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + backgroundColor: const Color(0xff272b38), + body: Padding( + padding: const EdgeInsets.all( 140 ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: PlatformUtils.isPhoneWeb(context) ? MediaQuery.of(context).size.width - 40 : 400, + child: AccountNumberLoginWidget( + loginTap: Login, + ), + ), + ], + ), + ), + ); + } + + /// 登录 + Login(String account, String password) { + + ToastUtils.showLoading(); + + /// 发送登录消息 + LoginSocketUtils.getInstance().login(account, password,(data){ + ToastUtils.dismissLoading(); + + if(data['err'] == 1){ + ToastUtils.showToast('登录失败!请检查账号和密码'); + return; + } + + /// 登录服务心跳 + LoginSocketUtils.getInstance().heartbeat(); + /// 同步socket + SocketUtils.getInstance().connect(); + SocketUtils.getInstance().heartbeat(); + + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => const WindowsPage()), + (route) => false); + + }); + } + +} diff --git a/lib/login/login_socket_utils.dart b/lib/login/login_socket_utils.dart new file mode 100644 index 0000000..91ed859 --- /dev/null +++ b/lib/login/login_socket_utils.dart @@ -0,0 +1,156 @@ + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:common/utils/toast_utils.dart'; +import 'package:web_synchronization_tool/windows/socket_tool.dart'; + +import '../windows/code.dart'; + +class LoginSocketUtils extends LoginSocket { + + // 私有构造函数 + LoginSocketUtils._(); + // 私有静态变量,保存类的唯一实例 + static LoginSocketUtils? _instance; + + // 公开的静态方法,返回类的唯一实例 + static LoginSocketUtils getInstance() { + _instance ??= LoginSocketUtils._(); + return _instance!; + } + +} + + +typedef loginBlockFun = Function(Map); + + +class LoginSocket{ + + String url = '192.168.200.17'; + static int port = 37785; + + String uuid = ''; + + RawDatagramSocket? socket; + + int heartTime = 0; + + + connect() async { + socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0); + + // 监听来自网络的数据包 + socket?.listen((RawSocketEvent e) { + Datagram? d = socket?.receive(); + if (d == null) return; + print(d.data); + Map data = json.decode(cutecode(d.data));// dataCute(); + print(data); + // 登录结果 + if (data['op'] == 2){ + loginBlock(data); + } + + // 服务器心跳 + if (data['op'] == 4){ + try{ + if(data['time']> (3 * 60 * 1000) ){ // 时间戳 > 3分钟 + socketError(); + } + }catch(e){ + socketError(); + } + } + + }); + + // 设置超时时间为30秒 + socket?.timeout(const Duration(seconds: 30), onTimeout: (eventSink) { + + ToastUtils.dismissLoading(); + ToastUtils.showToast('连接超时'); + + eventSink.close(); // 关闭套接字 + }); + + } + + socketError(){ + /// 任何错误都显示这个 + ToastUtils.showLoading(msg: '重连中'); + /// 关闭同步 socket + SocketUtils.getInstance().socket?.close(); + } + + + late loginBlockFun loginBlock; + + login(String account,String passwoord,loginBlockFun blockFun){ + + loginBlock = blockFun; + + Map map = { + 'account':account, + 'passwoord':passwoord, + 'uuid':uuid // 默认0, 需要c盘 硬盘序列号 + }; + List data = jsonEncode(map).codeUnits; + socket?.send(data, InternetAddress(url), port); + } + + /// 心跳 + heartbeat(){ + DateTime now = DateTime.now(); + int timestamp = now.millisecondsSinceEpoch; // 秒 + heartTime = timestamp; + + Timer timer = Timer(const Duration(seconds: 20), () async { + + if (socket == null){ + // 重连 + await connect(); + } + + Map map = { + "op": 3, + }; + List data = jsonEncode(map).codeUnits; + socket?.send(data, InternetAddress(url), port); + + heartbeat(); + + }); + } + + // /// 发送数据的处理 + // List dataMake(Map map){ + // // 将Map对象转换为JSON字符串 + // String json = jsonEncode(map); + // // 加密 + // String ps = makecode(json, skey); + // // 转成无符号整数数组 + // Uint8List uinData = Uint8List.fromList(ps.codeUnits); + // // 在前面添加 4位 表示长度的小端序 + // uinData = uinData.toLittle(value: uinData.length); + // return uinData.toList(); + // } + // + // /// 接收数据处理 + // Map dataCute(Uint8List data){ + // + // if (data.length <= 4) return {}; + // + // Uint8List pData = data.sublist(4,data.length); + // + // String str = cutecode(pData); + // + // Map map = json.decode(str); + // + // return map; + // } + +} + diff --git a/lib/login/widget/account_number_login_widget.dart b/lib/login/widget/account_number_login_widget.dart new file mode 100644 index 0000000..02fe3b4 --- /dev/null +++ b/lib/login/widget/account_number_login_widget.dart @@ -0,0 +1,108 @@ + +import 'package:common/utils/toast_utils.dart'; +import 'login_text_field_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:common/utils/widget_utils.dart'; + + +class LoginController{ + + set name(String newText) { + if (nameChang != null){ + nameChang!(newText); + } + } + + Function(String name)? nameChang; + + set password(String newText) { + if (passwordChang != null){ + passwordChang!(newText); + } + } + + Function(String password)? passwordChang; + +} + + + +class AccountNumberLoginWidget extends StatefulWidget { + const AccountNumberLoginWidget({Key? key, required this.loginTap, this.controller }) : super(key: key); + + final LoginController? controller; + final Function(String,String) loginTap; + + + @override + State createState() => + _AccountNumberLoginWidgetState(); +} + +class _AccountNumberLoginWidgetState extends State { + TextEditingController phoneController = TextEditingController(); + TextEditingController passwordController = TextEditingController(); + + + + _login() { + + if (phoneController.text.isEmpty || passwordController.text.isEmpty){ + ToastUtils.showToast('请输入账号和密码'); + return; + } + + widget.loginTap(phoneController.text,passwordController.text); + + } + + @override + void initState() { + super.initState(); + + widget.controller?.nameChang = (name){ + phoneController.text = name; + }; + widget.controller?.passwordChang = (password){ + passwordController.text = password; + }; + + } + + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + LoginPhoneTextField(controller: phoneController), + WidgetUtils.spacer(height: 15), + LoginPasswordTextField(controller: passwordController,onSubmitted: (_){ + _login(); + },), + WidgetUtils.spacer(height: 15), + const Text('注册请直接输入账号密码 再点击注册',style: TextStyle(fontSize: 14,color: Colors.grey),), + WidgetUtils.spacer(height: 15), + TextButton( + onPressed: _login, + style: TextButton.styleFrom(padding: EdgeInsets.zero), + child: Container( + height: 40, + alignment: Alignment.center, + decoration: const BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.all(Radius.circular(5))), + child: const Text( + '登录', + style: TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/login/widget/login_text_field_widget.dart b/lib/login/widget/login_text_field_widget.dart new file mode 100644 index 0000000..bc70870 --- /dev/null +++ b/lib/login/widget/login_text_field_widget.dart @@ -0,0 +1,210 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class LoginPhoneTextField extends StatelessWidget { + const LoginPhoneTextField( + {Key? key, required this.controller, this.helperText, this.rightWidget}) + : super(key: key); + + final TextEditingController controller; + final String? helperText; + final Widget? rightWidget; + + @override + Widget build(BuildContext context) { + Widget textField = SizedBox( + height: 40, + child: TextFieldBase( + keyboardType: TextInputType.number, + leftWidget: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.perm_contact_calendar_sharp, + color: Colors.grey, + size: 20, + ), + ), + rightWidget: rightWidget, + hintText: '请输入账号', + style: const TextStyle(color: Colors.white), + controller: controller, + ), + ); + + return helperText == null + ? textField + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + textField, + Padding( + padding: const EdgeInsets.only(top: 5), + child: Text( + helperText!, + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + ) + ], + ); + } +} + +class LoginPasswordTextField extends StatefulWidget { + const LoginPasswordTextField( + {Key? key, + required this.controller, + this.hintText = '请输入密码', + this.onSubmitted}) + : super(key: key); + + final TextEditingController controller; + final String hintText; + final ValueChanged? onSubmitted; + + @override + State createState() => _LoginPasswordTextFieldState(); +} + +class _LoginPasswordTextFieldState extends State { + bool hideCancel = true; + bool obsureText = true; + + @override + Widget build(BuildContext context) { + Widget cancel = Offstage( + offstage: hideCancel, + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(Colors.transparent), + ), + onPressed: () { + setState(() { + obsureText = !obsureText; + }); + }, + child: Icon( + Icons.remove_red_eye_rounded, + color: Colors.grey, + size: 20, + ), + ), + ); + + widget.controller.addListener(() { + setState(() { + hideCancel = !widget.controller.text.isNotEmpty; + }); + }); + + return SizedBox( + height: 40, + child: TextFieldBase( + hintText: widget.hintText, + controller: widget.controller, + obsureText: obsureText, + onSubmitted: widget.onSubmitted, + leftWidget: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.lock_sharp, + color: Colors.grey, + size: 20, + ), + ), + style: const TextStyle(color: Colors.white), + rightWidget: cancel, + ), + ); + } +} + +class TextFieldBase extends StatefulWidget { + const TextFieldBase( + {Key? key, + this.leftWidget, + this.rightWidget, + this.hintText = '', + required this.controller, + this.obsureText = false, + this.keyboardType, + this.textAlign = TextAlign.start, + this.onChanged, + this.onSubmitted, + this.inputFormatters, + this.focusNode, + this.decoration, + this.style, + this.noFocusSubmitted = false}) + : super(key: key); + + final TextEditingController controller; + final FocusNode? focusNode; + final Widget? leftWidget; + final Widget? rightWidget; + final InputDecoration? decoration; + final TextStyle? style; + final TextInputType? keyboardType; + final String hintText; + /// 暗文 + final bool obsureText; + final TextAlign textAlign; + final List? inputFormatters; + /// 失去焦点是否回调 + final bool noFocusSubmitted; + final ValueChanged? onChanged; + final ValueChanged? onSubmitted; + + @override + State createState() => _TextFieldBaseState(); +} + +class _TextFieldBaseState extends State { + late FocusNode focusNode; + + @override + void initState() { + super.initState(); + + focusNode = widget.focusNode ?? FocusNode(); + + if (widget.onSubmitted != null && widget.noFocusSubmitted){ + focusNode.addListener(() { + if (focusNode.hasFocus == false){ + widget.onSubmitted!(widget.controller.text); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final decoration = widget.decoration ?? + InputDecoration( + prefixIcon: widget.leftWidget, + prefixIconConstraints: const BoxConstraints(minWidth: 4), + suffixIcon: widget.rightWidget, + hintText: widget.hintText, + hintStyle: const TextStyle(color: Colors.grey), + border: MaterialStateOutlineInputBorder.resolveWith((states) => + const OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey))), + contentPadding: + const EdgeInsets.only(left: 0, top: 0, bottom: 0, right: 15), + ); + + return TextField( + focusNode: focusNode, + controller: widget.controller, + inputFormatters: widget.inputFormatters, + style: widget.style ?? const TextStyle(fontSize: 14), + keyboardType: widget.keyboardType, + obscureText: widget.obsureText, + textAlign: widget.textAlign, + cursorColor: Colors.blue, + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + decoration: decoration, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 0dacdc2..7eed2a0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; + +import 'package:common/utils/toast_utils.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; -import 'package:web_synchronization_tool/windows/windows_main_page.dart'; +import 'package:web_synchronization_tool/login/login_page.dart'; +import 'package:web_synchronization_tool/login/login_socket_utils.dart'; import 'package:window_manager/window_manager.dart'; void main() async { @@ -32,17 +35,14 @@ class _MyAppState extends State { void initState() { super.initState(); - //允许调整窗口大小 - windowManager.setResizable(false); + info(); + } + + + info() async { + final deviceInfo = await DeviceInfoPlugin().deviceInfo; + LoginSocketUtils.getInstance().uuid = deviceInfo.data.toString(); - const double width = 1920; - const double height = 1000; - //设置最小大小 - const windowSize = Size(width, height); - windowManager.setSize(windowSize); - appWindow.minSize = windowSize; - windowManager.center(); - windowManager.focus(); } @override @@ -54,7 +54,9 @@ class _MyAppState extends State { useMaterial3: true, ), // home: const MainPage(), - home: const WindowsPage(), + home: const LoginPage(), + navigatorObservers: [FlutterSmartDialog.observer], + builder: FlutterSmartDialog.init(), ); } } diff --git a/lib/windows/windows_main_page.dart b/lib/windows/windows_main_page.dart index 2228668..53fcee0 100644 --- a/lib/windows/windows_main_page.dart +++ b/lib/windows/windows_main_page.dart @@ -1,7 +1,9 @@ +import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:web_synchronization_tool/windows/socket_tool.dart'; import 'package:web_synchronization_tool/windows/web_grid_view.dart'; import 'package:webview_windows/webview_windows.dart'; +import 'package:window_manager/window_manager.dart'; class WindowsPage extends StatefulWidget { const WindowsPage({super.key}); @@ -24,12 +26,27 @@ class _WindowsPageState extends State { void initState() { super.initState(); - SocketUtils.getInstance().connect(); - SocketUtils.getInstance().heartbeat(); - + + initWindow(); controllerInit(); } + initWindow(){ + //允许调整窗口大小 + windowManager.setResizable(false); + + const double width = 1920; + const double height = 1000; + //设置最小大小 + const windowSize = Size(width, height); + windowManager.setSize(windowSize); + appWindow.minSize = windowSize; + windowManager.center(); + windowManager.focus(); + } + + + Future controllerInit() async { await mainController.initialize(); diff --git a/pubspec.yaml b/pubspec.yaml index c483f7d..34bf652 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,11 +11,13 @@ dependencies: flutter: sdk: flutter - cupertino_icons: ^1.0.2 + common: + path: E:\Code\FlutterProjectCode\common webview_windows: path: ../flutter-webview-windows-main bitsdojo_window: ^0.1.6 window_manager: ^0.3.7 + device_info_plus: ^9.1.2 dev_dependencies: flutter_test: