Merge pull request #1066 from Kingtous/flutter_desktop
feat: user login/logout with UserModel
This commit is contained in:
		
						commit
						b6d56790fe
					
				@ -3,6 +3,7 @@ import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:contextmenu/contextmenu.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
 | 
			
		||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
 | 
			
		||||
import 'package:get/get.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
@ -588,7 +589,13 @@ class _ConnectionPageState extends State<ConnectionPage> {
 | 
			
		||||
    svcIsUsingPublicServer.value = await gFFI.bind.mainIsUsingPublicServer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleLogin() {}
 | 
			
		||||
  handleLogin() {
 | 
			
		||||
    loginDialog().then((success) {
 | 
			
		||||
      if (success) {
 | 
			
		||||
        setState(() {});
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Widget> buildAddressBook(BuildContext context) async {
 | 
			
		||||
    final token = await gFFI.getLocalOption('access_token');
 | 
			
		||||
@ -975,27 +982,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AddressBookPage extends StatefulWidget {
 | 
			
		||||
  const AddressBookPage({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<AddressBookPage> createState() => _AddressBookPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _AddressBookPageState extends State<AddressBookPage> {
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    // TODO: implement initState
 | 
			
		||||
    final ab = gFFI.abModel.getAb();
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class WebMenu extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  _WebMenuState createState() => _WebMenuState();
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
@ -110,68 +111,18 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                            fontSize: 18, fontWeight: FontWeight.w500),
 | 
			
		||||
                      ),
 | 
			
		||||
                      PopupMenuButton(
 | 
			
		||||
                        padding: EdgeInsets.all(4.0),
 | 
			
		||||
                        itemBuilder: (context) => [
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Enable Keyboard/Mouse"),
 | 
			
		||||
                            'enable-keyboard',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Enable Clipboard"),
 | 
			
		||||
                            'enable-clipboard',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Enable File Transfer"),
 | 
			
		||||
                            'enable-file-transfer',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Enable TCP Tunneling"),
 | 
			
		||||
                            'enable-tunnel',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genAudioInputPopupMenuItem(),
 | 
			
		||||
                          // TODO: Audio Input
 | 
			
		||||
                          PopupMenuItem(
 | 
			
		||||
                            child: Text(translate("ID/Relay Server")),
 | 
			
		||||
                            value: 'custom-server',
 | 
			
		||||
                          ),
 | 
			
		||||
                          PopupMenuItem(
 | 
			
		||||
                            child: Text(translate("IP Whitelisting")),
 | 
			
		||||
                            value: 'whitelist',
 | 
			
		||||
                          ),
 | 
			
		||||
                          PopupMenuItem(
 | 
			
		||||
                            child: Text(translate("Socks5 Proxy")),
 | 
			
		||||
                            value: 'socks5-proxy',
 | 
			
		||||
                          ),
 | 
			
		||||
                          // sep
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Enable Service"),
 | 
			
		||||
                            'stop-service',
 | 
			
		||||
                          ),
 | 
			
		||||
                          // TODO: direct server
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Always connected via relay"),
 | 
			
		||||
                            'allow-always-relay',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Start ID/relay service"),
 | 
			
		||||
                            'stop-rendezvous-service',
 | 
			
		||||
                          ),
 | 
			
		||||
                          PopupMenuItem(
 | 
			
		||||
                            child: Text(translate("Change ID")),
 | 
			
		||||
                            value: 'change-id',
 | 
			
		||||
                          ),
 | 
			
		||||
                          genEnablePopupMenuItem(
 | 
			
		||||
                            translate("Dark Theme"),
 | 
			
		||||
                            'allow-darktheme',
 | 
			
		||||
                          ),
 | 
			
		||||
                          PopupMenuItem(
 | 
			
		||||
                            child: Text(translate("About")),
 | 
			
		||||
                            value: 'about',
 | 
			
		||||
                          ),
 | 
			
		||||
                        ],
 | 
			
		||||
                        onSelected: onSelectMenu,
 | 
			
		||||
                      )
 | 
			
		||||
                      FutureBuilder<Widget>(
 | 
			
		||||
                          future: buildPopupMenu(context),
 | 
			
		||||
                          builder: (context, snapshot) {
 | 
			
		||||
                            if (snapshot.hasError) {
 | 
			
		||||
                              print("${snapshot.error}");
 | 
			
		||||
                            }
 | 
			
		||||
                            if (snapshot.hasData) {
 | 
			
		||||
                              return snapshot.data!;
 | 
			
		||||
                            } else {
 | 
			
		||||
                              return Offstage();
 | 
			
		||||
                            }
 | 
			
		||||
                          })
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
@ -189,6 +140,91 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Widget> buildPopupMenu(BuildContext context) async {
 | 
			
		||||
    var position;
 | 
			
		||||
    return GestureDetector(
 | 
			
		||||
        onTapDown: (detail) {
 | 
			
		||||
          final x = detail.globalPosition.dx;
 | 
			
		||||
          final y = detail.globalPosition.dy;
 | 
			
		||||
          position = RelativeRect.fromLTRB(x, y, x, y);
 | 
			
		||||
        },
 | 
			
		||||
        onTap: () async {
 | 
			
		||||
          final userName = await gFFI.userModel.getUserName();
 | 
			
		||||
          var menu = [
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Enable Keyboard/Mouse"),
 | 
			
		||||
              'enable-keyboard',
 | 
			
		||||
            ),
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Enable Clipboard"),
 | 
			
		||||
              'enable-clipboard',
 | 
			
		||||
            ),
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Enable File Transfer"),
 | 
			
		||||
              'enable-file-transfer',
 | 
			
		||||
            ),
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Enable TCP Tunneling"),
 | 
			
		||||
              'enable-tunnel',
 | 
			
		||||
            ),
 | 
			
		||||
            genAudioInputPopupMenuItem(),
 | 
			
		||||
            PopupMenuItem(
 | 
			
		||||
              child: Text(translate("ID/Relay Server")),
 | 
			
		||||
              value: 'custom-server',
 | 
			
		||||
            ),
 | 
			
		||||
            PopupMenuItem(
 | 
			
		||||
              child: Text(translate("IP Whitelisting")),
 | 
			
		||||
              value: 'whitelist',
 | 
			
		||||
            ),
 | 
			
		||||
            PopupMenuItem(
 | 
			
		||||
              child: Text(translate("Socks5 Proxy")),
 | 
			
		||||
              value: 'socks5-proxy',
 | 
			
		||||
            ),
 | 
			
		||||
            // sep
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Enable Service"),
 | 
			
		||||
              'stop-service',
 | 
			
		||||
            ),
 | 
			
		||||
            // TODO: direct server
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Always connected via relay"),
 | 
			
		||||
              'allow-always-relay',
 | 
			
		||||
            ),
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Start ID/relay service"),
 | 
			
		||||
              'stop-rendezvous-service',
 | 
			
		||||
            ),
 | 
			
		||||
            userName.isEmpty
 | 
			
		||||
                ? PopupMenuItem(
 | 
			
		||||
                    child: Text(translate("Login")),
 | 
			
		||||
                    value: 'login',
 | 
			
		||||
                  )
 | 
			
		||||
                : PopupMenuItem(
 | 
			
		||||
                    child: Text("${translate("Logout")} $userName"),
 | 
			
		||||
                    value: 'logout',
 | 
			
		||||
                  ),
 | 
			
		||||
            PopupMenuItem(
 | 
			
		||||
              child: Text(translate("Change ID")),
 | 
			
		||||
              value: 'change-id',
 | 
			
		||||
            ),
 | 
			
		||||
            genEnablePopupMenuItem(
 | 
			
		||||
              translate("Dark Theme"),
 | 
			
		||||
              'allow-darktheme',
 | 
			
		||||
            ),
 | 
			
		||||
            PopupMenuItem(
 | 
			
		||||
              child: Text(translate("About")),
 | 
			
		||||
              value: 'about',
 | 
			
		||||
            ),
 | 
			
		||||
          ];
 | 
			
		||||
          final v =
 | 
			
		||||
              await showMenu(context: context, position: position, items: menu);
 | 
			
		||||
          if (v != null) {
 | 
			
		||||
            onSelectMenu(v);
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        child: Icon(Icons.more_vert_outlined));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buildPasswordBoard(BuildContext context) {
 | 
			
		||||
    final model = gFFI.serverModel;
 | 
			
		||||
    return Container(
 | 
			
		||||
@ -259,15 +295,15 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
          Text(translate("Control Remote Desktop")),
 | 
			
		||||
          Form(
 | 
			
		||||
              child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                controller: TextEditingController(),
 | 
			
		||||
                inputFormatters: [
 | 
			
		||||
                  FilteringTextInputFormatter.allow(RegExp(r"[0-9]"))
 | 
			
		||||
                children: [
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: TextEditingController(),
 | 
			
		||||
                    inputFormatters: [
 | 
			
		||||
                      FilteringTextInputFormatter.allow(RegExp(r"[0-9]"))
 | 
			
		||||
                    ],
 | 
			
		||||
                  )
 | 
			
		||||
                ],
 | 
			
		||||
              )
 | 
			
		||||
            ],
 | 
			
		||||
          ))
 | 
			
		||||
              ))
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
@ -284,7 +320,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
      case "quit":
 | 
			
		||||
        exit(0);
 | 
			
		||||
      case "show":
 | 
			
		||||
        // windowManager.show();
 | 
			
		||||
      // windowManager.show();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
@ -323,6 +359,10 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
      changeSocks5Proxy();
 | 
			
		||||
    } else if (value == "about") {
 | 
			
		||||
      about();
 | 
			
		||||
    } else if (value == "logout") {
 | 
			
		||||
      logOut();
 | 
			
		||||
    } else if (value == "login") {
 | 
			
		||||
      login();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -348,7 +388,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
    return isPositive
 | 
			
		||||
        ? TextStyle()
 | 
			
		||||
        : TextStyle(
 | 
			
		||||
            color: Colors.redAccent, decoration: TextDecoration.lineThrough);
 | 
			
		||||
        color: Colors.redAccent, decoration: TextDecoration.lineThrough);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  PopupMenuItem<String> genAudioInputPopupMenuItem() {
 | 
			
		||||
@ -366,23 +406,23 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
            }
 | 
			
		||||
            var inputList = inputs
 | 
			
		||||
                .map((e) => PopupMenuItem(
 | 
			
		||||
                      child: Row(
 | 
			
		||||
                        children: [
 | 
			
		||||
                          Obx(() => Offstage(
 | 
			
		||||
                              offstage: defaultInput.value != e,
 | 
			
		||||
                              child: Icon(Icons.check))),
 | 
			
		||||
                          Expanded(
 | 
			
		||||
                              child: Tooltip(
 | 
			
		||||
                                  message: e,
 | 
			
		||||
                                  child: Text(
 | 
			
		||||
                                    "$e",
 | 
			
		||||
                                    maxLines: 1,
 | 
			
		||||
                                    overflow: TextOverflow.ellipsis,
 | 
			
		||||
                                  ))),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                      value: e,
 | 
			
		||||
                    ))
 | 
			
		||||
              child: Row(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Obx(() => Offstage(
 | 
			
		||||
                      offstage: defaultInput.value != e,
 | 
			
		||||
                      child: Icon(Icons.check))),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                      child: Tooltip(
 | 
			
		||||
                          message: e,
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            "$e",
 | 
			
		||||
                            maxLines: 1,
 | 
			
		||||
                            overflow: TextOverflow.ellipsis,
 | 
			
		||||
                          ))),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              value: e,
 | 
			
		||||
            ))
 | 
			
		||||
                .toList();
 | 
			
		||||
            inputList.insert(
 | 
			
		||||
                0,
 | 
			
		||||
@ -503,7 +543,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
 | 
			
		||||
  void changeServer() async {
 | 
			
		||||
    Map<String, dynamic> oldOptions =
 | 
			
		||||
        jsonDecode(await gFFI.bind.mainGetOptions());
 | 
			
		||||
    jsonDecode(await gFFI.bind.mainGetOptions());
 | 
			
		||||
    print("${oldOptions}");
 | 
			
		||||
    String idServer = oldOptions['custom-rendezvous-server'] ?? "";
 | 
			
		||||
    var idServerMsg = "";
 | 
			
		||||
@ -542,7 +582,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                          border: OutlineInputBorder(),
 | 
			
		||||
                          errorText:
 | 
			
		||||
                              idServerMsg.isNotEmpty ? idServerMsg : null),
 | 
			
		||||
                          idServerMsg.isNotEmpty ? idServerMsg : null),
 | 
			
		||||
                      controller: TextEditingController(text: idServer),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -595,7 +635,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                          border: OutlineInputBorder(),
 | 
			
		||||
                          errorText:
 | 
			
		||||
                              apiServerMsg.isNotEmpty ? apiServerMsg : null),
 | 
			
		||||
                          apiServerMsg.isNotEmpty ? apiServerMsg : null),
 | 
			
		||||
                      controller: TextEditingController(text: apiServer),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -711,7 +751,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
 | 
			
		||||
  void changeWhiteList() async {
 | 
			
		||||
    Map<String, dynamic> oldOptions =
 | 
			
		||||
        jsonDecode(await gFFI.bind.mainGetOptions());
 | 
			
		||||
    jsonDecode(await gFFI.bind.mainGetOptions());
 | 
			
		||||
    var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(',');
 | 
			
		||||
    var newWhiteListField = newWhiteList.join('\n');
 | 
			
		||||
    var msg = "";
 | 
			
		||||
@ -767,7 +807,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                  // pass
 | 
			
		||||
                } else {
 | 
			
		||||
                  final ips =
 | 
			
		||||
                      newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
 | 
			
		||||
                  newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
 | 
			
		||||
                  // test ip
 | 
			
		||||
                  final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
 | 
			
		||||
                  for (final ip in ips) {
 | 
			
		||||
@ -832,8 +872,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                      },
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                          border: OutlineInputBorder(),
 | 
			
		||||
                          errorText:
 | 
			
		||||
                              proxyMsg.isNotEmpty ? proxyMsg : null),
 | 
			
		||||
                          errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
 | 
			
		||||
                      controller: TextEditingController(text: proxy),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -857,8 +896,8 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                        username = s;
 | 
			
		||||
                      },
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                          border: OutlineInputBorder(),
 | 
			
		||||
                          ),
 | 
			
		||||
                        border: OutlineInputBorder(),
 | 
			
		||||
                      ),
 | 
			
		||||
                      controller: TextEditingController(text: username),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -882,8 +921,8 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                        password = s;
 | 
			
		||||
                      },
 | 
			
		||||
                      decoration: InputDecoration(
 | 
			
		||||
                          border: OutlineInputBorder(),
 | 
			
		||||
                          ),
 | 
			
		||||
                        border: OutlineInputBorder(),
 | 
			
		||||
                      ),
 | 
			
		||||
                      controller: TextEditingController(text: password),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
@ -941,9 +980,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
    final appName = await gFFI.bind.mainGetAppName();
 | 
			
		||||
    final license = await gFFI.bind.mainGetLicense();
 | 
			
		||||
    final version = await gFFI.bind.mainGetVersion();
 | 
			
		||||
    final linkStyle = TextStyle(
 | 
			
		||||
      decoration: TextDecoration.underline
 | 
			
		||||
    );
 | 
			
		||||
    final linkStyle = TextStyle(decoration: TextDecoration.underline);
 | 
			
		||||
    DialogManager.show((setState, close) {
 | 
			
		||||
      return CustomAlertDialog(
 | 
			
		||||
        title: Text("About $appName"),
 | 
			
		||||
@ -960,16 +997,20 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                  onTap: () {
 | 
			
		||||
                    launchUrlString("https://rustdesk.com/privacy");
 | 
			
		||||
                  },
 | 
			
		||||
                  child: Text("Privacy Statement", style: linkStyle,).marginSymmetric(vertical: 4.0)),
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    "Privacy Statement",
 | 
			
		||||
                    style: linkStyle,
 | 
			
		||||
                  ).marginSymmetric(vertical: 4.0)),
 | 
			
		||||
              InkWell(
 | 
			
		||||
              onTap: () {
 | 
			
		||||
                launchUrlString("https://rustdesk.com");
 | 
			
		||||
              }
 | 
			
		||||
              ,child: Text("Website",style: linkStyle,).marginSymmetric(vertical: 4.0)),
 | 
			
		||||
                  onTap: () {
 | 
			
		||||
                    launchUrlString("https://rustdesk.com");
 | 
			
		||||
                  },
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    "Website",
 | 
			
		||||
                    style: linkStyle,
 | 
			
		||||
                  ).marginSymmetric(vertical: 4.0)),
 | 
			
		||||
              Container(
 | 
			
		||||
                decoration: BoxDecoration(
 | 
			
		||||
                  color: Color(0xFF2c8cff)
 | 
			
		||||
                ),
 | 
			
		||||
                decoration: BoxDecoration(color: Color(0xFF2c8cff)),
 | 
			
		||||
                padding: EdgeInsets.symmetric(vertical: 24, horizontal: 8),
 | 
			
		||||
                child: Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
@ -977,13 +1018,16 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
                      child: Column(
 | 
			
		||||
                        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                        children: [
 | 
			
		||||
                          Text("Copyright © 2022 Purslane Ltd.\n$license", style: TextStyle(
 | 
			
		||||
                            color: Colors.white
 | 
			
		||||
                          ),),
 | 
			
		||||
                          Text("Made with heart in this chaotic world!", style: TextStyle(
 | 
			
		||||
                              fontWeight: FontWeight.w800,
 | 
			
		||||
                            color: Colors.white
 | 
			
		||||
                          ),)
 | 
			
		||||
                          Text(
 | 
			
		||||
                            "Copyright © 2022 Purslane Ltd.\n$license",
 | 
			
		||||
                            style: TextStyle(color: Colors.white),
 | 
			
		||||
                          ),
 | 
			
		||||
                          Text(
 | 
			
		||||
                            "Made with heart in this chaotic world!",
 | 
			
		||||
                            style: TextStyle(
 | 
			
		||||
                                fontWeight: FontWeight.w800,
 | 
			
		||||
                                color: Colors.white),
 | 
			
		||||
                          )
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
@ -1003,4 +1047,151 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void login() {
 | 
			
		||||
    loginDialog().then((success) {
 | 
			
		||||
      if (success) {
 | 
			
		||||
        // refresh frame
 | 
			
		||||
        setState(() {});
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void logOut() {
 | 
			
		||||
    gFFI.userModel.logOut().then((_) => {setState(() {})});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// common login dialog for desktop
 | 
			
		||||
/// call this directly
 | 
			
		||||
Future<bool> loginDialog() async {
 | 
			
		||||
  String userName = "";
 | 
			
		||||
  var userNameMsg = "";
 | 
			
		||||
  String pass = "";
 | 
			
		||||
  var passMsg = "";
 | 
			
		||||
 | 
			
		||||
  var isInProgress = false;
 | 
			
		||||
  var completer = Completer<bool>();
 | 
			
		||||
  DialogManager.show((setState, close) {
 | 
			
		||||
    return CustomAlertDialog(
 | 
			
		||||
      title: Text(translate("Login")),
 | 
			
		||||
      content: ConstrainedBox(
 | 
			
		||||
        constraints: BoxConstraints(minWidth: 500),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
          children: [
 | 
			
		||||
            SizedBox(
 | 
			
		||||
              height: 8.0,
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                ConstrainedBox(
 | 
			
		||||
                    constraints: BoxConstraints(minWidth: 100),
 | 
			
		||||
                    child: Text(
 | 
			
		||||
                      "${translate('Username')}:",
 | 
			
		||||
                      textAlign: TextAlign.start,
 | 
			
		||||
                    ).marginOnly(bottom: 16.0)),
 | 
			
		||||
                SizedBox(
 | 
			
		||||
                  width: 24.0,
 | 
			
		||||
                ),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: TextField(
 | 
			
		||||
                    onChanged: (s) {
 | 
			
		||||
                      userName = s;
 | 
			
		||||
                    },
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                        border: OutlineInputBorder(),
 | 
			
		||||
                        errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
 | 
			
		||||
                    controller: TextEditingController(text: userName),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            SizedBox(
 | 
			
		||||
              height: 8.0,
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                ConstrainedBox(
 | 
			
		||||
                    constraints: BoxConstraints(minWidth: 100),
 | 
			
		||||
                    child: Text("${translate('Password')}:")
 | 
			
		||||
                        .marginOnly(bottom: 16.0)),
 | 
			
		||||
                SizedBox(
 | 
			
		||||
                  width: 24.0,
 | 
			
		||||
                ),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: TextField(
 | 
			
		||||
                    obscureText: true,
 | 
			
		||||
                    onChanged: (s) {
 | 
			
		||||
                      pass = s;
 | 
			
		||||
                    },
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                        border: OutlineInputBorder(),
 | 
			
		||||
                        errorText: passMsg.isNotEmpty ? passMsg : null),
 | 
			
		||||
                    controller: TextEditingController(text: pass),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            SizedBox(
 | 
			
		||||
              height: 4.0,
 | 
			
		||||
            ),
 | 
			
		||||
            Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      actions: [
 | 
			
		||||
        TextButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              completer.complete(false);
 | 
			
		||||
              close();
 | 
			
		||||
            },
 | 
			
		||||
            child: Text(translate("Cancel"))),
 | 
			
		||||
        TextButton(
 | 
			
		||||
            onPressed: () async {
 | 
			
		||||
              setState(() {
 | 
			
		||||
                userNameMsg = "";
 | 
			
		||||
                passMsg = "";
 | 
			
		||||
                isInProgress = true;
 | 
			
		||||
              });
 | 
			
		||||
              final cancel = () {
 | 
			
		||||
                setState(() {
 | 
			
		||||
                  isInProgress = false;
 | 
			
		||||
                });
 | 
			
		||||
              };
 | 
			
		||||
              userName = userName;
 | 
			
		||||
              pass = pass;
 | 
			
		||||
              if (userName.isEmpty) {
 | 
			
		||||
                userNameMsg = translate("Username missed");
 | 
			
		||||
                cancel();
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
              if (pass.isEmpty) {
 | 
			
		||||
                passMsg = translate("Password missed");
 | 
			
		||||
                cancel();
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
              try {
 | 
			
		||||
                final resp = await gFFI.userModel.login(userName, pass);
 | 
			
		||||
                if (resp.containsKey('error')) {
 | 
			
		||||
                  passMsg = resp['error'];
 | 
			
		||||
                  cancel();
 | 
			
		||||
                  return;
 | 
			
		||||
                }
 | 
			
		||||
                // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
 | 
			
		||||
                // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
 | 
			
		||||
                debugPrint("$resp");
 | 
			
		||||
                completer.complete(true);
 | 
			
		||||
              } catch (err) {
 | 
			
		||||
                print(err.toString());
 | 
			
		||||
                cancel();
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
              close();
 | 
			
		||||
            },
 | 
			
		||||
            child: Text(translate("OK"))),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  return completer.future;
 | 
			
		||||
}
 | 
			
		||||
@ -8,6 +8,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart';
 | 
			
		||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 | 
			
		||||
import 'package:get/route_manager.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
 | 
			
		||||
// import 'package:window_manager/window_manager.dart';
 | 
			
		||||
 | 
			
		||||
import 'common.dart';
 | 
			
		||||
@ -77,6 +78,8 @@ class App extends StatelessWidget {
 | 
			
		||||
        ChangeNotifierProvider.value(value: gFFI.imageModel),
 | 
			
		||||
        ChangeNotifierProvider.value(value: gFFI.cursorModel),
 | 
			
		||||
        ChangeNotifierProvider.value(value: gFFI.canvasModel),
 | 
			
		||||
        ChangeNotifierProvider.value(value: gFFI.abModel),
 | 
			
		||||
        ChangeNotifierProvider.value(value: gFFI.userModel),
 | 
			
		||||
      ],
 | 
			
		||||
      child: GetMaterialApp(
 | 
			
		||||
          navigatorKey: globalKey,
 | 
			
		||||
 | 
			
		||||
@ -140,4 +140,10 @@ class AbModel with ChangeNotifier {
 | 
			
		||||
      return it.first['tags'] ?? [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void clear() {
 | 
			
		||||
    peers.clear();
 | 
			
		||||
    tags.clear();
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import 'package:flutter_hbb/models/ab_model.dart';
 | 
			
		||||
import 'package:flutter_hbb/models/chat_model.dart';
 | 
			
		||||
import 'package:flutter_hbb/models/file_model.dart';
 | 
			
		||||
import 'package:flutter_hbb/models/server_model.dart';
 | 
			
		||||
import 'package:flutter_hbb/models/user_model.dart';
 | 
			
		||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'package:tuple/tuple.dart';
 | 
			
		||||
@ -811,6 +812,7 @@ class FFI {
 | 
			
		||||
  late final ChatModel chatModel;
 | 
			
		||||
  late final FileModel fileModel;
 | 
			
		||||
  late final AbModel abModel;
 | 
			
		||||
  late final UserModel userModel;
 | 
			
		||||
 | 
			
		||||
  FFI() {
 | 
			
		||||
    this.imageModel = ImageModel(WeakReference(this));
 | 
			
		||||
@ -821,6 +823,7 @@ class FFI {
 | 
			
		||||
    this.chatModel = ChatModel(WeakReference(this));
 | 
			
		||||
    this.fileModel = FileModel(WeakReference(this));
 | 
			
		||||
    this.abModel = AbModel(WeakReference(this));
 | 
			
		||||
    this.userModel = UserModel(WeakReference(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static FFI newFFI() {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										83
									
								
								flutter/lib/models/user_model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								flutter/lib/models/user_model.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:get/get.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
 | 
			
		||||
import 'model.dart';
 | 
			
		||||
 | 
			
		||||
class UserModel extends ChangeNotifier {
 | 
			
		||||
  var userName = "".obs;
 | 
			
		||||
  WeakReference<FFI> parent;
 | 
			
		||||
 | 
			
		||||
  UserModel(this.parent);
 | 
			
		||||
 | 
			
		||||
  Future<String> getUserName() async {
 | 
			
		||||
    if (userName.isNotEmpty) {
 | 
			
		||||
      return userName.value;
 | 
			
		||||
    }
 | 
			
		||||
    final userInfo =
 | 
			
		||||
        await parent.target?.bind.mainGetLocalOption(key: 'user_info') ?? "{}";
 | 
			
		||||
    if (userInfo.trim().isEmpty) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
    final m = jsonDecode(userInfo);
 | 
			
		||||
    userName.value = m['name'] ?? '';
 | 
			
		||||
    return userName.value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> logOut() async {
 | 
			
		||||
    debugPrint("start logout");
 | 
			
		||||
    final bind = parent.target?.bind;
 | 
			
		||||
    if (bind == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final url = await bind.mainGetApiServer();
 | 
			
		||||
    final _ = await http.post(Uri.parse("$url/api/logout"),
 | 
			
		||||
        body: {
 | 
			
		||||
          "id": await bind.mainGetMyId(),
 | 
			
		||||
          "uuid": await bind.mainGetUuid(),
 | 
			
		||||
        },
 | 
			
		||||
        headers: await _getHeaders());
 | 
			
		||||
    await Future.wait([
 | 
			
		||||
      bind.mainSetLocalOption(key: 'access_token', value: ''),
 | 
			
		||||
      bind.mainSetLocalOption(key: 'user_info', value: ''),
 | 
			
		||||
      bind.mainSetLocalOption(key: 'selected-tags', value: ''),
 | 
			
		||||
    ]);
 | 
			
		||||
    parent.target?.abModel.clear();
 | 
			
		||||
    userName.value = "";
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, String>>? _getHeaders() {
 | 
			
		||||
    return parent.target?.getHttpHeaders();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, dynamic>> login(String userName, String pass) async {
 | 
			
		||||
    final bind = parent.target?.bind;
 | 
			
		||||
    if (bind == null) {
 | 
			
		||||
      return {"error": "no context"};
 | 
			
		||||
    }
 | 
			
		||||
    final url = await bind.mainGetApiServer();
 | 
			
		||||
    try {
 | 
			
		||||
      final resp = await http.post(Uri.parse("$url/api/login"),
 | 
			
		||||
          headers: {"Content-Type": "application/json"},
 | 
			
		||||
          body: jsonEncode({
 | 
			
		||||
            "username": userName,
 | 
			
		||||
            "password": pass,
 | 
			
		||||
            "id": await bind.mainGetMyId(),
 | 
			
		||||
            "uuid": await bind.mainGetUuid()
 | 
			
		||||
          }));
 | 
			
		||||
      final body = jsonDecode(resp.body);
 | 
			
		||||
      bind.mainSetLocalOption(
 | 
			
		||||
          key: "access_token", value: body['access_token'] ?? "");
 | 
			
		||||
      bind.mainSetLocalOption(
 | 
			
		||||
          key: "user_info", value: jsonEncode(body['user']));
 | 
			
		||||
      this.userName.value = body['user']?['name'] ?? "";
 | 
			
		||||
      return body;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return {"error": "$err"};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -21,10 +21,10 @@ use crate::start_server;
 | 
			
		||||
use crate::ui_interface;
 | 
			
		||||
use crate::ui_interface::{
 | 
			
		||||
    change_id, check_connect_status, get_api_server, get_app_name, get_async_job_status,
 | 
			
		||||
    get_connect_status, get_fav, get_lan_peers, get_license, get_local_option, get_options,
 | 
			
		||||
    get_peer, get_socks, get_sound_inputs, get_version, has_rendezvous_service, is_ok_change_id,
 | 
			
		||||
    post_request, set_local_option, set_options, set_socks, store_fav, test_if_valid_server,
 | 
			
		||||
    using_public_server,
 | 
			
		||||
    get_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_options,
 | 
			
		||||
    get_peer, get_socks, get_sound_inputs, get_uuid, get_version, has_rendezvous_service,
 | 
			
		||||
    is_ok_change_id, post_request, set_local_option, set_options, set_socks, store_fav,
 | 
			
		||||
    test_if_valid_server, using_public_server,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn initialize(app_dir: &str) {
 | 
			
		||||
@ -488,6 +488,14 @@ pub fn main_set_local_option(key: String, value: String) {
 | 
			
		||||
    set_local_option(key, value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn main_get_my_id() -> String {
 | 
			
		||||
    get_id()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn main_get_uuid() -> String {
 | 
			
		||||
    get_uuid()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// FFI for **get** commands which are idempotent.
 | 
			
		||||
/// Return result in c string.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user