feat(auth): integrate SmartAuth for SMS code auto-fill and update localization strings

This commit is contained in:
sergejwinston
2026-04-07 02:51:34 +07:00
parent 1a130b80ff
commit 37e320a52b
10 changed files with 101 additions and 54 deletions
+4
View File
@@ -2,3 +2,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m
android.useAndroidX=true android.useAndroidX=true
kotlin.incremental=false kotlin.incremental=false
dev.steenbakker.mobile_scanner.useUnbundled=true dev.steenbakker.mobile_scanner.useUnbundled=true
systemProp.socksProxyHost=127.0.0.1
systemProp.socksProxyPort=10808
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=10808
@@ -1,8 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:komet/l10n/app_localizations.dart'; import 'package:komet/l10n/app_localizations.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:smart_auth/smart_auth.dart';
import '../chats/chat_list_screen.dart'; import '../chats/chat_list_screen.dart';
import 'password_2fa_screen.dart'; import 'password_2fa_screen.dart';
import '../../../main.dart'; import '../../../main.dart';
@@ -26,9 +28,11 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
with TickerProviderStateMixin { with TickerProviderStateMixin {
final TextEditingController _codeController = TextEditingController(); final TextEditingController _codeController = TextEditingController();
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
final SmartAuth _smartAuth = SmartAuth.instance;
int _timerSeconds = 30; int _timerSeconds = 30;
Timer? _timer; Timer? _timer;
Timer? _errorTimer; Timer? _errorTimer;
bool _isListeningSmsConsent = false;
String? _errorMessage; String? _errorMessage;
late AnimationController _shakeController; late AnimationController _shakeController;
@@ -41,6 +45,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus(); _focusNode.requestFocus();
}); });
_startSmsCodeListener();
_shakeController = AnimationController( _shakeController = AnimationController(
vsync: this, vsync: this,
@@ -56,10 +61,63 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
]).animate(CurvedAnimation(parent: _shakeController, curve: Curves.linear)); ]).animate(CurvedAnimation(parent: _shakeController, curve: Curves.linear));
} }
Future<void> _startSmsCodeListener() async {
if (defaultTargetPlatform != TargetPlatform.android) return;
if (_isListeningSmsConsent) return;
_isListeningSmsConsent = true;
final result = await _smartAuth.getSmsWithUserConsentApi(matcher: r'\d{6}');
_isListeningSmsConsent = false;
if (!mounted || !result.hasData) return;
final receivedCode = result.data?.code;
if (receivedCode == null || receivedCode.isEmpty) return;
_applyAutoFillCode(receivedCode);
}
Future<void> _restartSmsCodeListener() async {
if (defaultTargetPlatform != TargetPlatform.android) return;
await _smartAuth.removeUserConsentApiListener();
_isListeningSmsConsent = false;
await _startSmsCodeListener();
}
void _handleCodeChanged(String value) {
setState(() {
if (_errorMessage != null) {
_errorMessage = null;
}
});
if (value.length == 6) {
_verifyCode();
}
}
void _applyAutoFillCode(String rawCode) {
final digitsOnly = rawCode.replaceAll(RegExp(r'\D'), '');
if (digitsOnly.isEmpty) return;
final nextCode = digitsOnly.length <= 6
? digitsOnly
: digitsOnly.substring(0, 6);
if (_codeController.text == nextCode) return;
_codeController.value = TextEditingValue(
text: nextCode,
selection: TextSelection.collapsed(offset: nextCode.length),
);
_handleCodeChanged(nextCode);
}
@override @override
void dispose() { void dispose() {
_timer?.cancel(); _timer?.cancel();
_errorTimer?.cancel(); _errorTimer?.cancel();
unawaited(_smartAuth.removeUserConsentApiListener());
_shakeController.dispose(); _shakeController.dispose();
_codeController.dispose(); _codeController.dispose();
_focusNode.dispose(); _focusNode.dispose();
@@ -92,6 +150,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
void _resendCode() { void _resendCode() {
if (_timerSeconds == 0) { if (_timerSeconds == 0) {
_startTimer(); _startTimer();
unawaited(_restartSmsCodeListener());
} }
} }
@@ -206,11 +265,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6), LengthLimitingTextInputFormatter(6),
], ],
onChanged: (value) { onChanged: _handleCodeChanged,
if (hasError) setState(() => _errorMessage = null);
setState(() {});
if (value.length == 6) _verifyCode();
},
), ),
), ),
), ),
@@ -795,15 +795,6 @@ class _LoginScreenState extends State<LoginScreen> {
height: 1.4, height: 1.4,
), ),
children: [ children: [
TextSpan(
text: l10n.loginTermsIntro,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 14,
height: 1.4,
fontWeight: FontWeight.w400,
),
),
TextSpan( TextSpan(
text: l10n.loginTermsLink, text: l10n.loginTermsLink,
style: GoogleFonts.inter( style: GoogleFonts.inter(
+7 -8
View File
@@ -1,23 +1,22 @@
{ {
"@@locale": "en", "@@locale": "en",
"loginTitle": "Sign in to Komet", "loginTitle": "Log in to Komet",
"loginSubtitle": "Check your country code and enter your\nphone number.", "loginSubtitle": "Check your country code and enter your\nphone number.",
"loginCountry": "Country", "loginCountry": "Country",
"loginPhoneNumber": "Phone number", "loginPhoneNumber": "Phone number",
"loginPhoneHint": "(000) 000-00-00", "loginPhoneHint": "(000) 000-00-00",
"loginOtherSignInMethods": "Other sign-in methods", "loginOtherSignInMethods": "Other sign-in methods",
"loginTermsIntro": "By continuing, you agree to \n", "loginTermsLink": "Terms of Use «Komet»",
"loginTermsLink": "the terms of use", "loginTermsOfUse": "Terms of Use «Komet»",
"loginTermsOfUse": "Terms of use",
"loginConfirmPhoneTitle": "Is this the correct number?", "loginConfirmPhoneTitle": "Is this the correct number?",
"loginEdit": "Change", "loginEdit": "Change",
"loginDone": "Done", "loginDone": "Done",
"loginReadTermsNotification": "Please read the terms of use first", "loginReadTermsNotification": "Please read the Terms of Use «Komet»",
"loginSpoofRedacted": "Spoof redaction", "loginSpoofRedacted": "Spoof redaction",
"loginProxy": "Proxy", "loginProxy": "Proxy",
"loginSignInWithQr": "Sign in with QR code", "loginSignInWithQr": "Log in with QR code",
"loginSignInWithToken": "Sign in with token", "loginSignInWithToken": "Log in with token",
"loginSignInWithSessionFile": "Sign in with session file", "loginSignInWithSessionFile": "Log in with session file",
"loginLanguage": "Language", "loginLanguage": "Language",
"languageNameRu": "Русский", "languageNameRu": "Русский",
"languageNameEn": "English", "languageNameEn": "English",
+7 -13
View File
@@ -101,7 +101,7 @@ abstract class AppLocalizations {
/// No description provided for @loginTitle. /// No description provided for @loginTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Sign in to Komet'** /// **'Log in to Komet'**
String get loginTitle; String get loginTitle;
/// No description provided for @loginSubtitle. /// No description provided for @loginSubtitle.
@@ -134,22 +134,16 @@ abstract class AppLocalizations {
/// **'Other sign-in methods'** /// **'Other sign-in methods'**
String get loginOtherSignInMethods; String get loginOtherSignInMethods;
/// No description provided for @loginTermsIntro.
///
/// In en, this message translates to:
/// **'By continuing, you agree to \n'**
String get loginTermsIntro;
/// No description provided for @loginTermsLink. /// No description provided for @loginTermsLink.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'the terms of use'** /// **'Terms of Use «Komet»'**
String get loginTermsLink; String get loginTermsLink;
/// No description provided for @loginTermsOfUse. /// No description provided for @loginTermsOfUse.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Terms of use'** /// **'Terms of Use «Komet»'**
String get loginTermsOfUse; String get loginTermsOfUse;
/// No description provided for @loginConfirmPhoneTitle. /// No description provided for @loginConfirmPhoneTitle.
@@ -173,7 +167,7 @@ abstract class AppLocalizations {
/// No description provided for @loginReadTermsNotification. /// No description provided for @loginReadTermsNotification.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Please read the terms of use first'** /// **'Please read the Terms of Use «Komet»'**
String get loginReadTermsNotification; String get loginReadTermsNotification;
/// No description provided for @loginSpoofRedacted. /// No description provided for @loginSpoofRedacted.
@@ -191,19 +185,19 @@ abstract class AppLocalizations {
/// No description provided for @loginSignInWithQr. /// No description provided for @loginSignInWithQr.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Sign in with QR code'** /// **'Log in with QR code'**
String get loginSignInWithQr; String get loginSignInWithQr;
/// No description provided for @loginSignInWithToken. /// No description provided for @loginSignInWithToken.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Sign in with token'** /// **'Log in with token'**
String get loginSignInWithToken; String get loginSignInWithToken;
/// No description provided for @loginSignInWithSessionFile. /// No description provided for @loginSignInWithSessionFile.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Sign in with session file'** /// **'Log in with session file'**
String get loginSignInWithSessionFile; String get loginSignInWithSessionFile;
/// No description provided for @loginLanguage. /// No description provided for @loginLanguage.
+8 -10
View File
@@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale); AppLocalizationsEn([String locale = 'en']) : super(locale);
@override @override
String get loginTitle => 'Sign in to Komet'; String get loginTitle => 'Log in to Komet';
@override @override
String get loginSubtitle => String get loginSubtitle =>
@@ -28,13 +28,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get loginOtherSignInMethods => 'Other sign-in methods'; String get loginOtherSignInMethods => 'Other sign-in methods';
@override @override
String get loginTermsIntro => 'By continuing, you agree to \n'; String get loginTermsLink => 'Terms of Use «Komet»';
@override @override
String get loginTermsLink => 'the terms of use'; String get loginTermsOfUse => 'Terms of Use «Komet»';
@override
String get loginTermsOfUse => 'Terms of use';
@override @override
String get loginConfirmPhoneTitle => 'Is this the correct number?'; String get loginConfirmPhoneTitle => 'Is this the correct number?';
@@ -46,7 +43,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get loginDone => 'Done'; String get loginDone => 'Done';
@override @override
String get loginReadTermsNotification => 'Please read the terms of use first'; String get loginReadTermsNotification =>
'Please read the Terms of Use «Komet»';
@override @override
String get loginSpoofRedacted => 'Spoof redaction'; String get loginSpoofRedacted => 'Spoof redaction';
@@ -55,13 +53,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get loginProxy => 'Proxy'; String get loginProxy => 'Proxy';
@override @override
String get loginSignInWithQr => 'Sign in with QR code'; String get loginSignInWithQr => 'Log in with QR code';
@override @override
String get loginSignInWithToken => 'Sign in with token'; String get loginSignInWithToken => 'Log in with token';
@override @override
String get loginSignInWithSessionFile => 'Sign in with session file'; String get loginSignInWithSessionFile => 'Log in with session file';
@override @override
String get loginLanguage => 'Language'; String get loginLanguage => 'Language';
+3 -6
View File
@@ -28,13 +28,10 @@ class AppLocalizationsRu extends AppLocalizations {
String get loginOtherSignInMethods => 'Другие способы входа'; String get loginOtherSignInMethods => 'Другие способы входа';
@override @override
String get loginTermsIntro => 'Продолжая, вы соглашаетесь с \n'; String get loginTermsLink => 'Условия использования «Komet»';
@override @override
String get loginTermsLink => 'пользовательскими соглашениями'; String get loginTermsOfUse => 'Условия использования «Komet»';
@override
String get loginTermsOfUse => 'Условия использования';
@override @override
String get loginConfirmPhoneTitle => 'Это правильный номер?'; String get loginConfirmPhoneTitle => 'Это правильный номер?';
@@ -47,7 +44,7 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get loginReadTermsNotification => String get loginReadTermsNotification =>
'Сначала прочитайте условия использования'; 'Сначала прочитайте условия использования «Komet»';
@override @override
String get loginSpoofRedacted => 'Подделка спуфа'; String get loginSpoofRedacted => 'Подделка спуфа';
+3 -3
View File
@@ -7,12 +7,12 @@
"loginPhoneHint": "(000) 000-00-00", "loginPhoneHint": "(000) 000-00-00",
"loginOtherSignInMethods": "Другие способы входа", "loginOtherSignInMethods": "Другие способы входа",
"loginTermsIntro": "Продолжая, вы соглашаетесь с \n", "loginTermsIntro": "Продолжая, вы соглашаетесь с \n",
"loginTermsLink": "пользовательскими соглашениями", "loginTermsLink": "Условия использования «Komet»",
"loginTermsOfUse": "Условия использования", "loginTermsOfUse": "Условия использования «Komet»",
"loginConfirmPhoneTitle": "Это правильный номер?", "loginConfirmPhoneTitle": "Это правильный номер?",
"loginEdit": "Изменить", "loginEdit": "Изменить",
"loginDone": "Готово", "loginDone": "Готово",
"loginReadTermsNotification": "Сначала прочитайте условия использования", "loginReadTermsNotification": "Сначала прочитайте условия использования «Komet»",
"loginSpoofRedacted": "Подделка спуфа", "loginSpoofRedacted": "Подделка спуфа",
"loginProxy": "Прокси", "loginProxy": "Прокси",
"loginSignInWithQr": "По QR code", "loginSignInWithQr": "По QR code",
+8
View File
@@ -530,6 +530,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
smart_auth:
dependency: "direct main"
description:
name: smart_auth
sha256: a536423c50d71e9a311d16027346634d0deadd07dafc5b5606b46719cf6fb2b6
url: "https://pub.dev"
source: hosted
version: "3.2.0"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
+1
View File
@@ -53,6 +53,7 @@ dependencies:
shared_preferences: ^2.5.4 shared_preferences: ^2.5.4
package_info_plus: ^9.0.1 package_info_plus: ^9.0.1
mobile_scanner: ^7.2.0 mobile_scanner: ^7.2.0
smart_auth: ^3.2.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: