From 2abfb92f17beeb6fcfbc97622af7b8d5aef602a3 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 8 May 2026 19:52:07 +0700 Subject: [PATCH] Polish Telegram login screen --- lib/screens/mapflow_shell.dart | 83 +++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/lib/screens/mapflow_shell.dart b/lib/screens/mapflow_shell.dart index 5f9276f..e26152a 100644 --- a/lib/screens/mapflow_shell.dart +++ b/lib/screens/mapflow_shell.dart @@ -151,17 +151,17 @@ class _UserAvatar extends StatelessWidget { return Padding( padding: const EdgeInsets.only(left: 12, top: 8), - child: CircleAvatar( - radius: 22, - backgroundColor: const Color(0xFFFFFBF5), - foregroundColor: const Color(0xFF17211D), - backgroundImage: photoUrl == null ? null : NetworkImage(photoUrl), - child: photoUrl == null - ? Text( - fallback, - style: const TextStyle(fontWeight: FontWeight.w900), - ) - : null, + child: ClipOval( + child: SizedBox.square( + dimension: 44, + child: photoUrl == null || photoUrl.isEmpty + ? _AvatarFallback(text: fallback) + : Image.network( + photoUrl, + fit: BoxFit.cover, + errorBuilder: (_, _, _) => _AvatarFallback(text: fallback), + ), + ), ), ); } @@ -181,6 +181,28 @@ class _UserAvatar extends StatelessWidget { } } +class _AvatarFallback extends StatelessWidget { + const _AvatarFallback({required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: const Color(0xFFFFFBF5), + child: Center( + child: Text( + text, + style: const TextStyle( + color: Color(0xFF17211D), + fontWeight: FontWeight.w900, + ), + ), + ), + ); + } +} + class _TelegramLoginScreen extends StatefulWidget { const _TelegramLoginScreen({required this.onAuthenticated}); @@ -193,6 +215,8 @@ class _TelegramLoginScreen extends StatefulWidget { class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { final _api = MapflowApi(); Timer? _pollTimer; + Timer? _countdownTimer; + DateTime? _loginExpiresAt; var _loading = false; var _message = ''; @@ -216,6 +240,7 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { @override void dispose() { _pollTimer?.cancel(); + _countdownTimer?.cancel(); super.dispose(); } @@ -228,13 +253,20 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { telegram_session.savePendingTelegramLoginToken(login.token); telegram_session.openExternalUrl(login.botUrl); _pollTimer?.cancel(); + _countdownTimer?.cancel(); _pollTimer = Timer.periodic( const Duration(seconds: 2), (_) => _pollLogin(login.token), ); + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) { + if (mounted) { + setState(() {}); + } + }); setState(() { _loading = false; - _message = 'Подтверди вход в боте.'; + _loginExpiresAt = login.expiresAt; + _message = ''; }); } @@ -242,10 +274,12 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { final status = await _api.fetchTelegramBotLoginStatus(token); if (status.isExpired) { _pollTimer?.cancel(); + _countdownTimer?.cancel(); telegram_session.clearPendingTelegramLoginToken(); if (!mounted) { return; } + _loginExpiresAt = null; setState(() => _message = 'Ссылка устарела. Запусти вход заново.'); return; } @@ -255,12 +289,27 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { } _pollTimer?.cancel(); + _countdownTimer?.cancel(); telegram_session.saveMapflowSessionToken(status.sessionToken!); telegram_session.clearPendingTelegramLoginToken(); widget.onAuthenticated(); telegram_session.reloadApp(); } + String get _remainingText { + final expiresAt = _loginExpiresAt; + if (expiresAt == null) { + return ''; + } + final seconds = expiresAt.difference(DateTime.now()).inSeconds; + if (seconds <= 0) { + return '00:00'; + } + final minutes = seconds ~/ 60; + final rest = (seconds % 60).toString().padLeft(2, '0'); + return '$minutes:$rest'; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -280,6 +329,16 @@ class _TelegramLoginScreenState extends State<_TelegramLoginScreen> { ), const SizedBox(height: 24), TelegramLoginButton(onPressed: _startLogin, loading: _loading), + if (_remainingText.isNotEmpty) ...[ + const SizedBox(height: 12), + Text( + _remainingText, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w800, + letterSpacing: 0, + ), + ), + ], if (_message.isNotEmpty) ...[ const SizedBox(height: 14), Text(