Экран выбора страны, динамик цвета

This commit is contained in:
noxzion
2026-03-17 16:36:58 +05:00
parent 65010cccfc
commit f843f467e9
8 changed files with 436 additions and 363 deletions
+1
View File
@@ -1,2 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
kotlin.incremental=false
@@ -53,7 +53,6 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen> {
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<CodeConfirmationScreen> {
@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<CodeConfirmationScreen> {
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<CodeConfirmationScreen> {
),
),
),
GestureDetector(
onTap: () => _focusNode.requestFocus(),
child: FittedBox(
@@ -144,12 +143,12 @@ class _CodeConfirmationScreenState extends State<CodeConfirmationScreen> {
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<CodeConfirmationScreen> {
child: Text(
char,
key: ValueKey<String>(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<CodeConfirmationScreen> {
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<CodeConfirmationScreen> {
}
},
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,
),
),
],
+186 -235
View File
@@ -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<LoginScreen> {
_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<CountryName>(
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'
'• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов 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'
'• Ключи шифрования не хранятся в приложении и известны только пользователю',
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'
'• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов 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'
'• Ключи шифрования не хранятся в приложении и известны только пользователю',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.5,
),
),
],
),
),
],
),
),
),
),
);
},
);
}
@@ -258,6 +207,7 @@ class _LoginScreenState extends State<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
Text(
'Это правильный номер?',
style: GoogleFonts.inter(
color: Colors.white70,
color: cs.onSurfaceVariant,
fontSize: 14,
fontWeight: FontWeight.w500,
),
@@ -289,7 +239,7 @@ class _LoginScreenState extends State<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
@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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
Container(
width: 1,
height: 24,
color: Colors.white24,
color: cs.outlineVariant,
),
const SizedBox(width: 12),
Expanded(
@@ -474,10 +425,10 @@ class _LoginScreenState extends State<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
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<LoginScreen> {
);
}
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<LoginScreen> {
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<LoginScreen> {
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,
),
@@ -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<SelectCountryScreen> createState() => _SelectCountryScreenState();
}
class _SelectCountryScreenState extends State<SelectCountryScreen> {
bool _isSearching = false;
final TextEditingController _searchController = TextEditingController();
List<CountryName> _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);
},
);
},
),
);
}
}
@@ -15,8 +15,9 @@ class _ChatListScreenState extends State<ChatListScreen> {
@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<ChatListScreen> {
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<ChatListScreen> {
),
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<ChatListScreen> {
),
),
),
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<ChatListScreen> {
),
),
),
SliverToBoxAdapter(
child: SizedBox(
height: 48,
@@ -187,11 +186,11 @@ class _ChatListScreenState extends State<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
}
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
}
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
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<ChatListScreen> {
}
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<ChatListScreen> {
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<ChatListScreen> {
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(
color: Colors.black,
style: TextStyle(
color: cs.onPrimary,
fontSize: 13,
fontWeight: FontWeight.w600,
),
+42 -11
View File
@@ -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(),
);
},
);
}
}
+8 -56
View File
@@ -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:
+1
View File
@@ -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: