From 685c7a7939c8f339dcfc9cc2eb80a58881e1fe6b Mon Sep 17 00:00:00 2001 From: Jganenok Date: Thu, 28 May 2026 22:05:37 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB=202?= =?UTF-8?q?=D1=84=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backend/modules/account.dart | 84 +++++++----- .../profile/password_entry_screen.dart | 120 ++++++++---------- 2 files changed, 104 insertions(+), 100 deletions(-) diff --git a/lib/backend/modules/account.dart b/lib/backend/modules/account.dart index e5de6c2..75449dd 100644 --- a/lib/backend/modules/account.dart +++ b/lib/backend/modules/account.dart @@ -621,15 +621,17 @@ class AccountModule { String? hint, }) async { _ensureOnline(); + final capabilities = [0, if (hint != null) 3, 4]; final payload = { - 'expectedCapabilities': [0, 3, 4], + 'expectedCapabilities': capabilities, 'trackId': trackId, 'password': password, }; if (hint != null) payload['hint'] = hint; - final packet = await _api.sendRequest(Opcode.authSet2fa, payload); - _checkPacketError(packet, 'confirm2fa'); - return _processProfileUpdate(packet); + return _processProfileUpdate( + _api.sendRequest(Opcode.authSet2fa, payload), + 'confirm2fa', + ); } // 2FA Management (when already set) @@ -707,15 +709,16 @@ class AccountModule { } final payload = { - 'expectedCapabilities': [1, 3], + 'expectedCapabilities': [1, if (hint != null) 3], 'trackId': trackId, 'password': newPassword, }; if (hint != null) payload['hint'] = hint; - final packet = await _api.sendRequest(Opcode.authSet2fa, payload); - _checkPacketError(packet, 'update2faPassword'); - return _processProfileUpdate(packet); + return _processProfileUpdate( + _api.sendRequest(Opcode.authSet2fa, payload), + 'update2faPassword', + ); } Future update2faEmail({ @@ -740,9 +743,10 @@ class AccountModule { 'expectedCapabilities': [4], 'trackId': trackId, }; - final packet = await _api.sendRequest(Opcode.authSet2fa, payload); - _checkPacketError(packet, 'update2faEmail'); - return _processProfileUpdate(packet); + return _processProfileUpdate( + _api.sendRequest(Opcode.authSet2fa, payload), + 'update2faEmail', + ); } Future remove2fa(String trackId) async { @@ -752,34 +756,46 @@ class AccountModule { 'trackId': trackId, 'remove2fa': true, }; - final packet = await _api.sendRequest(Opcode.authSet2fa, payload); - _checkPacketError(packet, 'remove2fa'); - return _processProfileUpdate(packet); + return _processProfileUpdate( + _api.sendRequest(Opcode.authSet2fa, payload), + 'remove2fa', + ); } - Future _processProfileUpdate(Packet packet) async { - _api.registerPushHandler(Opcode.notifProfile, (p) {}); - try { - await for (final push in _api.pushStream - .where((p) => p.opcode == Opcode.notifProfile) - .timeout(const Duration(seconds: 15))) { - final payload = push.payload; - if (payload is Map) { - final profile = payload['profile']; - if (profile is Map) { - final contact = profile['contact']; - if (contact is Map) { - return ProfileData.fromServerMap(contact.cast()); - } - } - } + Future _processProfileUpdate( + Future requestFuture, + String tag, + ) async { + final completer = Completer(); + final sub = _api.pushStream + .where((p) => p.opcode == Opcode.notifProfile) + .listen((push) { + if (completer.isCompleted) return; + final payload = push.payload; + if (payload is! Map) return; + final profile = payload['profile']; + if (profile is! Map) return; + final contact = profile['contact']; + if (contact is! Map) return; + completer.complete( + ProfileData.fromServerMap(contact.cast()), + ); + }); + final timer = Timer(const Duration(seconds: 15), () { + if (!completer.isCompleted) { + completer.completeError( + Exception('Таймаут ожидания обновления профиля'), + ); } - } on TimeoutException { - throw Exception('Таймаут ожидания обновления профиля'); + }); + try { + final packet = await requestFuture; + _checkPacketError(packet, tag); + return await completer.future; } finally { - _api.unregisterPushHandler(Opcode.notifProfile); + timer.cancel(); + await sub.cancel(); } - throw Exception('Не удалось получить обновлённый профиль'); } Future requestCode( diff --git a/lib/frontend/screens/profile/password_entry_screen.dart b/lib/frontend/screens/profile/password_entry_screen.dart index ab6998d..895028d 100644 --- a/lib/frontend/screens/profile/password_entry_screen.dart +++ b/lib/frontend/screens/profile/password_entry_screen.dart @@ -564,18 +564,9 @@ class _TwoFactorSetupScreenState extends State { style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 16), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Введите пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Введите пароль', ), ], ); @@ -599,18 +590,9 @@ class _TwoFactorSetupScreenState extends State { style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 16), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Повторите пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Повторите пароль', ), ], ); @@ -818,18 +800,9 @@ class _TwoFactorManageScreenState extends State { style: TextStyle(color: cs.onErrorContainer), ), ), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Пароль', ), const SizedBox(height: 24), SizedBox( @@ -1035,18 +1008,9 @@ class _TwoFactorPasswordChangeScreenState style: TextStyle(color: cs.onErrorContainer), ), ), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Введите новый пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Введите новый пароль', ), const SizedBox(height: 24), Text( @@ -1216,18 +1180,9 @@ class _TwoFactorEmailChangeScreenState ), ), const SizedBox(height: 16), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Пароль', ), ] else ...[ if (_errorMessage != null) @@ -1440,18 +1395,9 @@ class _TwoFactorRemoveScreenState extends State { style: TextStyle(color: cs.onErrorContainer), ), ), - TextField( + _PasswordField( controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - hintText: 'Пароль', - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), + hintText: 'Пароль', ), const SizedBox(height: 24), SizedBox( @@ -1484,3 +1430,45 @@ class _TwoFactorRemoveScreenState extends State { ); } } + +class _PasswordField extends StatefulWidget { + final TextEditingController controller; + final String hintText; + + const _PasswordField({ + required this.controller, + required this.hintText, + }); + + @override + State<_PasswordField> createState() => _PasswordFieldState(); +} + +class _PasswordFieldState extends State<_PasswordField> { + bool _visible = false; + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return TextField( + controller: widget.controller, + obscureText: !_visible, + decoration: InputDecoration( + hintText: widget.hintText, + filled: true, + fillColor: cs.surfaceContainerHighest, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + suffixIcon: IconButton( + icon: Icon( + _visible ? Symbols.visibility_off : Symbols.visibility, + color: cs.onSurfaceVariant, + ), + onPressed: () => setState(() => _visible = !_visible), + ), + ), + ); + } +}