diff --git a/flutter/lib/common/formatter/id_formatter.dart b/flutter/lib/common/formatter/id_formatter.dart index 29aea84ff..c7ce14da4 100644 --- a/flutter/lib/common/formatter/id_formatter.dart +++ b/flutter/lib/common/formatter/id_formatter.dart @@ -1,4 +1,52 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; -/// TODO: Divide every 3 number to display ID -class IdFormController extends TextEditingController {} +class IDTextEditingController extends TextEditingController { + IDTextEditingController({String? text}) : super(text: text); + + String get id => trimID(value.text); + + set id(String newID) => text = formatID(newID); +} + +class IDTextInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isEmpty) { + return newValue.copyWith(text: ''); + } else if (newValue.text.compareTo(oldValue.text) == 0) { + return newValue; + } else { + int selectionIndexFromTheRight = + newValue.text.length - newValue.selection.extentOffset; + String newID = formatID(newValue.text); + return TextEditingValue( + text: newID, + selection: TextSelection.collapsed( + offset: newID.length - selectionIndexFromTheRight, + ), + ); + } + } +} + +String formatID(String id) { + String id2 = id.replaceAll(' ', ''); + String newID = ''; + if (id2.length <= 3) { + newID = id2; + } else { + var n = id2.length; + var a = n % 3 != 0 ? n % 3 : 3; + newID = id2.substring(0, a); + for (var i = a; i < n; i += 3) { + newID += " ${id2.substring(i, i + 3)}"; + } + } + return newID; +} + +String trimID(String id) { + return id.replaceAll(' ', ''); +} diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 5fd6b4a28..fe363c4c9 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -12,6 +12,7 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../common.dart'; +import '../../common/formatter/id_formatter.dart'; import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/settings_page.dart'; import '../../models/model.dart'; @@ -30,7 +31,7 @@ class ConnectionPage extends StatefulWidget { /// State for the connection page. class _ConnectionPageState extends State { /// Controller for the id input bar. - final _idController = TextEditingController(); + final _idController = IDTextEditingController(); /// Update url. If it's not null, means an update is available. final _updateUrl = ''; @@ -43,9 +44,9 @@ class _ConnectionPageState extends State { if (_idController.text.isEmpty) { () async { final lastRemoteId = await bind.mainGetLastRemoteId(); - if (lastRemoteId != _idController.text) { + if (lastRemoteId != _idController.id) { setState(() { - _idController.text = lastRemoteId; + _idController.id = lastRemoteId; }); } }(); @@ -110,7 +111,7 @@ class _ConnectionPageState extends State { /// Callback for the connect button. /// Connects to the selected peer. void onConnect({bool isFileTransfer = false}) { - final id = _idController.text.trim(); + final id = _idController.id; connect(id, isFileTransfer: isFileTransfer); } @@ -185,35 +186,41 @@ class _ConnectionPageState extends State { Row( children: [ Expanded( - child: TextField( - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - style: TextStyle( - fontFamily: 'WorkSans', - fontSize: 22, - height: 1, - ), - decoration: InputDecoration( - hintText: translate('Enter Remote ID'), - hintStyle: TextStyle( - color: MyTheme.color(context).placeholder), - border: OutlineInputBorder( + child: Obx( + () => TextField( + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.visiblePassword, + focusNode: focusNode, + style: TextStyle( + fontFamily: 'WorkSans', + fontSize: 22, + height: 1, + ), + decoration: InputDecoration( + hintText: inputFocused.value + ? null + : translate('Enter Remote ID'), + hintStyle: TextStyle( + color: MyTheme.color(context).placeholder), + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide( + color: MyTheme.color(context).placeholder!)), + focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide( - color: MyTheme.color(context).placeholder!)), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.zero, - borderSide: - BorderSide(color: MyTheme.button, width: 3), - ), - isDense: true, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 12)), - controller: _idController, - onSubmitted: (s) { - onConnect(); - }, + borderSide: + BorderSide(color: MyTheme.button, width: 3), + ), + isDense: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 12)), + controller: _idController, + inputFormatters: [IDTextInputFormatter()], + onSubmitted: (s) { + onConnect(); + }, + ), ), ), ], diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index d9c0015f8..13cf02699 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; import '../../common.dart'; +import '../../common/formatter/id_formatter.dart'; import '../../models/model.dart'; import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; @@ -119,7 +120,7 @@ class _PeerCardState extends State<_PeerCard> ? Colors.green : Colors.yellow)), Text( - '${peer.id}', + formatID('${peer.id}'), style: TextStyle(fontWeight: FontWeight.w400), ), ]), @@ -240,7 +241,7 @@ class _PeerCardState extends State<_PeerCard> backgroundColor: peer.online ? Colors.green : Colors.yellow)), - Text(peer.id) + Text(formatID(peer.id)) ]).paddingSymmetric(vertical: 8), _actionMore(peer), ], diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 31c579f83..5d23dc949 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -7,6 +7,7 @@ import 'package:flutter_hbb/models/platform_model.dart'; import 'package:wakelock/wakelock.dart'; import '../common.dart'; +import '../common/formatter/id_formatter.dart'; import '../desktop/pages/server_page.dart' as Desktop; import '../desktop/widgets/tabbar_widget.dart'; import '../mobile/pages/server_page.dart'; @@ -29,7 +30,7 @@ class ServerModel with ChangeNotifier { String _temporaryPasswordLength = ""; late String _emptyIdShow; - late final TextEditingController _serverId; + late final IDTextEditingController _serverId; final _serverPasswd = TextEditingController(text: ""); final tabController = DesktopTabController(tabType: DesktopTabType.cm); @@ -88,7 +89,7 @@ class ServerModel with ChangeNotifier { ServerModel(this.parent) { _emptyIdShow = translate("Generating ..."); - _serverId = TextEditingController(text: this._emptyIdShow); + _serverId = IDTextEditingController(text: _emptyIdShow); Timer.periodic(Duration(seconds: 1), (timer) async { var status = await bind.mainGetOnlineStatue(); @@ -300,7 +301,7 @@ class ServerModel with ChangeNotifier { } _fetchID() async { - final old = _serverId.text; + final old = _serverId.id; var count = 0; const maxCount = 10; while (count < maxCount) { @@ -309,12 +310,12 @@ class ServerModel with ChangeNotifier { if (id.isEmpty) { continue; } else { - _serverId.text = id; + _serverId.id = id; } - debugPrint("fetch id again at $count:id:${_serverId.text}"); + debugPrint("fetch id again at $count:id:${_serverId.id}"); count++; - if (_serverId.text != old) { + if (_serverId.id != old) { break; } }