переделал раздел настроек визуала
This commit is contained in:
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:gwid/utils/theme_provider.dart';
|
import 'package:gwid/utils/theme_provider.dart';
|
||||||
import 'package:gwid/screens/settings/customization_screen.dart';
|
import 'package:gwid/screens/settings/customization_screen.dart';
|
||||||
import 'package:gwid/screens/settings/animations_screen.dart';
|
|
||||||
|
|
||||||
class AppearanceSettingsScreen extends StatelessWidget {
|
class AppearanceSettingsScreen extends StatelessWidget {
|
||||||
final bool isModal;
|
final bool isModal;
|
||||||
@@ -29,11 +28,11 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle("Кастомизация", colors),
|
_buildSectionTitle("Персонализация", colors),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
leading: const Icon(Icons.palette_outlined),
|
leading: const Icon(Icons.palette_outlined),
|
||||||
title: const Text("Настройки тем"),
|
title: const Text("Персонализация"),
|
||||||
subtitle: const Text("Тема, обои и другие настройки"),
|
subtitle: const Text("Тема, обои и другие настройки"),
|
||||||
trailing: const Icon(Icons.chevron_right_rounded),
|
trailing: const Icon(Icons.chevron_right_rounded),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -44,21 +43,6 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
leading: const Icon(Icons.animation),
|
|
||||||
title: const Text("Настройки анимаций"),
|
|
||||||
subtitle: const Text("Анимации сообщений и переходов"),
|
|
||||||
trailing: const Icon(Icons.chevron_right_rounded),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => AnimationsScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -103,11 +87,11 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle("Кастомизация", colors),
|
_buildSectionTitle("Персонализация", colors),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
leading: const Icon(Icons.palette_outlined),
|
leading: const Icon(Icons.palette_outlined),
|
||||||
title: const Text("Настройки тем"),
|
title: const Text("Персонализация"),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
@@ -117,19 +101,6 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
leading: const Icon(Icons.animation_outlined),
|
|
||||||
title: const Text("Анимации"),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AnimationsScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -232,11 +203,11 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle("Кастомизация", colors),
|
_buildSectionTitle("Персонализация", colors),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
leading: const Icon(Icons.palette_outlined),
|
leading: const Icon(Icons.palette_outlined),
|
||||||
title: const Text("Настройки тем"),
|
title: const Text("Персонализация"),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
@@ -246,19 +217,6 @@ class AppearanceSettingsScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
leading: const Icon(Icons.animation_outlined),
|
|
||||||
title: const Text("Анимации"),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const AnimationsScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
|
|||||||
: Colors.blue.shade100;
|
: Colors.blue.shade100;
|
||||||
final Color theirBubbleFallback = isCurrentlyDark
|
final Color theirBubbleFallback = isCurrentlyDark
|
||||||
? const Color(0xFF182533)
|
? const Color(0xFF182533)
|
||||||
: Colors.grey.shade200;
|
: const Color(0xFF464646); // RGB(70, 70, 70)
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Кастомизация"),
|
title: const Text("Персонализация"),
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
backgroundColor: colors.surface,
|
backgroundColor: colors.surface,
|
||||||
),
|
),
|
||||||
@@ -275,202 +275,229 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
|
|||||||
_ModernSection(
|
_ModernSection(
|
||||||
title: "Сообщения",
|
title: "Сообщения",
|
||||||
children: [
|
children: [
|
||||||
_SliderTile(
|
// Предпросмотр баблов
|
||||||
icon: Icons.text_fields,
|
const _MessageBubblesPreview(),
|
||||||
label: "Непрозрачность текста",
|
|
||||||
value: theme.messageTextOpacity,
|
|
||||||
min: 0.1,
|
|
||||||
max: 1.0,
|
|
||||||
divisions: 18,
|
|
||||||
onChanged: (value) => theme.setMessageTextOpacity(value),
|
|
||||||
displayValue: "${(theme.messageTextOpacity * 100).round()}%",
|
|
||||||
),
|
|
||||||
_SliderTile(
|
|
||||||
icon: Icons.blur_circular,
|
|
||||||
label: "Интенсивность тени",
|
|
||||||
value: theme.messageShadowIntensity,
|
|
||||||
min: 0.0,
|
|
||||||
max: 0.5,
|
|
||||||
divisions: 10,
|
|
||||||
onChanged: (value) => theme.setMessageShadowIntensity(value),
|
|
||||||
displayValue:
|
|
||||||
"${(theme.messageShadowIntensity * 100).round()}%",
|
|
||||||
),
|
|
||||||
_SliderTile(
|
|
||||||
icon: Icons.rounded_corner,
|
|
||||||
label: "Скругление углов",
|
|
||||||
value: theme.messageBorderRadius,
|
|
||||||
min: 4.0,
|
|
||||||
max: 50.0,
|
|
||||||
divisions: 23,
|
|
||||||
onChanged: (value) => theme.setMessageBorderRadius(value),
|
|
||||||
displayValue: "${theme.messageBorderRadius.round()}px",
|
|
||||||
),
|
|
||||||
const Divider(height: 24),
|
|
||||||
_SliderTile(
|
|
||||||
icon: Icons.menu,
|
|
||||||
label: "Непрозрачность меню",
|
|
||||||
value: theme.messageMenuOpacity,
|
|
||||||
min: 0.1,
|
|
||||||
max: 1.0,
|
|
||||||
divisions: 18,
|
|
||||||
onChanged: (value) => theme.setMessageMenuOpacity(value),
|
|
||||||
displayValue: "${(theme.messageMenuOpacity * 100).round()}%",
|
|
||||||
),
|
|
||||||
_SliderTile(
|
|
||||||
icon: Icons.blur_on,
|
|
||||||
label: "Размытие меню",
|
|
||||||
value: theme.messageMenuBlur,
|
|
||||||
min: 0.0,
|
|
||||||
max: 20.0,
|
|
||||||
divisions: 20,
|
|
||||||
onChanged: (value) => theme.setMessageMenuBlur(value),
|
|
||||||
displayValue: theme.messageMenuBlur.toStringAsFixed(1),
|
|
||||||
),
|
|
||||||
const Divider(height: 24),
|
|
||||||
if (MediaQuery.of(context).size.height < 600)
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
_SliderTile(
|
|
||||||
icon: Icons.opacity,
|
|
||||||
label: "Непрозрачность сообщений",
|
|
||||||
value: 1.0 - theme.messageBubbleOpacity,
|
|
||||||
min: 0.0,
|
|
||||||
max: 1.0,
|
|
||||||
divisions: 20,
|
|
||||||
onChanged: (value) =>
|
|
||||||
theme.setMessageBubbleOpacity(1.0 - value),
|
|
||||||
displayValue:
|
|
||||||
"${((1.0 - theme.messageBubbleOpacity) * 100).round()}%",
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_CustomSettingTile(
|
|
||||||
icon: Icons.format_color_fill,
|
// Прозрачность (сворачиваемый, по умолчанию свернут)
|
||||||
title: "Тип отображения",
|
_ExpandableSection(
|
||||||
child: IgnorePointer(
|
title: "Прозрачность",
|
||||||
ignoring: isSystemTheme,
|
initiallyExpanded: false,
|
||||||
child: Opacity(
|
children: [
|
||||||
opacity: isSystemTheme ? 0.5 : 1.0,
|
_SliderTile(
|
||||||
child: DropdownButton<MessageBubbleType>(
|
icon: Icons.text_fields,
|
||||||
value: theme.messageBubbleType,
|
label: "Непрозрачность текста",
|
||||||
underline: const SizedBox.shrink(),
|
value: theme.messageTextOpacity,
|
||||||
onChanged: (value) {
|
min: 0.1,
|
||||||
if (value != null) theme.setMessageBubbleType(value);
|
max: 1.0,
|
||||||
},
|
divisions: 18,
|
||||||
items: MessageBubbleType.values.map((type) {
|
onChanged: (value) => theme.setMessageTextOpacity(value),
|
||||||
return DropdownMenuItem(
|
displayValue: "${(theme.messageTextOpacity * 100).round()}%",
|
||||||
value: type,
|
|
||||||
child: Text(type.displayName),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
_SliderTile(
|
||||||
|
icon: Icons.blur_circular,
|
||||||
|
label: "Интенсивность тени",
|
||||||
|
value: theme.messageShadowIntensity,
|
||||||
|
min: 0.0,
|
||||||
|
max: 0.5,
|
||||||
|
divisions: 10,
|
||||||
|
onChanged: (value) => theme.setMessageShadowIntensity(value),
|
||||||
|
displayValue:
|
||||||
|
"${(theme.messageShadowIntensity * 100).round()}%",
|
||||||
|
),
|
||||||
|
_SliderTile(
|
||||||
|
icon: Icons.menu,
|
||||||
|
label: "Непрозрачность меню",
|
||||||
|
value: theme.messageMenuOpacity,
|
||||||
|
min: 0.1,
|
||||||
|
max: 1.0,
|
||||||
|
divisions: 18,
|
||||||
|
onChanged: (value) => theme.setMessageMenuOpacity(value),
|
||||||
|
displayValue: "${(theme.messageMenuOpacity * 100).round()}%",
|
||||||
|
),
|
||||||
|
_SliderTile(
|
||||||
|
icon: Icons.blur_on,
|
||||||
|
label: "Размытие меню",
|
||||||
|
value: theme.messageMenuBlur,
|
||||||
|
min: 0.0,
|
||||||
|
max: 20.0,
|
||||||
|
divisions: 20,
|
||||||
|
onChanged: (value) => theme.setMessageMenuBlur(value),
|
||||||
|
displayValue: theme.messageMenuBlur.toStringAsFixed(1),
|
||||||
|
),
|
||||||
|
_SliderTile(
|
||||||
|
icon: Icons.opacity,
|
||||||
|
label: "Непрозрачность сообщений",
|
||||||
|
value: 1.0 - theme.messageBubbleOpacity,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
divisions: 20,
|
||||||
|
onChanged: (value) =>
|
||||||
|
theme.setMessageBubbleOpacity(1.0 - value),
|
||||||
|
displayValue:
|
||||||
|
"${((1.0 - theme.messageBubbleOpacity) * 100).round()}%",
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 8),
|
||||||
_CustomSettingTile(
|
|
||||||
icon: Icons.palette,
|
// Вид (сворачиваемый)
|
||||||
title: "Цвет моих сообщений",
|
_ExpandableSection(
|
||||||
child: IgnorePointer(
|
title: "Вид",
|
||||||
ignoring: isSystemTheme,
|
initiallyExpanded: false,
|
||||||
child: Opacity(
|
children: [
|
||||||
opacity: isSystemTheme ? 0.5 : 1.0,
|
_SliderTile(
|
||||||
child: GestureDetector(
|
icon: Icons.rounded_corner,
|
||||||
onTap: () async {
|
label: "Скругление углов",
|
||||||
final initial = myBubbleColorToShow ?? myBubbleFallback;
|
value: theme.messageBorderRadius,
|
||||||
_showColorPicker(
|
min: 4.0,
|
||||||
context,
|
max: 50.0,
|
||||||
initialColor: initial,
|
divisions: 23,
|
||||||
onColorChanged: (color) => myBubbleSetter(color),
|
onChanged: (value) => theme.setMessageBorderRadius(value),
|
||||||
);
|
displayValue: "${theme.messageBorderRadius.round()}px",
|
||||||
},
|
),
|
||||||
child: Container(
|
const SizedBox(height: 16),
|
||||||
width: 40,
|
_CustomSettingTile(
|
||||||
height: 40,
|
icon: Icons.format_color_fill,
|
||||||
decoration: BoxDecoration(
|
title: "Тип отображения",
|
||||||
color: myBubbleColorToShow ?? myBubbleFallback,
|
child: IgnorePointer(
|
||||||
borderRadius: BorderRadius.circular(8),
|
ignoring: isSystemTheme,
|
||||||
border: Border.all(color: Colors.grey),
|
child: Opacity(
|
||||||
|
opacity: isSystemTheme ? 0.5 : 1.0,
|
||||||
|
child: DropdownButton<MessageBubbleType>(
|
||||||
|
value: theme.messageBubbleType,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) theme.setMessageBubbleType(value);
|
||||||
|
},
|
||||||
|
items: MessageBubbleType.values.map((type) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: type,
|
||||||
|
child: Text(type.displayName),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
),
|
_CustomSettingTile(
|
||||||
const SizedBox(height: 16),
|
icon: Icons.palette,
|
||||||
_CustomSettingTile(
|
title: "Цвет моих сообщений",
|
||||||
icon: Icons.palette_outlined,
|
child: IgnorePointer(
|
||||||
title: "Цвет сообщений собеседника",
|
ignoring: isSystemTheme,
|
||||||
child: IgnorePointer(
|
child: Opacity(
|
||||||
ignoring: isSystemTheme,
|
opacity: isSystemTheme ? 0.5 : 1.0,
|
||||||
child: Opacity(
|
child: GestureDetector(
|
||||||
opacity: isSystemTheme ? 0.5 : 1.0,
|
onTap: () async {
|
||||||
child: GestureDetector(
|
final initial = myBubbleColorToShow ?? myBubbleFallback;
|
||||||
onTap: () async {
|
_showColorPicker(
|
||||||
final initial =
|
context,
|
||||||
theirBubbleColorToShow ?? theirBubbleFallback;
|
initialColor: initial,
|
||||||
_showColorPicker(
|
onColorChanged: (color) => myBubbleSetter(color),
|
||||||
context,
|
);
|
||||||
initialColor: initial,
|
},
|
||||||
onColorChanged: (color) => theirBubbleSetter(color),
|
child: Container(
|
||||||
);
|
width: 40,
|
||||||
},
|
height: 40,
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
width: 40,
|
color: myBubbleColorToShow ?? myBubbleFallback,
|
||||||
height: 40,
|
borderRadius: BorderRadius.circular(8),
|
||||||
decoration: BoxDecoration(
|
border: Border.all(color: Colors.grey),
|
||||||
color: theirBubbleColorToShow ?? theirBubbleFallback,
|
),
|
||||||
borderRadius: BorderRadius.circular(8),
|
),
|
||||||
border: Border.all(color: Colors.grey),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
|
_CustomSettingTile(
|
||||||
|
icon: Icons.palette_outlined,
|
||||||
|
title: "Цвет сообщений собеседника",
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: isSystemTheme,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: isSystemTheme ? 0.5 : 1.0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final initial =
|
||||||
|
theirBubbleColorToShow ?? theirBubbleFallback;
|
||||||
|
_showColorPicker(
|
||||||
|
context,
|
||||||
|
initialColor: initial,
|
||||||
|
onColorChanged: (color) => theirBubbleSetter(color),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theirBubbleColorToShow ?? theirBubbleFallback,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 24),
|
||||||
|
_CustomSettingTile(
|
||||||
|
icon: Icons.reply,
|
||||||
|
title: "Автоцвет панели ответа",
|
||||||
|
subtitle: "",
|
||||||
|
child: Switch(
|
||||||
|
value: theme.useAutoReplyColor,
|
||||||
|
onChanged: (value) => theme.setUseAutoReplyColor(value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!theme.useAutoReplyColor) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_ColorPickerTile(
|
||||||
|
title: "Цвет панели ответа",
|
||||||
|
subtitle: "Фиксированный цвет",
|
||||||
|
color: theme.customReplyColor ?? Colors.blue,
|
||||||
|
onColorChanged: (color) => theme.setCustomReplyColor(color),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const Divider(height: 24),
|
|
||||||
_CustomSettingTile(
|
|
||||||
icon: Icons.reply,
|
|
||||||
title: "Автоцвет панели ответа",
|
|
||||||
subtitle: "",
|
|
||||||
child: Switch(
|
|
||||||
value: theme.useAutoReplyColor,
|
|
||||||
onChanged: (value) => theme.setUseAutoReplyColor(value),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!theme.useAutoReplyColor) ...[
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_ColorPickerTile(
|
|
||||||
title: "Цвет панели ответа",
|
|
||||||
subtitle: "Фиксированный цвет",
|
|
||||||
color: theme.customReplyColor ?? Colors.blue,
|
|
||||||
onColorChanged: (color) => theme.setCustomReplyColor(color),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_ModernSection(
|
_ModernSection(
|
||||||
title: "Всплывающие окна",
|
title: "Всплывающие окна",
|
||||||
children: [
|
children: [
|
||||||
_SliderTile(
|
// Предпросмотр всплывающего окна
|
||||||
icon: Icons.opacity,
|
_DialogPreview(),
|
||||||
label: "Прозрачность фона (профиль)",
|
const SizedBox(height: 16),
|
||||||
value: theme.profileDialogOpacity,
|
|
||||||
min: 0.0,
|
// Развернуть настройки
|
||||||
max: 1.0,
|
_ExpandableSection(
|
||||||
divisions: 20,
|
title: "Настройки",
|
||||||
onChanged: (value) => theme.setProfileDialogOpacity(value),
|
initiallyExpanded: false,
|
||||||
displayValue: "${(theme.profileDialogOpacity * 100).round()}%",
|
children: [
|
||||||
),
|
_SliderTile(
|
||||||
_SliderTile(
|
icon: Icons.opacity,
|
||||||
icon: Icons.blur_on,
|
label: "Прозрачность фона (профиль)",
|
||||||
label: "Размытие фона (профиль)",
|
value: theme.profileDialogOpacity,
|
||||||
value: theme.profileDialogBlur,
|
min: 0.0,
|
||||||
min: 0.0,
|
max: 1.0,
|
||||||
max: 30.0,
|
divisions: 20,
|
||||||
divisions: 30,
|
onChanged: (value) => theme.setProfileDialogOpacity(value),
|
||||||
onChanged: (value) => theme.setProfileDialogBlur(value),
|
displayValue: "${(theme.profileDialogOpacity * 100).round()}%",
|
||||||
displayValue: theme.profileDialogBlur.toStringAsFixed(1),
|
),
|
||||||
|
_SliderTile(
|
||||||
|
icon: Icons.blur_on,
|
||||||
|
label: "Размытие фона (профиль)",
|
||||||
|
value: theme.profileDialogBlur,
|
||||||
|
min: 0.0,
|
||||||
|
max: 30.0,
|
||||||
|
divisions: 30,
|
||||||
|
onChanged: (value) => theme.setProfileDialogBlur(value),
|
||||||
|
displayValue: theme.profileDialogBlur.toStringAsFixed(1),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -493,6 +520,11 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
|
|||||||
_ModernSection(
|
_ModernSection(
|
||||||
title: "Панели чата",
|
title: "Панели чата",
|
||||||
children: [
|
children: [
|
||||||
|
// Предпросмотр панелей
|
||||||
|
_PanelsPreview(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Галочка включения эффекта стекла
|
||||||
_CustomSettingTile(
|
_CustomSettingTile(
|
||||||
icon: Icons.tune,
|
icon: Icons.tune,
|
||||||
title: "Эффект стекла для панелей",
|
title: "Эффект стекла для панелей",
|
||||||
@@ -502,46 +534,52 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
|
|||||||
onChanged: (value) => theme.setUseGlassPanels(value),
|
onChanged: (value) => theme.setUseGlassPanels(value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (theme.useGlassPanels) ...[
|
const SizedBox(height: 8),
|
||||||
const Divider(height: 24, indent: 16, endIndent: 16),
|
|
||||||
_SliderTile(
|
// Развернуть настройки
|
||||||
label: "Непрозрачность верхней панели",
|
_ExpandableSection(
|
||||||
value: theme.topBarOpacity,
|
title: "Настройки",
|
||||||
min: 0.1,
|
initiallyExpanded: false,
|
||||||
max: 1.0,
|
children: [
|
||||||
divisions: 18,
|
_SliderTile(
|
||||||
onChanged: (value) => theme.setTopBarOpacity(value),
|
label: "Непрозрачность верхней панели",
|
||||||
displayValue: "${(theme.topBarOpacity * 100).round()}%",
|
value: theme.topBarOpacity,
|
||||||
),
|
min: 0.1,
|
||||||
_SliderTile(
|
max: 1.0,
|
||||||
label: "Размытие верхней панели",
|
divisions: 18,
|
||||||
value: theme.topBarBlur,
|
onChanged: (value) => theme.setTopBarOpacity(value),
|
||||||
min: 0.0,
|
displayValue: "${(theme.topBarOpacity * 100).round()}%",
|
||||||
max: 20.0,
|
),
|
||||||
divisions: 40,
|
_SliderTile(
|
||||||
onChanged: (value) => theme.setTopBarBlur(value),
|
label: "Размытие верхней панели",
|
||||||
displayValue: theme.topBarBlur.toStringAsFixed(1),
|
value: theme.topBarBlur,
|
||||||
),
|
min: 0.0,
|
||||||
const Divider(height: 24, indent: 16, endIndent: 16),
|
max: 20.0,
|
||||||
_SliderTile(
|
divisions: 40,
|
||||||
label: "Непрозрачность нижней панели",
|
onChanged: (value) => theme.setTopBarBlur(value),
|
||||||
value: theme.bottomBarOpacity,
|
displayValue: theme.topBarBlur.toStringAsFixed(1),
|
||||||
min: 0.1,
|
),
|
||||||
max: 1.0,
|
const Divider(height: 24, indent: 16, endIndent: 16),
|
||||||
divisions: 18,
|
_SliderTile(
|
||||||
onChanged: (value) => theme.setBottomBarOpacity(value),
|
label: "Непрозрачность нижней панели",
|
||||||
displayValue: "${(theme.bottomBarOpacity * 100).round()}%",
|
value: theme.bottomBarOpacity,
|
||||||
),
|
min: 0.1,
|
||||||
_SliderTile(
|
max: 1.0,
|
||||||
label: "Размытие нижней панели",
|
divisions: 18,
|
||||||
value: theme.bottomBarBlur,
|
onChanged: (value) => theme.setBottomBarOpacity(value),
|
||||||
min: 0.0,
|
displayValue: "${(theme.bottomBarOpacity * 100).round()}%",
|
||||||
max: 20.0,
|
),
|
||||||
divisions: 40,
|
_SliderTile(
|
||||||
onChanged: (value) => theme.setBottomBarBlur(value),
|
label: "Размытие нижней панели",
|
||||||
displayValue: theme.bottomBarBlur.toStringAsFixed(1),
|
value: theme.bottomBarBlur,
|
||||||
),
|
min: 0.0,
|
||||||
],
|
max: 20.0,
|
||||||
|
divisions: 40,
|
||||||
|
onChanged: (value) => theme.setBottomBarBlur(value),
|
||||||
|
displayValue: theme.bottomBarBlur.toStringAsFixed(1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1501,3 +1539,346 @@ class _VideoWallpaperState extends State<_VideoWallpaper> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ExpandableSection extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final List<Widget> children;
|
||||||
|
final bool initiallyExpanded;
|
||||||
|
|
||||||
|
const _ExpandableSection({
|
||||||
|
required this.title,
|
||||||
|
required this.children,
|
||||||
|
this.initiallyExpanded = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ExpandableSection> createState() => _ExpandableSectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandableSectionState extends State<_ExpandableSection> {
|
||||||
|
late bool _isExpanded;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_isExpanded = widget.initiallyExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () => setState(() => _isExpanded = !_isExpanded),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 4.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Icon(
|
||||||
|
_isExpanded ? Icons.expand_less : Icons.expand_more,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_isExpanded) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
...widget.children,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageBubblesPreview extends StatelessWidget {
|
||||||
|
const _MessageBubblesPreview();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
final mockMyMessage = Message(
|
||||||
|
id: '1',
|
||||||
|
senderId: 100,
|
||||||
|
text: "Выглядит отлично! 🔥",
|
||||||
|
time: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
attaches: const [],
|
||||||
|
);
|
||||||
|
final mockTheirMessage = Message(
|
||||||
|
id: '2',
|
||||||
|
senderId: 200,
|
||||||
|
text: "Привет! Как тебе новый вид?",
|
||||||
|
time: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
attaches: const [],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceVariant.withOpacity(0.3),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ChatMessageBubble(
|
||||||
|
message: mockTheirMessage,
|
||||||
|
isMe: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Expanded(
|
||||||
|
child: ChatMessageBubble(
|
||||||
|
message: mockMyMessage,
|
||||||
|
isMe: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DialogPreview extends StatelessWidget {
|
||||||
|
const _DialogPreview();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<ThemeProvider>();
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 120,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceVariant.withOpacity(0.3),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Фон с размытием
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
colors.primary.withOpacity(0.1),
|
||||||
|
colors.secondary.withOpacity(0.1),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Размытие фона
|
||||||
|
if (theme.profileDialogBlur > 0)
|
||||||
|
BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: theme.profileDialogBlur,
|
||||||
|
sigmaY: theme.profileDialogBlur,
|
||||||
|
),
|
||||||
|
child: Container(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
// Всплывающее окно
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surface.withOpacity(theme.profileDialogOpacity),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: colors.outline.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.person,
|
||||||
|
color: colors.onSurface,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PanelsPreview extends StatelessWidget {
|
||||||
|
const _PanelsPreview();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<ThemeProvider>();
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceVariant.withOpacity(0.3),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Фон - градиент от беловатого к серому для лучшей видимости эффекта стекла
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Colors.grey.shade300, // Беловатый сверху
|
||||||
|
Colors.grey.shade600, // Серый снизу
|
||||||
|
],
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// Верхняя панель
|
||||||
|
if (theme.useGlassPanels)
|
||||||
|
ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: theme.topBarBlur,
|
||||||
|
sigmaY: theme.topBarBlur,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
height: 30,
|
||||||
|
color: colors.surface.withOpacity(theme.topBarOpacity),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: colors.primaryContainer,
|
||||||
|
radius: 8,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
height: 30,
|
||||||
|
color: colors.surface,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: colors.primaryContainer,
|
||||||
|
radius: 8,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
// Нижняя панель
|
||||||
|
if (theme.useGlassPanels)
|
||||||
|
ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: theme.bottomBarBlur,
|
||||||
|
sigmaY: theme.bottomBarBlur,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
height: 30,
|
||||||
|
color: colors.surface.withOpacity(theme.bottomBarOpacity),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceVariant,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Icon(Icons.send, color: colors.primary, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(
|
||||||
|
height: 30,
|
||||||
|
color: colors.surface,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colors.surfaceVariant,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Icon(Icons.send, color: colors.primary, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -786,17 +786,8 @@ class ThemeProvider with ChangeNotifier {
|
|||||||
myLightLight,
|
myLightLight,
|
||||||
).toColor();
|
).toColor();
|
||||||
|
|
||||||
final double theirLightSat = (hslLight.saturation * 0.2).clamp(0.05, 0.25);
|
// Для светлой темы используем RGB(70, 70, 70) по умолчанию
|
||||||
final double theirLightLight = (hslLight.lightness * 0.1 + 0.85).clamp(
|
final Color theirColorLight = const Color(0xFF464646); // RGB(70, 70, 70)
|
||||||
0.85,
|
|
||||||
0.98,
|
|
||||||
);
|
|
||||||
final Color theirColorLight = HSLColor.fromAHSL(
|
|
||||||
hslLight.alpha,
|
|
||||||
hslLight.hue,
|
|
||||||
theirLightSat,
|
|
||||||
theirLightLight,
|
|
||||||
).toColor();
|
|
||||||
|
|
||||||
if (_myBubbleColorLight == myColorLight &&
|
if (_myBubbleColorLight == myColorLight &&
|
||||||
_theirBubbleColorLight == theirColorLight &&
|
_theirBubbleColorLight == theirColorLight &&
|
||||||
|
|||||||
@@ -1206,7 +1206,7 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
!message.isReply &&
|
!message.isReply &&
|
||||||
!message.isForwarded;
|
!message.isForwarded;
|
||||||
|
|
||||||
final bubbleColor = _getBubbleColor(isMe, themeProvider, messageOpacity);
|
final bubbleColor = _getBubbleColor(isMe, themeProvider, messageOpacity, context);
|
||||||
final textColor = _getTextColor(
|
final textColor = _getTextColor(
|
||||||
isMe,
|
isMe,
|
||||||
bubbleColor,
|
bubbleColor,
|
||||||
@@ -1773,7 +1773,7 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
final themeProvider = Provider.of<ThemeProvider>(context);
|
final themeProvider = Provider.of<ThemeProvider>(context);
|
||||||
final isUltraOptimized = themeProvider.ultraOptimizeChats;
|
final isUltraOptimized = themeProvider.ultraOptimizeChats;
|
||||||
final messageOpacity = themeProvider.messageBubbleOpacity;
|
final messageOpacity = themeProvider.messageBubbleOpacity;
|
||||||
final bubbleColor = _getBubbleColor(isMe, themeProvider, messageOpacity);
|
final bubbleColor = _getBubbleColor(isMe, themeProvider, messageOpacity, context);
|
||||||
final textColor = _getTextColor(
|
final textColor = _getTextColor(
|
||||||
isMe,
|
isMe,
|
||||||
bubbleColor,
|
bubbleColor,
|
||||||
@@ -3889,10 +3889,12 @@ class ChatMessageBubble extends StatelessWidget {
|
|||||||
bool isMe,
|
bool isMe,
|
||||||
ThemeProvider themeProvider,
|
ThemeProvider themeProvider,
|
||||||
double messageOpacity,
|
double messageOpacity,
|
||||||
|
BuildContext context,
|
||||||
) {
|
) {
|
||||||
|
final bool isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final baseColor = isMe
|
final baseColor = isMe
|
||||||
? (themeProvider.myBubbleColor ?? const Color(0xFF2b5278))
|
? (themeProvider.myBubbleColor ?? const Color(0xFF2b5278))
|
||||||
: (themeProvider.theirBubbleColor ?? const Color(0xFF182533));
|
: (themeProvider.theirBubbleColor ?? (isDark ? const Color(0xFF182533) : const Color(0xFF464646)));
|
||||||
return baseColor.withOpacity(1.0 - messageOpacity);
|
return baseColor.withOpacity(1.0 - messageOpacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user