From 81ea86c546e2d903b8a5c20e2e8c282e6e05884e Mon Sep 17 00:00:00 2001 From: noxzion Date: Tue, 17 Mar 2026 16:36:58 +0500 Subject: [PATCH] =?UTF-8?q?=D0=AD=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D1=8B?= =?UTF-8?q?,=20=D0=B4=D0=B8=D0=BD=D0=B0=D0=BC=D0=B8=D0=BA=20=D1=86=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/gradle.properties | 1 + .../auth/code_confirmation_screen.dart | 37 +- lib/frontend/screens/auth/login_screen.dart | 421 ++++++++---------- .../screens/auth/select_country_screen.dart | 135 ++++++ .../screens/chats/chat_list_screen.dart | 87 ++-- lib/main.dart | 53 ++- pubspec.lock | 64 +-- pubspec.yaml | 1 + 8 files changed, 436 insertions(+), 363 deletions(-) create mode 100644 lib/frontend/screens/auth/select_country_screen.dart diff --git a/android/gradle.properties b/android/gradle.properties index fbee1d8..23bd6e3 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true +kotlin.incremental=false diff --git a/lib/frontend/screens/auth/code_confirmation_screen.dart b/lib/frontend/screens/auth/code_confirmation_screen.dart index cd9861f..0ea6a65 100644 --- a/lib/frontend/screens/auth/code_confirmation_screen.dart +++ b/lib/frontend/screens/auth/code_confirmation_screen.dart @@ -53,7 +53,6 @@ class _CodeConfirmationScreenState extends State { void _resendCode() { if (_timerSeconds == 0) { _startTimer(); - // TODO: Implement actual resend logic here print('Resending code to ${widget.phoneNumber}'); } } @@ -68,13 +67,14 @@ class _CodeConfirmationScreenState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFF0D0D12), + backgroundColor: cs.surface, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white70), + icon: Icon(Icons.arrow_back, color: cs.onSurfaceVariant), onPressed: () => Navigator.pop(context), ), ), @@ -88,16 +88,16 @@ class _CodeConfirmationScreenState extends State { Text( widget.phoneNumber, style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 22, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 12), - const Text( + Text( 'Мы отправили SMS с кодом подтверждения на ваш номер телефона.', style: TextStyle( - color: Colors.white54, + color: cs.outline, fontSize: 15, fontWeight: FontWeight.w400, height: 1.4, @@ -129,7 +129,6 @@ class _CodeConfirmationScreenState extends State { ), ), ), - GestureDetector( onTap: () => _focusNode.requestFocus(), child: FittedBox( @@ -144,12 +143,12 @@ class _CodeConfirmationScreenState extends State { height: 54, margin: EdgeInsets.only(right: index == 4 ? 0 : 10), decoration: BoxDecoration( - color: const Color(0xFF1E1E2A), + color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(8), border: Border.all( color: isFocused - ? const Color(0xFFBEC2FF) - : (hasValue ? Colors.white24 : Colors.transparent), + ? cs.primary + : (hasValue ? cs.outlineVariant : Colors.transparent), width: 1.5, ), ), @@ -165,8 +164,8 @@ class _CodeConfirmationScreenState extends State { child: Text( char, key: ValueKey(char + index.toString()), - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onSurface, fontSize: 20, fontWeight: FontWeight.w600, ), @@ -183,11 +182,11 @@ class _CodeConfirmationScreenState extends State { GestureDetector( onTap: _resendCode, child: Text( - _timerSeconds > 0 - ? 'Отправить повторно через $_timerSeconds сек.' + _timerSeconds > 0 + ? 'Отправить повторно через $_timerSeconds сек.' : 'Отправить код по SMS', - style: const TextStyle( - color: Color(0xFF63A9F5), + style: TextStyle( + color: cs.tertiary, fontSize: 14, fontWeight: FontWeight.w400, ), @@ -204,15 +203,15 @@ class _CodeConfirmationScreenState extends State { } }, backgroundColor: _codeController.text.length == 5 - ? const Color(0xffc1c4ff) - : const Color(0xFF1E1E2A), + ? cs.primaryContainer + : cs.surfaceContainerHighest, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), child: Icon( Icons.arrow_forward, - color: _codeController.text.length == 5 ? Colors.black : Colors.white24, + color: _codeController.text.length == 5 ? cs.onPrimaryContainer : cs.onSurfaceVariant, ), ), ], diff --git a/lib/frontend/screens/auth/login_screen.dart b/lib/frontend/screens/auth/login_screen.dart index d950724..fb375b4 100644 --- a/lib/frontend/screens/auth/login_screen.dart +++ b/lib/frontend/screens/auth/login_screen.dart @@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:komet/core/config/countries.dart'; import 'code_confirmation_screen.dart'; +import 'select_country_screen.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -24,228 +25,176 @@ class _LoginScreenState extends State { _selectedCountry = countriesByCode['RU'] ?? allCountries.first; } - void _showCountryPicker() { - showModalBottomSheet( - context: context, - backgroundColor: const Color(0xFF1E1E2A), - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - ), - builder: (context) => DraggableScrollableSheet( - initialChildSize: 0.7, - minChildSize: 0.5, - maxChildSize: 1.0, - snap: true, - snapSizes: const [0.7, 1.0], - expand: false, - builder: (context, scrollController) => Column( - children: [ - Padding( - padding: const EdgeInsets.all(24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Выберите страну', - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Symbols.close, color: Colors.white70), - ), - ], - ), - ), - Expanded( - child: ListView.builder( - controller: scrollController, - itemCount: allCountries.length, - itemBuilder: (context, index) { - final country = allCountries[index]; - return ListTile( - leading: Text( - country.phoneCode, - style: const TextStyle(color: Colors.white54, fontSize: 14), - ), - title: Text( - country.ru, - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w400, - ), - ), - onTap: () { - setState(() { - _selectedCountry = country; - _phoneController.clear(); - _isPhoneValid = false; - }); - Navigator.pop(context); - }, - ); - }, - ), - ), - ], + void _showCountryPicker() async { + final result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SelectCountryScreen( + selectedCountry: _selectedCountry, ), ), ); + if (result != null) { + setState(() { + _selectedCountry = result; + _phoneController.clear(); + _isPhoneValid = false; + }); + } } void _showTOS(BuildContext context) { + final cs = Theme.of(context).colorScheme; showModalBottomSheet( context: context, - backgroundColor: const Color(0xFF1E1E2A), + backgroundColor: cs.surfaceContainerHigh, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), - builder: (context) => DraggableScrollableSheet( - initialChildSize: 0.7, - minChildSize: 0.5, - maxChildSize: 1.0, - snap: true, - snapSizes: const [0.7, 1.0], - expand: false, - builder: (context, scrollController) => Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Условия использования', - style: GoogleFonts.inter( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w500, - ), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Symbols.close, color: Colors.white70), - ), - ], - ), - const SizedBox(height: 16), - Expanded( - child: ListView( - controller: scrollController, - children: const [ + builder: (context) { + final cs = Theme.of(context).colorScheme; + return DraggableScrollableSheet( + initialChildSize: 0.7, + minChildSize: 0.5, + maxChildSize: 1.0, + snap: true, + snapSizes: const [0.7, 1.0], + expand: false, + builder: (context, scrollController) => Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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' - '• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов Open‑Source‑Intelligence (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' - '• Ключи шифрования не хранятся в приложении и известны только пользователю', - style: TextStyle( - color: Colors.white70, - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1.5, + 'Условия использования', + style: GoogleFonts.inter( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.w500, ), ), + IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Symbols.close, color: cs.onSurfaceVariant), + ), ], ), - ), - ], + const SizedBox(height: 16), + Expanded( + 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' + '• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов Open‑Source‑Intelligence (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' + '• Ключи шифрования не хранятся в приложении и известны только пользователю', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.5, + ), + ), + ], + ), + ), + ], + ), ), - ), - ), + ); + }, ); } @@ -258,6 +207,7 @@ class _LoginScreenState extends State { transitionDuration: const Duration(milliseconds: 250), pageBuilder: (context, anim1, anim2) => const SizedBox.shrink(), transitionBuilder: (context, anim1, anim2, child) { + final cs = Theme.of(context).colorScheme; final curve = Curves.easeOutQuart.transform(anim1.value); return Opacity( opacity: anim1.value, @@ -266,7 +216,7 @@ class _LoginScreenState extends State { child: Transform.scale( scale: 0.8 + (0.2 * curve), child: AlertDialog( - backgroundColor: const Color(0xFF1E1E2A), + backgroundColor: cs.surfaceContainerHigh, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -280,7 +230,7 @@ class _LoginScreenState extends State { Text( 'Это правильный номер?', style: GoogleFonts.inter( - color: Colors.white70, + color: cs.onSurfaceVariant, fontSize: 14, fontWeight: FontWeight.w500, ), @@ -289,7 +239,7 @@ class _LoginScreenState extends State { Text( '${_selectedCountry.phoneCode} $formattedPhone', style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.w500, ), @@ -305,7 +255,7 @@ class _LoginScreenState extends State { child: Text( 'Изменить', style: GoogleFonts.inter( - color: const Color(0xFFAFAFFF), + color: cs.primary, fontSize: 15, fontWeight: FontWeight.w500, ), @@ -326,7 +276,7 @@ class _LoginScreenState extends State { child: Text( 'Готово', style: GoogleFonts.inter( - color: const Color(0xFFAFAFFF), + color: cs.primary, fontSize: 15, fontWeight: FontWeight.w600, ), @@ -349,8 +299,9 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFF0D0D12), + backgroundColor: cs.surface, body: GestureDetector( behavior: HitTestBehavior.translucent, onVerticalDragEnd: (details) { @@ -376,17 +327,17 @@ class _LoginScreenState extends State { children: [ IconButton( onPressed: () {}, - icon: const Icon( + icon: Icon( Symbols.admin_panel_settings, - color: Colors.white70, + color: cs.onSurfaceVariant, weight: 400, ), ), IconButton( onPressed: () {}, - icon: const Icon( + icon: Icon( Symbols.language, - color: Colors.white70, + color: cs.onSurfaceVariant, weight: 400, ), ), @@ -399,23 +350,23 @@ class _LoginScreenState extends State { Image.asset( 'assets/komet.png', height: 80, - color: Colors.white, + color: cs.onSurface, ), const SizedBox(height: 16), Text( 'Войдите в Komet', style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 32, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), - const Text( + Text( 'Проверьте код страны и введите свой\nномер телефона.', textAlign: TextAlign.center, style: TextStyle( - color: Colors.white70, + color: cs.onSurfaceVariant, fontSize: 16, fontWeight: FontWeight.w400, height: 1.4, @@ -435,13 +386,13 @@ class _LoginScreenState extends State { Text( _selectedCountry.ru, style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w400, ), ), const Spacer(), - const Icon(Icons.keyboard_arrow_down, color: Colors.white70), + Icon(Icons.keyboard_arrow_down, color: cs.onSurfaceVariant), ], ), ), @@ -454,7 +405,7 @@ class _LoginScreenState extends State { Text( _selectedCountry.phoneCode, style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w400, ), @@ -463,7 +414,7 @@ class _LoginScreenState extends State { Container( width: 1, height: 24, - color: Colors.white24, + color: cs.outlineVariant, ), const SizedBox(width: 12), Expanded( @@ -474,10 +425,10 @@ class _LoginScreenState extends State { FilteringTextInputFormatter.digitsOnly, _PhoneInputFormatter(_selectedCountry), ], - style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w400), - decoration: const InputDecoration( + style: TextStyle(color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w400), + decoration: InputDecoration( hintText: '(000) 000-00-00', - hintStyle: TextStyle(color: Colors.white38, fontSize: 15, fontWeight: FontWeight.w400), + hintStyle: TextStyle(color: cs.outline, fontSize: 15, fontWeight: FontWeight.w400), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -499,7 +450,7 @@ class _LoginScreenState extends State { child: Text( 'Другие способы входа', style: GoogleFonts.inter( - color: const Color(0xFFAFAFFF), + color: cs.primary, fontSize: 14, fontWeight: FontWeight.w400, height: 1.4, @@ -516,7 +467,7 @@ class _LoginScreenState extends State { child: RichText( text: TextSpan( style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w500, height: 1.4, @@ -525,7 +476,7 @@ class _LoginScreenState extends State { TextSpan( text: 'Продолжая, вы соглашаетесь с \n', style: GoogleFonts.inter( - color: Colors.white, + color: cs.onSurface, fontSize: 14, height: 1.4, fontWeight: FontWeight.w400, @@ -534,7 +485,7 @@ class _LoginScreenState extends State { TextSpan( text: 'пользовательскими соглашениями', style: GoogleFonts.inter( - color: const Color(0xFFAFAFFF), + color: cs.primary, fontSize: 14, height: 1.4, fontWeight: FontWeight.w400, @@ -551,11 +502,11 @@ class _LoginScreenState extends State { padding: const EdgeInsets.only(bottom: 16.0), child: FloatingActionButton( onPressed: _isPhoneValid ? _validateAndSubmit : null, - backgroundColor: _isPhoneValid ? const Color(0xffc1c4ff) : Colors.white10, + backgroundColor: _isPhoneValid ? cs.primaryContainer : cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), - child: Icon(Icons.arrow_forward, color: _isPhoneValid ? Colors.black : Colors.white24), + child: Icon(Icons.arrow_forward, color: _isPhoneValid ? cs.onPrimaryContainer : cs.onSurfaceVariant), ), ), ], @@ -574,11 +525,11 @@ class _LoginScreenState extends State { ); } - Widget _buildInputField({ required String label, required Widget content, }) { + final cs = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -591,7 +542,7 @@ class _LoginScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( border: Border.all( - color: const Color(0xFFBEC2FF), + color: cs.primary, width: 1.5, ), borderRadius: BorderRadius.circular(50), @@ -602,12 +553,12 @@ class _LoginScreenState extends State { top: -10, left: 20, child: Container( - color: const Color(0xFF0D0D12), + color: cs.surface, padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( label, style: GoogleFonts.inter( - color: const Color(0xFFBEC2FF), + color: cs.primary, fontSize: 14, fontWeight: FontWeight.w400, ), diff --git a/lib/frontend/screens/auth/select_country_screen.dart b/lib/frontend/screens/auth/select_country_screen.dart new file mode 100644 index 0000000..a72335e --- /dev/null +++ b/lib/frontend/screens/auth/select_country_screen.dart @@ -0,0 +1,135 @@ +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'; + +class SelectCountryScreen extends StatefulWidget { + final CountryName selectedCountry; + + const SelectCountryScreen({super.key, required this.selectedCountry}); + + @override + State createState() => _SelectCountryScreenState(); +} + +class _SelectCountryScreenState extends State { + bool _isSearching = false; + final TextEditingController _searchController = TextEditingController(); + List _filteredCountries = allCountries; + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + void _filterCountries(String query) { + setState(() { + if (query.isEmpty) { + _filteredCountries = allCountries; + } else { + final q = query.toLowerCase(); + _filteredCountries = allCountries.where((c) { + return c.ru.toLowerCase().contains(q) || + c.en.toLowerCase().contains(q) || + c.phoneCode.contains(q); + }).toList(); + } + }); + } + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + surfaceTintColor: Colors.transparent, + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Symbols.arrow_back, color: cs.onSurface), + ), + title: _isSearching + ? TextField( + controller: _searchController, + autofocus: true, + style: GoogleFonts.inter( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w400, + ), + decoration: InputDecoration( + hintText: 'Поиск страны...', + hintStyle: GoogleFonts.inter( + color: cs.onSurfaceVariant, + fontSize: 18, + fontWeight: FontWeight.w400, + ), + border: InputBorder.none, + ), + onChanged: _filterCountries, + ) + : Text( + 'Выберите страну', + style: GoogleFonts.inter( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ), + centerTitle: true, + actions: [ + IconButton( + onPressed: () { + setState(() { + _isSearching = !_isSearching; + if (!_isSearching) { + _searchController.clear(); + _filteredCountries = allCountries; + } + }); + }, + icon: Icon( + _isSearching ? Symbols.close : Symbols.search, + color: cs.onSurface, + ), + ), + ], + ), + body: ListView.builder( + itemCount: _filteredCountries.length, + itemBuilder: (context, index) { + final country = _filteredCountries[index]; + final isSelected = country.code == widget.selectedCountry.code; + + return ListTile( + leading: Text( + country.phoneCode, + style: GoogleFonts.inter( + color: cs.onSurfaceVariant, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + title: Text( + country.ru, + style: GoogleFonts.inter( + color: cs.onSurface, + fontSize: 16, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + ), + ), + trailing: isSelected + ? Icon(Symbols.check, color: cs.primary) + : null, + onTap: () { + Navigator.pop(context, country); + }, + ); + }, + ), + ); + } +} diff --git a/lib/frontend/screens/chats/chat_list_screen.dart b/lib/frontend/screens/chats/chat_list_screen.dart index 5358b67..4260cd8 100644 --- a/lib/frontend/screens/chats/chat_list_screen.dart +++ b/lib/frontend/screens/chats/chat_list_screen.dart @@ -15,8 +15,9 @@ class _ChatListScreenState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFF0D0D12), + backgroundColor: cs.surface, body: SafeArea( bottom: false, child: Stack( @@ -29,10 +30,10 @@ class _ChatListScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( + Text( 'Подключение...', style: TextStyle( - color: Colors.white, + color: cs.onSurface, fontSize: 20, fontWeight: FontWeight.w600, fontFamily: 'Outfit', @@ -40,9 +41,9 @@ class _ChatListScreenState extends State { ), IconButton( onPressed: () {}, - icon: const Icon( + icon: Icon( Symbols.more_vert, - color: Colors.white54, + color: cs.outline, weight: 400, ), ), @@ -63,27 +64,26 @@ class _ChatListScreenState extends State { ), ), ), - SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), sliver: SliverToBoxAdapter( child: Container( height: 44, decoration: BoxDecoration( - color: const Color(0xFF1E1E2A), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(50), ), padding: const EdgeInsets.symmetric(horizontal: 16), - child: const Row( + child: Row( children: [ - Icon(Symbols.search, color: Colors.white38, size: 20, weight: 400), - SizedBox(width: 10), + Icon(Symbols.search, color: cs.outline, size: 20, weight: 400), + const SizedBox(width: 10), Expanded( child: TextField( - style: TextStyle(color: Colors.white, fontSize: 15), + style: TextStyle(color: cs.onSurface, fontSize: 15), decoration: InputDecoration( hintText: 'Поиск', - hintStyle: TextStyle(color: Colors.white38, fontSize: 15), + hintStyle: TextStyle(color: cs.outline, fontSize: 15), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, @@ -95,7 +95,6 @@ class _ChatListScreenState extends State { ), ), ), - SliverToBoxAdapter( child: SizedBox( height: 48, @@ -187,11 +186,11 @@ class _ChatListScreenState extends State { height: 68, padding: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( - color: const Color(0xFF1E1E28), + color: cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(34), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.5), + color: Colors.black.withValues(alpha: 0.5), blurRadius: 20, offset: const Offset(0, 10), ), @@ -200,17 +199,17 @@ class _ChatListScreenState extends State { child: LayoutBuilder( builder: (context, constraints) { double totalWidth = constraints.maxWidth; - + double totalWeight = 5.2; double unitWidth = totalWidth / totalWeight; double activeWidth = unitWidth * 2.2; double inactiveWidth = unitWidth * 1.0; - + double leftOffset = 0; for (int i = 0; i < _currentNavIndex; i++) { leftOffset += inactiveWidth; } - + return Stack( children: [ AnimatedPositioned( @@ -222,7 +221,7 @@ class _ChatListScreenState extends State { width: activeWidth - 8, child: Container( decoration: BoxDecoration( - color: const Color(0xFFBEC2FF), + color: cs.primary, borderRadius: BorderRadius.circular(26), ), ), @@ -237,7 +236,7 @@ class _ChatListScreenState extends State { case 2: icon = Symbols.person_pin; label = 'Контакты'; break; default: icon = Symbols.settings; label = 'Настройки'; } - + bool isSelected = _currentNavIndex == index; return AnimatedContainer( duration: const Duration(milliseconds: 350), @@ -261,11 +260,11 @@ class _ChatListScreenState extends State { bottom: 110, child: FloatingActionButton( onPressed: () {}, - backgroundColor: const Color(0xFFC1C4FF), + backgroundColor: cs.primaryContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - child: const Icon(Symbols.add, color: Colors.black, size: 28, weight: 400), + child: Icon(Symbols.add, color: cs.onPrimaryContainer, size: 28, weight: 400), ), ), ], @@ -275,6 +274,7 @@ class _ChatListScreenState extends State { } Widget _buildStoryItem(String name, String imageUrl, bool hasUpdate) { + final cs = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.only(right: 16), child: Column( @@ -284,8 +284,8 @@ class _ChatListScreenState extends State { decoration: BoxDecoration( shape: BoxShape.circle, border: hasUpdate - ? Border.all(color: const Color(0xFFC1C4FF), width: 2) - : Border.all(color: Colors.white10), + ? Border.all(color: cs.primary, width: 2) + : Border.all(color: cs.outlineVariant), ), child: CircleAvatar( radius: 26, @@ -295,8 +295,8 @@ class _ChatListScreenState extends State { const SizedBox(height: 6), Text( name, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onSurface, fontSize: 11, fontWeight: FontWeight.w500, ), @@ -307,19 +307,20 @@ class _ChatListScreenState extends State { } Widget _buildFolderChip(String title) { + final cs = Theme.of(context).colorScheme; bool isSelected = _selectedCategory == title; return GestureDetector( onTap: () => setState(() => _selectedCategory = title), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( - color: isSelected ? const Color(0xFFC1C4FF) : const Color(0xFF1E1E2A), + color: isSelected ? cs.primaryContainer : cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Text( title, style: TextStyle( - color: isSelected ? Colors.black : Color(0xFFC1C4FF), + color: isSelected ? cs.onPrimaryContainer : cs.primary, fontSize: 14, fontWeight: FontWeight.w500, ), @@ -339,6 +340,7 @@ class _ChatListScreenState extends State { int unreadCount = 0, bool isMuted = false, }) { + final cs = Theme.of(context).colorScheme; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 2), leading: Stack( @@ -355,9 +357,9 @@ class _ChatListScreenState extends State { width: 12, height: 12, decoration: BoxDecoration( - color: const Color(0xFFC1C4FF), + color: cs.primary, shape: BoxShape.circle, - border: Border.all(color: const Color(0xFF0D0D12), width: 2), + border: Border.all(color: cs.surface, width: 2), ), ), ), @@ -368,20 +370,20 @@ class _ChatListScreenState extends State { Expanded( child: Text( name, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600, ), ), ), if (isMuted) - const Icon(Symbols.notifications_off, color: Colors.white24, size: 14, weight: 400), + Icon(Symbols.notifications_off, color: cs.outlineVariant, size: 14, weight: 400), const SizedBox(width: 8), Text( time, - style: const TextStyle( - color: Colors.white38, + style: TextStyle( + color: cs.outline, fontSize: 12, ), ), @@ -395,7 +397,7 @@ class _ChatListScreenState extends State { child: Text( message, style: TextStyle( - color: isTyping ? const Color(0xFFC1C4FF) : Colors.white54, + color: isTyping ? cs.primary : cs.outline, fontSize: 14, fontWeight: isTyping ? FontWeight.w500 : FontWeight.w400, ), @@ -407,20 +409,20 @@ class _ChatListScreenState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( - color: isMuted ? Colors.white10 : const Color(0xFF1E1E2A), + color: isMuted ? cs.surfaceContainerHighest : cs.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), ), child: Text( unreadCount.toString(), style: TextStyle( - color: isMuted ? Colors.white38 : Colors.white, + color: isMuted ? cs.outline : cs.onSurface, fontSize: 11, fontWeight: FontWeight.w600, ), ), ) else if (isRead) - const Icon(Symbols.done_all, color: Color(0xFFC1C4FF), size: 16, weight: 400), + Icon(Symbols.done_all, color: cs.primary, size: 16, weight: 400), ], ), ), @@ -428,6 +430,7 @@ class _ChatListScreenState extends State { } Widget _buildNavItem(int index, IconData icon, String label) { + final cs = Theme.of(context).colorScheme; bool isSelected = _currentNavIndex == index; return GestureDetector( onTap: () => setState(() => _currentNavIndex = index), @@ -441,7 +444,7 @@ class _ChatListScreenState extends State { children: [ Icon( icon, - color: isSelected ? Colors.black : Colors.white, + color: isSelected ? cs.onPrimary : cs.onSurface, size: 20, weight: 400, fill: isSelected ? 1.0 : 0.0, @@ -459,8 +462,8 @@ class _ChatListScreenState extends State { const SizedBox(width: 4), Text( label, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: cs.onPrimary, fontSize: 13, fontWeight: FontWeight.w600, ), diff --git a/lib/main.dart b/lib/main.dart index 0bd0220..3833514 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:komet/frontend/screens/auth/login_screen.dart'; @@ -9,19 +10,49 @@ void main() async { class MyApp extends StatelessWidget { const MyApp({super.key}); + static const _fallbackSeed = Color(0xFFC1C4FF); + + static ColorScheme _adjustScheme(ColorScheme base) { + return base.copyWith( + surface: Color.alphaBlend( + base.primary.withValues(alpha: 0.05), + const Color(0xFF0D0D14), + ), + surfaceContainerHigh: Color.alphaBlend( + base.primary.withValues(alpha: 0.08), + const Color(0xFF1A1A26), + ), + surfaceContainerHighest: Color.alphaBlend( + base.primary.withValues(alpha: 0.12), + const Color(0xFF262636), + ), + ); + } + @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Komet', - debugShowCheckedModeBanner: false, - theme: ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - textTheme: GoogleFonts.interTextTheme( - ThemeData.dark().textTheme, - ), - ), - home: const LoginScreen(), + return DynamicColorBuilder( + builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { + final baseScheme = darkDynamic ?? ColorScheme.fromSeed( + seedColor: _fallbackSeed, + brightness: Brightness.dark, + ); + + final darkScheme = _adjustScheme(baseScheme); + + return MaterialApp( + title: 'Komet', + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + colorScheme: darkScheme, + textTheme: GoogleFonts.interTextTheme( + ThemeData.dark().textTheme, + ), + ), + home: const LoginScreen(), + ); + }, ); } } diff --git a/pubspec.lock b/pubspec.lock index 6a90adc..e7c44c3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" + url: "https://pub.dev" + source: hosted + version: "1.8.1" fake_async: dependency: transitive description: @@ -155,14 +163,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" - source: hosted - version: "4.11.0" leak_tracker: dependency: transitive description: @@ -347,46 +347,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - screen_retriever: - dependency: transitive - description: - name: screen_retriever - sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_linux: - dependency: transitive - description: - name: screen_retriever_linux - sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_macos: - dependency: transitive - description: - name: screen_retriever_macos - sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_platform_interface: - dependency: transitive - description: - name: screen_retriever_platform_interface - sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 - url: "https://pub.dev" - source: hosted - version: "0.2.0" - screen_retriever_windows: - dependency: transitive - description: - name: screen_retriever_windows - sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" - url: "https://pub.dev" - source: hosted - version: "0.2.0" sky_engine: dependency: transitive description: flutter @@ -472,14 +432,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - window_manager: - dependency: "direct main" - description: - name: window_manager - sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" - url: "https://pub.dev" - source: hosted - version: "0.5.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b832e79..60d2dc8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: logger: ^2.6.2 google_fonts: ^6.2.1 material_symbols_icons: ^4.2906.0 + dynamic_color: ^1.8.1 dev_dependencies: flutter_test: