переделал раздел настроек визуала

This commit is contained in:
jganenok
2025-12-04 09:04:50 +07:00
parent 6463a3b016
commit 8210e6c376
4 changed files with 614 additions and 282 deletions

View File

@@ -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(),
),
);
},
),
], ],
), ),
), ),

View File

@@ -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),
],
),
),
],
),
],
),
),
);
}
}

View File

@@ -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 &&

View File

@@ -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);
} }