Added localization support for multiple languages, including Russian and English, and integrated language selection in the login and country selection screens. Updated pubspec.yaml and pubspec.lock to include necessary dependencies.

This commit is contained in:
klockky
2026-04-03 21:29:45 +03:00
parent 4b2e5e5c47
commit 1caa8c1049
16 changed files with 996 additions and 163 deletions
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:komet/l10n/app_localizations.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import '../chats/chat_list_screen.dart';
@@ -109,7 +110,10 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
final trackId = result.challengeTrackId;
if (trackId == null) {
showCustomNotification(context, 'Ошибка: отсутствуют данные для 2FA');
showCustomNotification(
context,
AppLocalizations.of(context)!.codeError2faMissing,
);
return;
}
@@ -141,6 +145,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
final hasError = _errorMessage != null;
return Scaffold(
@@ -170,7 +175,7 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
),
const SizedBox(height: 12),
Text(
'Мы отправили SMS с кодом подтверждения на ваш номер телефона.',
l10n.codeConfirmationSmsSent,
style: TextStyle(
color: cs.outline,
fontSize: 15,
@@ -326,8 +331,8 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen>
),
child: Text(
_timerSeconds > 0
? 'Отправить повторно через $_timerSeconds сек.'
: 'Отправить код по SMS',
? l10n.codeResendInSeconds(_timerSeconds)
: l10n.codeResendSms,
),
),
),
+116 -128
View File
@@ -5,6 +5,8 @@ import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:komet/core/config/countries.dart';
import 'package:komet/l10n/app_localizations.dart';
import 'package:komet/l10n/terms_of_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'code_confirmation_screen.dart';
import 'select_country_screen.dart';
@@ -77,8 +79,82 @@ class _LoginScreenState extends State<LoginScreen> {
}
}
String _countryDisplayName(CountryName country) {
final lang = Localizations.localeOf(context).languageCode;
return lang == 'ru' ? country.ru : country.en;
}
void _showLanguagePicker() {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
final appContext = context;
showModalBottomSheet<void>(
context: context,
backgroundColor: cs.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (sheetContext) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 24.0,
horizontal: 16.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
l10n.loginLanguage,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
ListTile(
title: Text(
l10n.languageNameRu,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
onTap: () {
Navigator.pop(sheetContext);
KometApp.stateOf(appContext)?.applyLocale(const Locale('ru'));
},
),
ListTile(
title: Text(
l10n.languageNameEn,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
onTap: () {
Navigator.pop(sheetContext);
KometApp.stateOf(appContext)?.applyLocale(const Locale('en'));
},
),
],
),
),
);
},
);
}
void _showTOS(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final termsLocale = Localizations.localeOf(context);
showModalBottomSheet(
context: context,
backgroundColor: cs.surfaceContainerHigh,
@@ -107,7 +183,7 @@ class _LoginScreenState extends State<LoginScreen> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Условия использования',
AppLocalizations.of(context)!.loginTermsOfUse,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 20,
@@ -147,99 +223,8 @@ class _LoginScreenState extends State<LoginScreen> {
child: ListView(
controller: scrollController,
children: [
//У мя вопрос а тут точно номера статей верные???
Text(
'Условия использования неофициального клиента на MAX, именуемым "KometClient" или же "Komet"\n\n'
'1. Статус и отношения\n'
'1.1. «Komet» (далее — «Приложение») — неофициальное стороннее приложение, не имеющее отношения к ООО «Коммуникационная платформа" (правообладатель сервиса «MAX").\n'
'1.2. Разработчики Приложения не являются партнёрами, сотрудниками или аффилированными лицами ООО «Коммуникационная платформа».\n'
'1.3. Все упоминания торговых марок «MAX» и связанных сервисов принадлежат их правообладателям.\n\n'
'2. Условия использования\n'
'2.1. Используя Приложение «Komet», вы:\n'
'• Автоматически подтверждаете согласие с официальным Пользовательским соглашением «MAX» (https://legal.max.ru/ps)\n'
'• Осознаёте, что использование неофициального клиента может привести к блокировке аккаунта со стороны ООО «Коммуникационная платформа»;\n'
'• Принимаете на себя все риски, связанные с использованием Приложения.\n'
'2.2. Строго запрещено:\n'
'• Использовать Приложение «Komet» для распространения запрещённого контента;\n'
'• Осуществлять массовые рассылки (спам);\n'
'• Нарушать законодательство РФ и международное право;\n'
'• Предпринимать попытки взлома или нарушения работы оригинального сервиса «MAX».\n'
'2.3. Техническая реализация соответствует принципу добросовестного использования (свободное использование) и не нарушает исключительные права правообладателя в соответствии с статьёй 1273 ГК РФ.\n'
'2.4. Особенности технического взаимодействия:\n'
'• Приложение «Komet» использует публично доступные методы взаимодействия с сервисом «MAX», аналогичные веб-версии (https://web.max.ru)\n'
'• Все запросы выполняются в рамках добросовестного использования для обеспечения совместимости;\n'
'• Разработчики не осуществляют обход технических средств защиты и не декомпилируют оригинальное ПО.\n\n'
'3. Технические аспекты\n'
'3.1. Приложение «Komet» использует только публично доступные методы взаимодействия с сервисом «MAX» через официальные конечные точки.\n'
'3.2. Все запросы выполняются в рамках добросовестного использования (fair use) для обеспечения совместимости.\n'
'3.3. Разработчики не несут ответственности за:\n'
'• Изменения в API оригинального сервиса;\n'
'• Блокировку аккаунтов пользователей;\n'
'• Функциональные ограничения, вызванные действиями ООО «Коммуникационная платформа».\n\n'
'4. Конфиденциальность\n'
'4.1. Приложение «Komet» не хранит и не обрабатывает персональные данные пользователей.\n'
'4.2. Все данные авторизации передаются напрямую серверам ООО «Коммуникационная платформа».\n'
'4.3. Разработчики не имеют доступа к логинам, паролям, переписке и другим персональным данным пользователей.\n\n'
'5. Ответственность и ограничения\n'
'5.1. Приложение «Komet» предоставляется «как есть» (as is) без гарантий работоспособности.\n'
'5.2. Разработчики вправе прекратить поддержку Приложения в любой момент без объяснения причин.\n\n'
'6. Правовые основания\n'
'6.1. Разработка и распространение Приложения «Komet» осуществляются в соответствии с:\n'
'• Статья 1280.3 ГК РФ — декомпилирование программы для обеспечения совместимости;\n'
'• Статья 1229 ГК РФ — ограничения исключительного права в информационных целях;\n'
'• Федеральный закон № 149‑ФЗ «Об информации» — использование общедоступной информации;\n'
'• Право на межоперабельность (Directive (EU) 2019/790) — обеспечение взаимодействия программ.\n'
'6.2. Взаимодействие с сервисом «MAX» осуществляется исключительно через:\n'
'• Публичные API‑интерфейсы, доступные через веб‑версию сервиса;\n'
'• Методы обратной разработки, разрешённые ст. 1280.3 ГК РФ для целей совместимости;\n'
'• Открытые протоколы взаимодействия, не защищённые техническими средствами охраны.\n'
'6.3. Приложение «Komet» не обходит технические средства защиты и не нарушает нормальную работу оригинального сервиса, что соответствует требованиям статьи 1299 ГК РФ.\n\n'
'7. Заключительные положения\n'
'7.1. Используя Приложение «Komet», вы соглашаетесь с тем, что:\n'
'• Единственным правомочным способом использования сервиса «MAX» является применение официальных клиентов;\n'
'• Все претензии по работе сервиса должны направляться в ООО «Коммуникационная платформа»;\n'
'• Разработчики Приложения не несут ответственности за любые косвенные или прямые убытки.\n'
'7.2. Настоящее соглашение может быть изменено без предварительного уведомления пользователей.\n\n'
'8. Функции безопасности и конфиденциальности\n'
'8.1. Приложение «Komet» включает инструменты защиты приватности:\n'
'• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов OpenSourceIntelligence (OSINT);\n'
'• Система прокси‑подключений — для обеспечения безопасности сетевого взаимодействия;\n'
'• Ограничение телеметрии — для минимизации передачи диагностических данных.\n'
'8.2. Данные функции:\n'
'• Направлены исключительно на защиту конфиденциальности пользователей;\n'
'• Не используются для обхода систем безопасности оригинального сервиса;\n'
'• Реализованы в рамках статьи 152.1 ГК РФ о защите частной жизни.\n'
'8.3. Разработчики не несут ответственности за:\n'
'• Блокировки, связанные с использованием инструментов конфиденциальности;\n'
'• Изменения в работе сервиса при активации данных функций.\n'
'8.4. Функции экспорта и импорта сессии\n'
'8.4.1. Приложение «Komet» предоставляет возможность экспорта и импорта данных сессии для:\n'
'• Обеспечения переносимости данных между устройствами пользователя\n'
'• Резервного копирования учетных данных\n'
'• Восстановления доступа при утере устройства\n'
'8.4.2. Особенности реализации:\n'
'• Экспорт сессии осуществляется без привязки к номеру телефона\n'
'• Данные сессии защищаются паролем и шифрованием по алгоритмам AES‑256\n'
'• Ключ шифрования известен только пользователю и не сохраняется в приложении\n'
'8.4.3. Техническая реализация экспорта сессии:\n'
'• Экспорт сессии осуществляется через токен авторизации для идентификации в сервисе\n'
'• Используется подмена параметров сессии для сохранения контекста аутентификации\n'
'• Интеграция настроек прокси для обеспечения единой конфигурации подключения\n'
'• Импортированная сессия маскирует источник подключения через указанные прокси‑настройки\n'
'• Серверы оригинального сервиса не получают данных о смене устройства пользователя\n'
'• Шифрование применяется ко всему пакету данных (сессия + прокси‑конфиг)\n'
'8.4.4. Правовые основания:\n'
'• Статья 6 ФЗ‑152 «О персональных данных» — обработка данных с согласия субъекта\n'
'• Статья 434 ГК РФ — право на выбор формы сделки (электронная форма хранения учетных данных)\n'
'• Принцип минимизации данных — сбор только необходимой для работы информации\n'
'• Использование токена не является несанкционированным доступом (ст. 272 УК РФ не нарушается)\n'
'• Подмена сессии — легитимный метод сохранения аутентификации (аналог браузерных cookies)\n'
'• Маскировка IP‑адреса — законный способ защиты персональных данных (ст. 6 ФЗ‑152)\n'
'8.4.5. Ограничения ответственности:\n'
'• Пользователь самостоятельно несет ответственность за сохранность пароля и резервных копий\n'
'• Разработчики не имеют доступа к зашифрованным данным сессии\n'
'• Восстановление утерянных паролей невозможно в целях безопасности\n'
'• Ключи шифрования не хранятся в приложении и известны только пользователю',
termsOfServiceBody(termsLocale),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 14,
@@ -328,6 +313,7 @@ class _LoginScreenState extends State<LoginScreen> {
void _showPhoneConfirmationDialog(String formattedPhone) {
final screenContext = context;
final l10n = AppLocalizations.of(screenContext)!;
showGeneralDialog(
context: screenContext,
@@ -358,7 +344,7 @@ class _LoginScreenState extends State<LoginScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Это правильный номер?',
l10n.loginConfirmPhoneTitle,
style: GoogleFonts.inter(
color: cs.onSurfaceVariant,
fontSize: 14,
@@ -383,7 +369,7 @@ class _LoginScreenState extends State<LoginScreen> {
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Изменить',
l10n.loginEdit,
style: GoogleFonts.inter(
color: cs.primary,
fontSize: 15,
@@ -403,26 +389,24 @@ class _LoginScreenState extends State<LoginScreen> {
fullPhone,
);
if (mounted) {
Navigator.push(
screenContext,
MaterialPageRoute(
builder: (context) => CodeConfirmationScreen(
phoneNumber:
'${_selectedCountry.phoneCode} $formattedPhone',
token: result.token,
),
if (!screenContext.mounted) return;
Navigator.push(
screenContext,
MaterialPageRoute(
builder: (context) => CodeConfirmationScreen(
phoneNumber:
'${_selectedCountry.phoneCode} $formattedPhone',
token: result.token,
),
);
}
),
);
} catch (e) {
if (mounted) {
_showPhoneError(e.toString());
}
if (!screenContext.mounted) return;
_showPhoneError(e.toString());
}
},
child: Text(
'Готово',
l10n.loginDone,
style: GoogleFonts.inter(
color: cs.primary,
fontSize: 15,
@@ -443,7 +427,10 @@ class _LoginScreenState extends State<LoginScreen> {
void _validateAndSubmit() {
if (!_isTOSRead) {
showCustomNotification(context, 'Соглащение прочитай щегол');
showCustomNotification(
context,
AppLocalizations.of(context)!.loginReadTermsNotification,
);
return;
}
_showPhoneConfirmationDialog(_phoneController.text);
@@ -451,6 +438,7 @@ class _LoginScreenState extends State<LoginScreen> {
void _showSecurityOptions(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
showModalBottomSheet(
context: context,
backgroundColor: cs.surfaceContainerHigh,
@@ -470,7 +458,7 @@ class _LoginScreenState extends State<LoginScreen> {
ListTile(
leading: Icon(Symbols.security, color: cs.onSurface),
title: Text(
'Подделка спуфа',
l10n.loginSpoofRedacted,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
@@ -490,7 +478,7 @@ class _LoginScreenState extends State<LoginScreen> {
ListTile(
leading: Icon(Symbols.vpn_lock, color: cs.onSurface),
title: Text(
'Прокси',
l10n.loginProxy,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
@@ -511,6 +499,7 @@ class _LoginScreenState extends State<LoginScreen> {
void _showOtherLoginMethods(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
showModalBottomSheet(
context: context,
backgroundColor: cs.surfaceContainerHigh,
@@ -530,7 +519,7 @@ class _LoginScreenState extends State<LoginScreen> {
ListTile(
leading: Icon(Symbols.qr_code_2, color: cs.onSurface),
title: Text(
'По QR code',
l10n.loginSignInWithQr,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
@@ -544,7 +533,7 @@ class _LoginScreenState extends State<LoginScreen> {
ListTile(
leading: Icon(Symbols.key, color: cs.onSurface),
title: Text(
'По токену',
l10n.loginSignInWithToken,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
@@ -558,7 +547,7 @@ class _LoginScreenState extends State<LoginScreen> {
ListTile(
leading: Icon(Symbols.description, color: cs.onSurface),
title: Text(
'По файлу сессии',
l10n.loginSignInWithSessionFile,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
@@ -580,6 +569,7 @@ class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
return Scaffold(
backgroundColor: cs.surface,
body: GestureDetector(
@@ -617,7 +607,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
),
IconButton(
onPressed: () {},
onPressed: _showLanguagePicker,
icon: Icon(
Symbols.language,
color: cs.onSurfaceVariant,
@@ -637,7 +627,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
const SizedBox(height: 16),
Text(
'Войдите в Komet',
l10n.loginTitle,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 32,
@@ -646,7 +636,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
const SizedBox(height: 8),
Text(
'Проверьте код страны и введите свой\nномер телефона.',
l10n.loginSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
color: cs.onSurfaceVariant,
@@ -663,11 +653,11 @@ class _LoginScreenState extends State<LoginScreen> {
onTap: _showCountryPicker,
borderRadius: BorderRadius.circular(50),
child: _buildInputField(
label: 'Страна',
label: l10n.loginCountry,
content: Row(
children: [
Text(
_selectedCountry.ru,
_countryDisplayName(_selectedCountry),
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 15,
@@ -685,7 +675,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
const SizedBox(height: 40),
_buildInputField(
label: 'Номер телефона',
label: l10n.loginPhoneNumber,
content: Row(
children: [
Text(
@@ -717,7 +707,7 @@ class _LoginScreenState extends State<LoginScreen> {
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: '(000) 000-00-00',
hintText: l10n.loginPhoneHint,
hintStyle: TextStyle(
color: cs.outline,
fontSize: 15,
@@ -768,7 +758,7 @@ class _LoginScreenState extends State<LoginScreen> {
TextButton(
onPressed: () => _showOtherLoginMethods(context),
child: Text(
'Другие способы входа',
l10n.loginOtherSignInMethods,
style: GoogleFonts.inter(
color: cs.primary,
fontSize: 14,
@@ -794,8 +784,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
children: [
TextSpan(
text:
'Продолжая, вы соглашаетесь с \n',
text: l10n.loginTermsIntro,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 14,
@@ -804,8 +793,7 @@ class _LoginScreenState extends State<LoginScreen> {
),
),
TextSpan(
text:
'пользовательскими соглашениями',
text: l10n.loginTermsLink,
style: GoogleFonts.inter(
color: cs.primary,
fontSize: 14,
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:komet/core/config/countries.dart';
import 'package:komet/l10n/app_localizations.dart';
class SelectCountryScreen extends StatefulWidget {
final CountryName selectedCountry;
@@ -41,6 +42,8 @@ class _SelectCountryScreenState extends State<SelectCountryScreen> {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context)!;
final lang = Localizations.localeOf(context).languageCode;
return Scaffold(
backgroundColor: cs.surface,
@@ -61,7 +64,7 @@ class _SelectCountryScreenState extends State<SelectCountryScreen> {
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: 'Поиск страны...',
hintText: l10n.selectCountrySearchHint,
hintStyle: GoogleFonts.inter(
color: cs.onSurfaceVariant,
fontSize: 18,
@@ -72,7 +75,7 @@ class _SelectCountryScreenState extends State<SelectCountryScreen> {
onChanged: _filterCountries,
)
: Text(
'Выберите страну',
l10n.selectCountryTitle,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 20,
@@ -114,7 +117,7 @@ class _SelectCountryScreenState extends State<SelectCountryScreen> {
),
),
title: Text(
country.ru,
lang == 'ru' ? country.ru : country.en,
style: GoogleFonts.inter(
color: cs.onSurface,
fontSize: 16,
+37
View File
@@ -0,0 +1,37 @@
{
"@@locale": "en",
"loginTitle": "Sign in to Komet",
"loginSubtitle": "Check your country code and enter your\nphone number.",
"loginCountry": "Country",
"loginPhoneNumber": "Phone number",
"loginPhoneHint": "(000) 000-00-00",
"loginOtherSignInMethods": "Other sign-in methods",
"loginTermsIntro": "By continuing, you agree to \n",
"loginTermsLink": "the terms of use",
"loginTermsOfUse": "Terms of use",
"loginConfirmPhoneTitle": "Is this the correct number?",
"loginEdit": "Change",
"loginDone": "Done",
"loginReadTermsNotification": "Please read the terms of use first",
"loginSpoofRedacted": "Spoof redaction",
"loginProxy": "Proxy",
"loginSignInWithQr": "Sign in with QR code",
"loginSignInWithToken": "Sign in with token",
"loginSignInWithSessionFile": "Sign in with session file",
"loginLanguage": "Language",
"languageNameRu": "Русский",
"languageNameEn": "English",
"selectCountryTitle": "Select country",
"selectCountrySearchHint": "Search countries…",
"codeConfirmationSmsSent": "We sent an SMS with a verification code to your phone number.",
"codeResendInSeconds": "Resend in {seconds} s.",
"@codeResendInSeconds": {
"placeholders": {
"seconds": {
"type": "int"
}
}
},
"codeResendSms": "Resend code via SMS",
"codeError2faMissing": "Error: missing data for 2FA"
}
+296
View File
@@ -0,0 +1,296 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_en.dart';
import 'app_localizations_ru.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('en'),
Locale('ru'),
];
/// No description provided for @loginTitle.
///
/// In en, this message translates to:
/// **'Sign in to Komet'**
String get loginTitle;
/// No description provided for @loginSubtitle.
///
/// In en, this message translates to:
/// **'Check your country code and enter your\nphone number.'**
String get loginSubtitle;
/// No description provided for @loginCountry.
///
/// In en, this message translates to:
/// **'Country'**
String get loginCountry;
/// No description provided for @loginPhoneNumber.
///
/// In en, this message translates to:
/// **'Phone number'**
String get loginPhoneNumber;
/// No description provided for @loginPhoneHint.
///
/// In en, this message translates to:
/// **'(000) 000-00-00'**
String get loginPhoneHint;
/// No description provided for @loginOtherSignInMethods.
///
/// In en, this message translates to:
/// **'Other sign-in methods'**
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.
///
/// In en, this message translates to:
/// **'the terms of use'**
String get loginTermsLink;
/// No description provided for @loginTermsOfUse.
///
/// In en, this message translates to:
/// **'Terms of use'**
String get loginTermsOfUse;
/// No description provided for @loginConfirmPhoneTitle.
///
/// In en, this message translates to:
/// **'Is this the correct number?'**
String get loginConfirmPhoneTitle;
/// No description provided for @loginEdit.
///
/// In en, this message translates to:
/// **'Change'**
String get loginEdit;
/// No description provided for @loginDone.
///
/// In en, this message translates to:
/// **'Done'**
String get loginDone;
/// No description provided for @loginReadTermsNotification.
///
/// In en, this message translates to:
/// **'Please read the terms of use first'**
String get loginReadTermsNotification;
/// No description provided for @loginSpoofRedacted.
///
/// In en, this message translates to:
/// **'Spoof redaction'**
String get loginSpoofRedacted;
/// No description provided for @loginProxy.
///
/// In en, this message translates to:
/// **'Proxy'**
String get loginProxy;
/// No description provided for @loginSignInWithQr.
///
/// In en, this message translates to:
/// **'Sign in with QR code'**
String get loginSignInWithQr;
/// No description provided for @loginSignInWithToken.
///
/// In en, this message translates to:
/// **'Sign in with token'**
String get loginSignInWithToken;
/// No description provided for @loginSignInWithSessionFile.
///
/// In en, this message translates to:
/// **'Sign in with session file'**
String get loginSignInWithSessionFile;
/// No description provided for @loginLanguage.
///
/// In en, this message translates to:
/// **'Language'**
String get loginLanguage;
/// No description provided for @languageNameRu.
///
/// In en, this message translates to:
/// **'Русский'**
String get languageNameRu;
/// No description provided for @languageNameEn.
///
/// In en, this message translates to:
/// **'English'**
String get languageNameEn;
/// No description provided for @selectCountryTitle.
///
/// In en, this message translates to:
/// **'Select country'**
String get selectCountryTitle;
/// No description provided for @selectCountrySearchHint.
///
/// In en, this message translates to:
/// **'Search countries…'**
String get selectCountrySearchHint;
/// No description provided for @codeConfirmationSmsSent.
///
/// In en, this message translates to:
/// **'We sent an SMS with a verification code to your phone number.'**
String get codeConfirmationSmsSent;
/// No description provided for @codeResendInSeconds.
///
/// In en, this message translates to:
/// **'Resend in {seconds} s.'**
String codeResendInSeconds(int seconds);
/// No description provided for @codeResendSms.
///
/// In en, this message translates to:
/// **'Resend code via SMS'**
String get codeResendSms;
/// No description provided for @codeError2faMissing.
///
/// In en, this message translates to:
/// **'Error: missing data for 2FA'**
String get codeError2faMissing;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['en', 'ru'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'en':
return AppLocalizationsEn();
case 'ru':
return AppLocalizationsRu();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.',
);
}
+95
View File
@@ -0,0 +1,95 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get loginTitle => 'Sign in to Komet';
@override
String get loginSubtitle =>
'Check your country code and enter your\nphone number.';
@override
String get loginCountry => 'Country';
@override
String get loginPhoneNumber => 'Phone number';
@override
String get loginPhoneHint => '(000) 000-00-00';
@override
String get loginOtherSignInMethods => 'Other sign-in methods';
@override
String get loginTermsIntro => 'By continuing, you agree to \n';
@override
String get loginTermsLink => 'the terms of use';
@override
String get loginTermsOfUse => 'Terms of use';
@override
String get loginConfirmPhoneTitle => 'Is this the correct number?';
@override
String get loginEdit => 'Change';
@override
String get loginDone => 'Done';
@override
String get loginReadTermsNotification => 'Please read the terms of use first';
@override
String get loginSpoofRedacted => 'Spoof redaction';
@override
String get loginProxy => 'Proxy';
@override
String get loginSignInWithQr => 'Sign in with QR code';
@override
String get loginSignInWithToken => 'Sign in with token';
@override
String get loginSignInWithSessionFile => 'Sign in with session file';
@override
String get loginLanguage => 'Language';
@override
String get languageNameRu => 'Русский';
@override
String get languageNameEn => 'English';
@override
String get selectCountryTitle => 'Select country';
@override
String get selectCountrySearchHint => 'Search countries…';
@override
String get codeConfirmationSmsSent =>
'We sent an SMS with a verification code to your phone number.';
@override
String codeResendInSeconds(int seconds) {
return 'Resend in $seconds s.';
}
@override
String get codeResendSms => 'Resend code via SMS';
@override
String get codeError2faMissing => 'Error: missing data for 2FA';
}
+96
View File
@@ -0,0 +1,96 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Russian (`ru`).
class AppLocalizationsRu extends AppLocalizations {
AppLocalizationsRu([String locale = 'ru']) : super(locale);
@override
String get loginTitle => 'Войдите в Komet';
@override
String get loginSubtitle =>
'Проверьте код страны и введите свой\nномер телефона.';
@override
String get loginCountry => 'Страна';
@override
String get loginPhoneNumber => 'Номер телефона';
@override
String get loginPhoneHint => '(000) 000-00-00';
@override
String get loginOtherSignInMethods => 'Другие способы входа';
@override
String get loginTermsIntro => 'Продолжая, вы соглашаетесь с \n';
@override
String get loginTermsLink => 'пользовательскими соглашениями';
@override
String get loginTermsOfUse => 'Условия использования';
@override
String get loginConfirmPhoneTitle => 'Это правильный номер?';
@override
String get loginEdit => 'Изменить';
@override
String get loginDone => 'Готово';
@override
String get loginReadTermsNotification =>
'Сначала прочитайте условия использования';
@override
String get loginSpoofRedacted => 'Подделка спуфа';
@override
String get loginProxy => 'Прокси';
@override
String get loginSignInWithQr => 'По QR code';
@override
String get loginSignInWithToken => 'По токену';
@override
String get loginSignInWithSessionFile => 'По файлу сессии';
@override
String get loginLanguage => 'Язык';
@override
String get languageNameRu => 'Русский';
@override
String get languageNameEn => 'English';
@override
String get selectCountryTitle => 'Выберите страну';
@override
String get selectCountrySearchHint => 'Поиск страны…';
@override
String get codeConfirmationSmsSent =>
'Мы отправили SMS с кодом подтверждения на ваш номер телефона.';
@override
String codeResendInSeconds(int seconds) {
return 'Отправить повторно через $seconds сек.';
}
@override
String get codeResendSms => 'Отправить код по SMS';
@override
String get codeError2faMissing => 'Ошибка: отсутствуют данные для 2FA';
}
+37
View File
@@ -0,0 +1,37 @@
{
"@@locale": "ru",
"loginTitle": "Войдите в Komet",
"loginSubtitle": "Проверьте код страны и введите свой\nномер телефона.",
"loginCountry": "Страна",
"loginPhoneNumber": "Номер телефона",
"loginPhoneHint": "(000) 000-00-00",
"loginOtherSignInMethods": "Другие способы входа",
"loginTermsIntro": "Продолжая, вы соглашаетесь с \n",
"loginTermsLink": "пользовательскими соглашениями",
"loginTermsOfUse": "Условия использования",
"loginConfirmPhoneTitle": "Это правильный номер?",
"loginEdit": "Изменить",
"loginDone": "Готово",
"loginReadTermsNotification": "Сначала прочитайте условия использования",
"loginSpoofRedacted": "Подделка спуфа",
"loginProxy": "Прокси",
"loginSignInWithQr": "По QR code",
"loginSignInWithToken": "По токену",
"loginSignInWithSessionFile": "По файлу сессии",
"loginLanguage": "Язык",
"languageNameRu": "Русский",
"languageNameEn": "English",
"selectCountryTitle": "Выберите страну",
"selectCountrySearchHint": "Поиск страны…",
"codeConfirmationSmsSent": "Мы отправили SMS с кодом подтверждения на ваш номер телефона.",
"codeResendInSeconds": "Отправить повторно через {seconds} сек.",
"@codeResendInSeconds": {
"placeholders": {
"seconds": {
"type": "int"
}
}
},
"codeResendSms": "Отправить код по SMS",
"codeError2faMissing": "Ошибка: отсутствуют данные для 2FA"
}
+11
View File
@@ -0,0 +1,11 @@
import 'package:flutter/widgets.dart';
import 'tos_en.dart';
import 'tos_ru.dart';
String termsOfServiceBody(Locale locale) {
if (locale.languageCode == 'ru') {
return kTermsOfServiceRu;
}
return kTermsOfServiceEn;
}
+96
View File
@@ -0,0 +1,96 @@
const String kTermsOfServiceEn = r'''
Terms of use for the unofficial MAX client named "KometClient" or "Komet"
1. Status and relationships
1.1. "Komet" (the "App") is an unofficial third-party application and is not affiliated with Communication Platform LLC (rights holder of the "MAX" service).
1.2. The App developers are not partners, employees, or affiliates of Communication Platform LLC.
1.3. All references to the "MAX" trademarks and related services belong to their respective owners.
2. Terms of use
2.1. By using "Komet", you:
• Automatically agree to the official MAX User Agreement (https://legal.max.ru/ps)
• Understand that using an unofficial client may lead to your account being blocked by Communication Platform LLC
• Accept all risks associated with using the App
2.2. Strictly prohibited:
• Using "Komet" to distribute prohibited content
• Sending bulk messages (spam)
• Violating applicable law
• Attempting to hack or disrupt the original "MAX" service
2.3. The technical implementation follows fair-use principles and does not infringe the rights holder's exclusive rights under applicable interoperability and fair-use norms.
2.4. Technical interaction:
• "Komet" uses publicly available interaction methods with "MAX", similar to the web client (https://web.max.ru)
• Requests are made for interoperability purposes
• The developers do not circumvent technical protection measures or decompile the original software
3. Technical aspects
3.1. "Komet" uses only publicly available methods to interact with "MAX" through official endpoints.
3.2. Requests are made under fair use for interoperability.
3.3. The developers are not liable for:
• Changes to the original service API
• Account blocks
• Functional limitations caused by Communication Platform LLC
4. Privacy
4.1. "Komet" does not store or process users' personal data on developers' servers.
4.2. Authentication data is sent directly to Communication Platform LLC servers.
4.3. The developers do not have access to logins, passwords, chats, or other personal user data.
5. Liability and limitations
5.1. "Komet" is provided "as is" without warranties.
5.2. The developers may discontinue support at any time without notice.
6. Legal basis
6.1. Development and distribution of "Komet" are carried out with regard to interoperability, fair use, and publicly available information principles, including EU Directive 2019/790 on interoperability where applicable.
6.2. Interaction with "MAX" occurs only through:
• Public interfaces available via the web client
• Reverse engineering methods permitted for interoperability purposes where applicable by law
• Open interaction protocols not protected by technical protection measures
6.3. "Komet" does not circumvent technical protection measures or disrupt the normal operation of the original service.
7. Final provisions
7.1. By using "Komet", you agree that:
• The authorized way to use "MAX" is through official clients
• Service-related claims should be directed to Communication Platform LLC
• The App developers are not liable for any direct or indirect damages
7.2. These terms may change without prior notice.
8. Security and privacy features
8.1. "Komet" may include privacy tools:
• Session data substitution to reduce tracking via OSINT-style techniques
• Proxy connectivity for safer network access
• Reduced telemetry where applicable
8.2. These features:
• Aim to protect user privacy
• Are not intended to bypass the original service's security systems
• Are implemented with respect for privacy protections under applicable law
8.3. The developers are not liable for:
• Blocks related to privacy tools
• Service behavior when such features are enabled
8.4. Session export and import
8.4.1. "Komet" may allow exporting and importing session data for:
• Moving data between your devices
• Backing up credentials
• Restoring access if a device is lost
8.4.2. Implementation notes:
• Export may be decoupled from the phone number where technically possible
• Session data may be protected with a password and encryption (e.g. AES-256)
• Encryption keys are known only to you and are not stored by the developers
8.4.3. Technical approach:
• Export/import may use authorization tokens for service identification
• Session parameters may be adjusted to preserve authentication context
• Proxy settings may be included for a single connectivity configuration
• Imported sessions may route traffic according to your proxy settings
• The original service may not receive explicit device-change metadata beyond what the protocol normally sends
• Encryption may apply to the full exported package (session + proxy configuration)
8.4.4. Legal considerations:
• Processing is based on your consent where personal data laws require it
• Data minimization: only what is needed for operation
• Token use is not unauthorized access under applicable criminal computer-access laws
• Session handling is analogous to legitimate session persistence (e.g. cookies)
• IP masking can be a lawful way to protect personal data where applicable
8.4.5. Limitations:
• You are responsible for passwords and backups
• Developers cannot access your encrypted session exports
• Lost passwords cannot be recovered by design
• Encryption keys are not stored in the app and are known only to you
''';
+101
View File
@@ -0,0 +1,101 @@
const String kTermsOfServiceRu = r'''
Условия использования неофициального клиента на MAX, именуемым "KometClient" или же "Komet"
1. Статус и отношения
1.1. «Komet» (далее — «Приложение») — неофициальное стороннее приложение, не имеющее отношения к ООО «Коммуникационная платформа" (правообладатель сервиса «MAX").
1.2. Разработчики Приложения не являются партнёрами, сотрудниками или аффилированными лицами ООО «Коммуникационная платформа».
1.3. Все упоминания торговых марок «MAX» и связанных сервисов принадлежат их правообладателям.
2. Условия использования
2.1. Используя Приложение «Komet», вы:
• Автоматически подтверждаете согласие с официальным Пользовательским соглашением «MAX» (https://legal.max.ru/ps)
• Осознаёте, что использование неофициального клиента может привести к блокировке аккаунта со стороны ООО «Коммуникационная платформа»;
• Принимаете на себя все риски, связанные с использованием Приложения.
2.2. Строго запрещено:
• Использовать Приложение «Komet» для распространения запрещённого контента;
• Осуществлять массовые рассылки (спам);
• Нарушать законодательство РФ и международное право;
• Предпринимать попытки взлома или нарушения работы оригинального сервиса «MAX».
2.3. Техническая реализация соответствует принципу добросовестного использования (свободное использование) и не нарушает исключительные права правообладателя в соответствии с статьёй 1273 ГК РФ.
2.4. Особенности технического взаимодействия:
• Приложение «Komet» использует публично доступные методы взаимодействия с сервисом «MAX», аналогичные веб-версии (https://web.max.ru)
• Все запросы выполняются в рамках добросовестного использования для обеспечения совместимости;
• Разработчики не осуществляют обход технических средств защиты и не декомпилируют оригинальное ПО.
3. Технические аспекты
3.1. Приложение «Komet» использует только публично доступные методы взаимодействия с сервисом «MAX» через официальные конечные точки.
3.2. Все запросы выполняются в рамках добросовестного использования (fair use) для обеспечения совместимости.
3.3. Разработчики не несут ответственности за:
• Изменения в API оригинального сервиса;
• Блокировку аккаунтов пользователей;
• Функциональные ограничения, вызванные действиями ООО «Коммуникационная платформа».
4. Конфиденциальность
4.1. Приложение «Komet» не хранит и не обрабатывает персональные данные пользователей.
4.2. Все данные авторизации передаются напрямую серверам ООО «Коммуникационная платформа».
4.3. Разработчики не имеют доступа к логинам, паролям, переписке и другим персональным данным пользователей.
5. Ответственность и ограничения
5.1. Приложение «Komet» предоставляется «как есть» (as is) без гарантий работоспособности.
5.2. Разработчики вправе прекратить поддержку Приложения в любой момент без объяснения причин.
6. Правовые основания
6.1. Разработка и распространение Приложения «Komet» осуществляются в соответствии с:
• Статья 1280.3 ГК РФ — декомпилирование программы для обеспечения совместимости;
• Статья 1229 ГК РФ — ограничения исключительного права в информационных целях;
• Федеральный закон № 149‑ФЗ «Об информации» — использование общедоступной информации;
• Право на межоперабельность (Directive (EU) 2019/790) — обеспечение взаимодействия программ.
6.2. Взаимодействие с сервисом «MAX» осуществляется исключительно через:
• Публичные API‑интерфейсы, доступные через веб‑версию сервиса;
• Методы обратной разработки, разрешённые ст. 1280.3 ГК РФ для целей совместимости;
• Открытые протоколы взаимодействия, не защищённые техническими средствами охраны.
6.3. Приложение «Komet» не обходит технические средства защиты и не нарушает нормальную работу оригинального сервиса, что соответствует требованиям статьи 1299 ГК РФ.
7. Заключительные положения
7.1. Используя Приложение «Komet», вы соглашаетесь с тем, что:
• Единственным правомочным способом использования сервиса «MAX» является применение официальных клиентов;
• Все претензии по работе сервиса должны направляться в ООО «Коммуникационная платформа»;
• Разработчики Приложения не несут ответственности за любые косвенные или прямые убытки.
7.2. Настоящее соглашение может быть изменено без предварительного уведомления пользователей.
8. Функции безопасности и конфиденциальности
8.1. Приложение «Komet» включает инструменты защиты приватности:
• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов OpenSourceIntelligence (OSINT);
• Система прокси‑подключений — для обеспечения безопасности сетевого взаимодействия;
• Ограничение телеметрии — для минимизации передачи диагностических данных.
8.2. Данные функции:
• Направлены исключительно на защиту конфиденциальности пользователей;
• Не используются для обхода систем безопасности оригинального сервиса;
• Реализованы в рамках статьи 152.1 ГК РФ о защите частной жизни.
8.3. Разработчики не несут ответственности за:
• Блокировки, связанные с использованием инструментов конфиденциальности;
• Изменения в работе сервиса при активации данных функций.
8.4. Функции экспорта и импорта сессии
8.4.1. Приложение «Komet» предоставляет возможность экспорта и импорта данных сессии для:
• Обеспечения переносимости данных между устройствами пользователя
• Резервного копирования учетных данных
• Восстановления доступа при утере устройства
8.4.2. Особенности реализации:
• Экспорт сессии осуществляется без привязки к номеру телефона
• Данные сессии защищаются паролем и шифрованием по алгоритмам AES‑256
• Ключ шифрования известен только пользователю и не сохраняется в приложении
8.4.3. Техническая реализация экспорта сессии:
• Экспорт сессии осуществляется через токен авторизации для идентификации в сервисе
• Используется подмена параметров сессии для сохранения контекста аутентификации
• Интеграция настроек прокси для обеспечения единой конфигурации подключения
• Импортированная сессия маскирует источник подключения через указанные прокси‑настройки
• Серверы оригинального сервиса не получают данных о смене устройства пользователя
• Шифрование применяется ко всему пакету данных (сессия + прокси‑конфиг)
8.4.4. Правовые основания:
• Статья 6 ФЗ‑152 «О персональных данных» — обработка данных с согласия субъекта
• Статья 434 ГК РФ — право на выбор формы сделки (электронная форма хранения учетных данных)
• Принцип минимизации данных — сбор только необходимой для работы информации
• Использование токена не является несанкционированным доступом (ст. 272 УК РФ не нарушается)
• Подмена сессии — легитимный метод сохранения аутентификации (аналог браузерных cookies)
• Маскировка IP‑адреса — законный способ защиты персональных данных (ст. 6 ФЗ‑152)
8.4.5. Ограничения ответственности:
• Пользователь самостоятельно несет ответственность за сохранность пароля и резервных копий
• Разработчики не имеют доступа к зашифрованным данным сессии
• Восстановление утерянных паролей невозможно в целях безопасности
• Ключи шифрования не хранятся в приложении и известны только пользователю
''';
+55 -4
View File
@@ -1,6 +1,8 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:komet/l10n/app_localizations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'backend/api.dart';
import 'backend/modules/account.dart';
import 'backend/modules/messages.dart';
@@ -13,19 +15,65 @@ final api = Api();
final accountModule = AccountModule(api);
final messagesModule = MessagesModule(api);
Future<Locale> _loadInitialLocale() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString('app_locale');
if (code != null && (code == 'en' || code == 'ru')) {
return Locale(code);
}
final platform = WidgetsBinding.instance.platformDispatcher.locale;
if (platform.languageCode == 'en' || platform.languageCode == 'ru') {
return Locale(platform.languageCode);
}
return const Locale('ru');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AppDatabase.init();
await api.connect();
runApp(const MyApp());
final initialLocale = await _loadInitialLocale();
runApp(KometApp(initialLocale: initialLocale));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
class KometApp extends StatefulWidget {
const KometApp({super.key, required this.initialLocale});
final Locale initialLocale;
static KometAppState? stateOf(BuildContext context) {
return context.findAncestorStateOfType<KometAppState>();
}
@override
State<KometApp> createState() => KometAppState();
}
class KometAppState extends State<KometApp> {
static const _fallbackSeed = Color(0xFFC1C4FF);
static ColorScheme _adjustScheme(ColorScheme base) {
late Locale _locale;
@override
void initState() {
super.initState();
_locale = widget.initialLocale;
}
Future<void> applyLocale(Locale locale) async {
if (!AppLocalizations.supportedLocales.any(
(l) => l.languageCode == locale.languageCode,
)) {
return;
}
final prefs = await SharedPreferences.getInstance();
await prefs.setString('app_locale', locale.languageCode);
if (mounted) {
setState(() => _locale = locale);
}
}
ColorScheme _adjustScheme(ColorScheme base) {
return base.copyWith(
surface: Color.alphaBlend(
base.primary.withValues(alpha: 0.05),
@@ -58,6 +106,9 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Komet',
debugShowCheckedModeBanner: false,
locale: _locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
useMaterial3: true,
colorScheme: darkScheme,