Polish Telegram login screen
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m4s

This commit is contained in:
Ruslan Bakiev
2026-05-08 19:52:07 +07:00
parent 5f33a5e880
commit 2abfb92f17

View File

@@ -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(