Экран выбора страны, динамик цвета
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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'
|
||||
'• Подмена данных сессии — для предотвращения отслеживания пользователя с помощью продвинутых инструментов 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<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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user