Revert "Reapply "починил выход, наконец-то. Сделал недо жесты, добавил эксперементалбные функции замены цвета у боковой панели и списка чатов, добавил настройку отступа сообщений на андроедах а то может у кого то ломаться""

This reverts commit ceeab44b7b.
This commit is contained in:
jganenok
2025-12-05 16:21:15 +07:00
parent ceeab44b7b
commit 171ce42a72
4 changed files with 568 additions and 1313 deletions

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -2973,530 +2972,499 @@ class _ChatScreenState extends State<ChatScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<ThemeProvider>(); final theme = context.watch<ThemeProvider>();
final isDesktop = return Scaffold(
kIsWeb || extendBodyBehindAppBar: theme.useGlassPanels,
defaultTargetPlatform == TargetPlatform.windows || resizeToAvoidBottomInset: false,
defaultTargetPlatform == TargetPlatform.linux || appBar: _buildAppBar(),
defaultTargetPlatform == TargetPlatform.macOS; body: Stack(
children: [
final body = Stack( Positioned.fill(child: _buildChatWallpaper(theme)),
children: [ Column(
Positioned.fill(child: _buildChatWallpaper(theme)), children: [
Column( AnimatedSwitcher(
children: [ duration: const Duration(milliseconds: 250),
AnimatedSwitcher( switchInCurve: Curves.easeInOutCubic,
duration: const Duration(milliseconds: 250), switchOutCurve: Curves.easeInOutCubic,
switchInCurve: Curves.easeInOutCubic, transitionBuilder: (child, animation) {
switchOutCurve: Curves.easeInOutCubic, return SlideTransition(
transitionBuilder: (child, animation) { position: Tween<Offset>(
return SlideTransition( begin: const Offset(0, -0.5),
position: Tween<Offset>( end: Offset.zero,
begin: const Offset(0, -0.5), ).animate(animation),
end: Offset.zero, child: FadeTransition(opacity: animation, child: child),
).animate(animation), );
child: FadeTransition(opacity: animation, child: child), },
); child: _pinnedMessage != null
}, ? SafeArea(
child: _pinnedMessage != null key: ValueKey(_pinnedMessage!.id),
? SafeArea( child: InkWell(
key: ValueKey(_pinnedMessage!.id),
child: InkWell(
onTap: _scrollToPinnedMessage,
child: PinnedMessageWidget(
pinnedMessage: _pinnedMessage!,
contacts: _contactDetailsCache,
myId: _actualMyId ?? 0,
onTap: _scrollToPinnedMessage, onTap: _scrollToPinnedMessage,
onClose: () { child: PinnedMessageWidget(
setState(() { pinnedMessage: _pinnedMessage!,
_pinnedMessage = null; contacts: _contactDetailsCache,
}); myId: _actualMyId ?? 0,
}, onTap: _scrollToPinnedMessage,
), onClose: () {
), setState(() {
) _pinnedMessage = null;
: const SizedBox.shrink(key: ValueKey('empty')), });
), },
Expanded(
child: Stack(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
switchInCurve: Curves.easeInOutCubic,
switchOutCurve: Curves.easeInOutCubic,
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
),
), ),
child: child,
), ),
); )
}, : const SizedBox.shrink(key: ValueKey('empty')),
child: (!_isIdReady || _isLoadingHistory) ),
? const Center( Expanded(
key: ValueKey('loading'), child: Stack(
child: CircularProgressIndicator(), children: [
) AnimatedSwitcher(
: _messages.isEmpty && !widget.isChannel duration: const Duration(milliseconds: 300),
? _EmptyChatWidget( switchInCurve: Curves.easeInOutCubic,
sticker: _emptyChatSticker, switchOutCurve: Curves.easeInOutCubic,
onStickerTap: _sendEmptyChatSticker, transitionBuilder: (child, animation) {
) return FadeTransition(
: AnimatedPadding( opacity: animation,
key: const ValueKey('chat_list'), child: ScaleTransition(
duration: const Duration(milliseconds: 300), scale: Tween<double>(begin: 0.8, end: 1.0).animate(
curve: Curves.easeInOutCubic, CurvedAnimation(
padding: EdgeInsets.only( parent: animation,
bottom: () { curve: Curves.easeOutCubic,
final baseInset = MediaQuery.of(
context,
).viewInsets.bottom;
final isAndroid =
defaultTargetPlatform ==
TargetPlatform.android;
if (!isAndroid) {
return baseInset;
}
final keyboardVisible = baseInset > 0.0;
if (keyboardVisible &&
theme
.ignoreMobileBottomPaddingWhenKeyboard) {
return baseInset;
}
return baseInset +
theme.mobileChatBottomPadding;
}(),
),
child: ScrollablePositionedList.builder(
itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener,
reverse: true,
padding: EdgeInsets.fromLTRB(
8.0,
8.0,
8.0,
widget.isChannel
? 24.0
: (isDesktop ? 100.0 : 0.0),
), ),
itemCount: _chatItems.length, ),
itemBuilder: (context, index) { child: child,
final mappedIndex = ),
_chatItems.length - 1 - index; );
final item = _chatItems[mappedIndex]; },
final isLastVisual = child: (!_isIdReady || _isLoadingHistory)
index == _chatItems.length - 1; ? const Center(
key: ValueKey('loading'),
child: CircularProgressIndicator(),
)
: _messages.isEmpty && !widget.isChannel
? _EmptyChatWidget(
sticker: _emptyChatSticker,
onStickerTap: _sendEmptyChatSticker,
)
: AnimatedPadding(
key: const ValueKey('chat_list'),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubic,
padding: EdgeInsets.only(
bottom: MediaQuery.of(
context,
).viewInsets.bottom,
),
child: ScrollablePositionedList.builder(
itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener,
reverse: true,
padding: EdgeInsets.fromLTRB(
8.0,
8.0,
8.0,
widget.isChannel ? 16.0 : 100.0,
),
itemCount: _chatItems.length,
itemBuilder: (context, index) {
final mappedIndex =
_chatItems.length - 1 - index;
final item = _chatItems[mappedIndex];
final isLastVisual =
index == _chatItems.length - 1;
// Убрали вызов _loadMore() отсюда - он вызывается из _itemPositionsListener // Убрали вызов _loadMore() отсюда - он вызывается из _itemPositionsListener
// чтобы избежать setState() во время build фазы // чтобы избежать setState() во время build фазы
if (item is MessageItem) { if (item is MessageItem) {
final message = item.message; final message = item.message;
final key = _messageKeys.putIfAbsent( final key = _messageKeys.putIfAbsent(
message.id, message.id,
() => GlobalKey(), () => GlobalKey(),
);
final bool isHighlighted =
_isSearching &&
_searchResults.isNotEmpty &&
_currentResultIndex != -1 &&
message.id ==
_searchResults[_currentResultIndex]
.id;
final isControlMessage = message.attaches.any(
(a) => a['_type'] == 'CONTROL',
);
if (isControlMessage) {
return _ControlMessageChip(
message: message,
contacts: _contactDetailsCache,
myId: _actualMyId ?? widget.myId,
); );
} final bool isHighlighted =
_isSearching &&
_searchResults.isNotEmpty &&
_currentResultIndex != -1 &&
message.id ==
_searchResults[_currentResultIndex]
.id;
final bool isMe = final isControlMessage = message.attaches
item.message.senderId == _actualMyId; .any((a) => a['_type'] == 'CONTROL');
if (isControlMessage) {
MessageReadStatus? readStatus; return _ControlMessageChip(
if (isMe) { message: message,
final messageId = item.message.id; contacts: _contactDetailsCache,
if (messageId.startsWith('local_')) { myId: _actualMyId ?? widget.myId,
readStatus = MessageReadStatus.sending; );
} else {
readStatus = MessageReadStatus.sent;
} }
}
String? forwardedFrom; final bool isMe =
String? forwardedFromAvatarUrl; item.message.senderId == _actualMyId;
if (message.isForwarded) {
final link = message.link;
if (link is Map<String, dynamic>) {
final chatName =
link['chatName'] as String?;
final chatIconUrl =
link['chatIconUrl'] as String?;
if (chatName != null) { MessageReadStatus? readStatus;
forwardedFrom = chatName; if (isMe) {
forwardedFromAvatarUrl = chatIconUrl; final messageId = item.message.id;
if (messageId.startsWith('local_')) {
readStatus = MessageReadStatus.sending;
} else { } else {
final forwardedMessage = readStatus = MessageReadStatus.sent;
link['message'] }
as Map<String, dynamic>?; }
final originalSenderId =
forwardedMessage?['sender'] as int?; String? forwardedFrom;
if (originalSenderId != null) { String? forwardedFromAvatarUrl;
final originalSenderContact = if (message.isForwarded) {
_contactDetailsCache[originalSenderId]; final link = message.link;
if (originalSenderContact == null) { if (link is Map<String, dynamic>) {
_loadContactIfNeeded( final chatName =
originalSenderId, link['chatName'] as String?;
); final chatIconUrl =
forwardedFrom = link['chatIconUrl'] as String?;
'Участник $originalSenderId';
forwardedFromAvatarUrl = null; if (chatName != null) {
} else { forwardedFrom = chatName;
forwardedFrom = forwardedFromAvatarUrl = chatIconUrl;
originalSenderContact.name; } else {
forwardedFromAvatarUrl = final forwardedMessage =
originalSenderContact link['message']
.photoBaseUrl; as Map<String, dynamic>?;
final originalSenderId =
forwardedMessage?['sender']
as int?;
if (originalSenderId != null) {
final originalSenderContact =
_contactDetailsCache[originalSenderId];
if (originalSenderContact == null) {
_loadContactIfNeeded(
originalSenderId,
);
forwardedFrom =
'Участник $originalSenderId';
forwardedFromAvatarUrl = null;
} else {
forwardedFrom =
originalSenderContact.name;
forwardedFromAvatarUrl =
originalSenderContact
.photoBaseUrl;
}
} }
} }
} }
} }
} String? senderName;
String? senderName; if (widget.isGroupChat && !isMe) {
if (widget.isGroupChat && !isMe) { bool shouldShowName = true;
bool shouldShowName = true; if (mappedIndex > 0) {
if (mappedIndex > 0) { final previousItem =
final previousItem = _chatItems[mappedIndex - 1];
_chatItems[mappedIndex - 1]; if (previousItem is MessageItem) {
if (previousItem is MessageItem) { final previousMessage =
final previousMessage = previousItem.message;
previousItem.message; if (previousMessage.senderId ==
if (previousMessage.senderId == message.senderId) {
message.senderId) { final timeDifferenceInMinutes =
final timeDifferenceInMinutes = (message.time -
(message.time - previousMessage.time) /
previousMessage.time) / (1000 * 60);
(1000 * 60); if (timeDifferenceInMinutes < 5) {
if (timeDifferenceInMinutes < 5) { shouldShowName = false;
shouldShowName = false; }
} }
} }
} }
} if (shouldShowName) {
if (shouldShowName) { final senderContact =
final senderContact = _contactDetailsCache[message
_contactDetailsCache[message .senderId];
.senderId]; if (senderContact != null) {
if (senderContact != null) { senderName = getContactDisplayName(
senderName = getContactDisplayName( contactId: senderContact.id,
contactId: senderContact.id, originalName: senderContact.name,
originalName: senderContact.name, originalFirstName:
originalFirstName: senderContact.firstName,
senderContact.firstName, originalLastName:
originalLastName: senderContact.lastName,
senderContact.lastName, );
); } else {
} else { senderName = 'ID ${message.senderId}';
senderName = 'ID ${message.senderId}'; _loadContactIfNeeded(
_loadContactIfNeeded(message.senderId); message.senderId,
);
}
} }
} }
} final hasPhoto = item.message.attaches.any(
final hasPhoto = item.message.attaches.any( (a) => a['_type'] == 'PHOTO',
(a) => a['_type'] == 'PHOTO', );
); final isNew = !_animatedMessageIds.contains(
final isNew = !_animatedMessageIds.contains( item.message.id,
item.message.id, );
); final deferImageLoading =
final deferImageLoading = hasPhoto &&
hasPhoto && isNew &&
isNew && !_anyOptimize &&
!_anyOptimize && !context
!context .read<ThemeProvider>()
.read<ThemeProvider>() .animatePhotoMessages;
.animatePhotoMessages;
String? decryptedText; String? decryptedText;
if (_isEncryptionPasswordSetForCurrentChat && if (_isEncryptionPasswordSetForCurrentChat &&
_encryptionConfigForCurrentChat != null && _encryptionConfigForCurrentChat !=
_encryptionConfigForCurrentChat! null &&
.password _encryptionConfigForCurrentChat!
.isNotEmpty && .password
item.message.text.startsWith( .isNotEmpty &&
ChatEncryptionService.encryptedPrefix, item.message.text.startsWith(
)) { ChatEncryptionService.encryptedPrefix,
decryptedText = )) {
ChatEncryptionService.decryptWithPassword( decryptedText =
_encryptionConfigForCurrentChat! ChatEncryptionService.decryptWithPassword(
.password, _encryptionConfigForCurrentChat!
item.message.text, .password,
item.message.text,
);
}
final bubble = ChatMessageBubble(
key: key,
message: item.message,
isMe: isMe,
readStatus: readStatus,
isReactionSending: _sendingReactions
.contains(item.message.id),
deferImageLoading: deferImageLoading,
myUserId: _actualMyId,
chatId: widget.chatId,
isEncryptionPasswordSet:
_isEncryptionPasswordSetForCurrentChat,
decryptedText: decryptedText,
onReply: widget.isChannel
? null
: () => _replyToMessage(item.message),
onForward: () =>
_forwardMessage(item.message),
onEdit: isMe
? () => _editMessage(item.message)
: null,
canEditMessage: isMe
? item.message.canEdit(_actualMyId!)
: null,
onDeleteForMe: isMe
? () async {
await ApiService.instance
.deleteMessage(
widget.chatId,
item.message.id,
forMe: true,
);
widget.onChatUpdated?.call();
}
: null,
onDeleteForAll: isMe
? () async {
await ApiService.instance
.deleteMessage(
widget.chatId,
item.message.id,
forMe: false,
);
widget.onChatUpdated?.call();
}
: null,
onReaction: (emoji) {
_updateReactionOptimistically(
item.message.id,
emoji,
); );
} ApiService.instance.sendReaction(
widget.chatId,
final bubble = ChatMessageBubble( item.message.id,
key: key, emoji,
message: item.message,
isMe: isMe,
readStatus: readStatus,
isReactionSending: _sendingReactions
.contains(item.message.id),
deferImageLoading: deferImageLoading,
myUserId: _actualMyId,
chatId: widget.chatId,
isEncryptionPasswordSet:
_isEncryptionPasswordSetForCurrentChat,
decryptedText: decryptedText,
onReply: widget.isChannel
? null
: () => _replyToMessage(item.message),
onForward: () =>
_forwardMessage(item.message),
onEdit: isMe
? () => _editMessage(item.message)
: null,
canEditMessage: isMe
? item.message.canEdit(_actualMyId!)
: null,
onDeleteForMe: isMe
? () async {
await ApiService.instance
.deleteMessage(
widget.chatId,
item.message.id,
forMe: true,
);
widget.onChatUpdated?.call();
}
: null,
onDeleteForAll: isMe
? () async {
await ApiService.instance
.deleteMessage(
widget.chatId,
item.message.id,
forMe: false,
);
widget.onChatUpdated?.call();
}
: null,
onReaction: (emoji) {
_updateReactionOptimistically(
item.message.id,
emoji,
);
ApiService.instance.sendReaction(
widget.chatId,
item.message.id,
emoji,
);
widget.onChatUpdated?.call();
},
onRemoveReaction: () {
_removeReactionOptimistically(
item.message.id,
);
ApiService.instance.removeReaction(
widget.chatId,
item.message.id,
);
widget.onChatUpdated?.call();
},
isGroupChat: widget.isGroupChat,
isChannel: widget.isChannel,
senderName: senderName,
forwardedFrom: forwardedFrom,
forwardedFromAvatarUrl:
forwardedFromAvatarUrl,
contactDetailsCache: _contactDetailsCache,
onReplyTap: _scrollToMessage,
useAutoReplyColor: context
.read<ThemeProvider>()
.useAutoReplyColor,
customReplyColor: context
.read<ThemeProvider>()
.customReplyColor,
isFirstInGroup: item.isFirstInGroup,
isLastInGroup: item.isLastInGroup,
isGrouped: item.isGrouped,
avatarVerticalOffset:
-8.0, // Смещение аватарки вверх на 8px
onComplain: () =>
_showComplaintDialog(item.message.id),
);
Widget finalMessageWidget = bubble as Widget;
if (isHighlighted) {
return TweenAnimationBuilder<double>(
duration: const Duration(
milliseconds: 600,
),
tween: Tween<double>(
begin: 0.3,
end: 0.6,
),
curve: Curves.easeInOut,
builder: (context, value, child) {
return Container(
margin: const EdgeInsets.symmetric(
vertical: 2,
),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(value),
borderRadius: BorderRadius.circular(
16,
),
border: Border.all(
color: Theme.of(
context,
).colorScheme.primary,
width: 1.5,
),
),
child: child,
); );
widget.onChatUpdated?.call();
}, },
child: finalMessageWidget, onRemoveReaction: () {
_removeReactionOptimistically(
item.message.id,
);
ApiService.instance.removeReaction(
widget.chatId,
item.message.id,
);
widget.onChatUpdated?.call();
},
isGroupChat: widget.isGroupChat,
isChannel: widget.isChannel,
senderName: senderName,
forwardedFrom: forwardedFrom,
forwardedFromAvatarUrl:
forwardedFromAvatarUrl,
contactDetailsCache: _contactDetailsCache,
onReplyTap: _scrollToMessage,
useAutoReplyColor: context
.read<ThemeProvider>()
.useAutoReplyColor,
customReplyColor: context
.read<ThemeProvider>()
.customReplyColor,
isFirstInGroup: item.isFirstInGroup,
isLastInGroup: item.isLastInGroup,
isGrouped: item.isGrouped,
avatarVerticalOffset:
-8.0, // Смещение аватарки вверх на 8px
onComplain: () =>
_showComplaintDialog(item.message.id),
); );
}
// Плавное появление новых сообщений Widget finalMessageWidget =
if (isNew && !_anyOptimize) { bubble as Widget;
if (isHighlighted) {
return TweenAnimationBuilder<double>(
duration: const Duration(
milliseconds: 600,
),
tween: Tween<double>(
begin: 0.3,
end: 0.6,
),
curve: Curves.easeInOut,
builder: (context, value, child) {
return Container(
margin: const EdgeInsets.symmetric(
vertical: 2,
),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(value),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: Theme.of(
context,
).colorScheme.primary,
width: 1.5,
),
),
child: child,
);
},
child: finalMessageWidget,
);
}
// Плавное появление новых сообщений
if (isNew && !_anyOptimize) {
return TweenAnimationBuilder<double>(
duration: const Duration(
milliseconds: 400,
),
tween: Tween<double>(
begin: 0.0,
end: 1.0,
),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(
0,
20 * (1 - value),
),
child: child,
),
);
},
child: finalMessageWidget,
);
}
return finalMessageWidget;
} else if (item is DateSeparatorItem) {
return _DateSeparatorChip(date: item.date);
}
if (isLastVisual && _isLoadingMore) {
return TweenAnimationBuilder<double>( return TweenAnimationBuilder<double>(
duration: const Duration( duration: const Duration(
milliseconds: 400, milliseconds: 300,
), ),
tween: Tween<double>( tween: Tween<double>(
begin: 0.0, begin: 0.0,
end: 1.0, end: 1.0,
), ),
curve: Curves.easeOutCubic, curve: Curves.easeOut,
builder: (context, value, child) { builder: (context, value, child) {
return Opacity( return Opacity(
opacity: value, opacity: value,
child: Transform.translate( child: Transform.scale(
offset: Offset(0, 20 * (1 - value)), scale: 0.7 + (0.3 * value),
child: child, child: child,
), ),
); );
}, },
child: finalMessageWidget, child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
),
child: Center(
child: CircularProgressIndicator(),
),
),
); );
} }
return const SizedBox.shrink();
return finalMessageWidget; },
} else if (item is DateSeparatorItem) { ),
return _DateSeparatorChip(date: item.date);
}
if (isLastVisual && _isLoadingMore) {
return TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 300),
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(
scale: 0.7 + (0.3 * value),
child: child,
),
);
},
child: const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
}
return const SizedBox.shrink();
},
), ),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 100),
curve: Curves.easeOutQuad,
right: 16,
bottom:
MediaQuery.of(context).viewInsets.bottom +
MediaQuery.of(context).padding.bottom +
100,
child: AnimatedScale(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutBack,
scale: _showScrollToBottomNotifier.value ? 1.0 : 0.0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 150),
opacity: _showScrollToBottomNotifier.value
? 1.0
: 0.0,
child: FloatingActionButton(
mini: true,
onPressed: _scrollToBottom,
elevation: 4,
child: const Icon(Icons.arrow_downward_rounded),
), ),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 100),
curve: Curves.easeOutQuad,
right: 16,
bottom:
MediaQuery.of(context).viewInsets.bottom +
MediaQuery.of(context).padding.bottom +
100,
child: AnimatedScale(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutBack,
scale: _showScrollToBottomNotifier.value ? 1.0 : 0.0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 150),
opacity: _showScrollToBottomNotifier.value ? 1.0 : 0.0,
child: FloatingActionButton(
mini: true,
onPressed: _scrollToBottom,
elevation: 4,
child: const Icon(Icons.arrow_downward_rounded),
), ),
), ),
), ),
), ],
], ),
), ),
), ],
], ),
), AnimatedPositioned(
AnimatedPositioned( duration: const Duration(milliseconds: 100),
duration: const Duration(milliseconds: 100), curve: Curves.easeOutQuad,
curve: Curves.easeOutQuad, left: 8,
left: 8, right: 8,
right: 8, bottom:
bottom: MediaQuery.of(context).viewInsets.bottom +
MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom +
MediaQuery.of(context).padding.bottom + 12,
12, child: _buildTextInput(),
child: _buildTextInput(), ),
), ],
],
);
if (isDesktop) {
return Scaffold(
extendBodyBehindAppBar: theme.useGlassPanels,
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(),
body: body,
);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragEnd: (details) {
final velocity = details.primaryVelocity ?? 0;
if (velocity > 400) {
Navigator.of(context).maybePop();
}
},
child: Scaffold(
extendBodyBehindAppBar: theme.useGlassPanels,
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(),
body: body,
), ),
); );
} }

View File

@@ -1997,63 +1997,13 @@ class _ChatsScreenState extends State<ChatsScreen>
], ],
); );
final themeProvider = context.watch<ThemeProvider>();
Widget? chatsListBackground;
if (themeProvider.useExperimentalChatsListBackground) {
switch (themeProvider.experimentalChatsListBackgroundType) {
case ChatWallpaperType.solid:
chatsListBackground = Container(color: themeProvider.experimentalChatsListBackgroundColor1);
break;
case ChatWallpaperType.gradient:
chatsListBackground = Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
themeProvider.experimentalChatsListBackgroundColor1,
themeProvider.experimentalChatsListBackgroundColor2,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
);
break;
case ChatWallpaperType.image:
if (themeProvider.experimentalChatsListBackgroundImagePath != null) {
chatsListBackground = Image.file(
File(themeProvider.experimentalChatsListBackgroundImagePath!),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
);
}
break;
case ChatWallpaperType.video:
break;
}
}
final bodyContentWithBackground = chatsListBackground != null
? Stack(
children: [
Positioned.fill(child: chatsListBackground),
bodyContent,
],
)
: bodyContent;
if (widget.hasScaffold) { if (widget.hasScaffold) {
return Builder( return Builder(
builder: (context) { builder: (context) {
final platform = Theme.of(context).platform; return Scaffold(
final isMobile =
platform == TargetPlatform.android ||
platform == TargetPlatform.iOS;
Widget scaffold = Scaffold(
appBar: _buildAppBar(context), appBar: _buildAppBar(context),
drawer: _buildAppDrawer(context), drawer: _buildAppDrawer(context),
body: Row(children: [Expanded(child: bodyContentWithBackground)]), body: Row(children: [Expanded(child: bodyContent)]),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: () {
_showAddMenu(context); _showAddMenu(context);
@@ -2063,50 +2013,10 @@ class _ChatsScreenState extends State<ChatsScreen>
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
), ),
); );
if (!isMobile) return scaffold;
final scaffoldKey = GlobalKey<ScaffoldState>();
scaffold = Scaffold(
key: scaffoldKey,
appBar: _buildAppBar(context),
drawer: _buildAppDrawer(context),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragEnd: (details) {
final velocity = details.primaryVelocity ?? 0;
// Делаем жест проще: реагируем на более медленный свайп
if (velocity < -150) {
if (_folderTabController.length > 0 &&
_folderTabController.index ==
_folderTabController.length - 1) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => const SettingsScreen(),
),
);
} else {
scaffoldKey.currentState?.openDrawer();
}
}
},
child: Row(children: [Expanded(child: bodyContentWithBackground)]),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showAddMenu(context);
},
tooltip: 'Создать',
heroTag: 'create_menu',
child: const Icon(Icons.edit),
),
);
return scaffold;
}, },
); );
} else { } else {
return bodyContentWithBackground; return bodyContent;
} }
} }
@@ -2116,49 +2026,6 @@ class _ChatsScreenState extends State<ChatsScreen>
final themeProvider = context.watch<ThemeProvider>(); final themeProvider = context.watch<ThemeProvider>();
final isDarkMode = themeProvider.themeMode == ThemeMode.dark; final isDarkMode = themeProvider.themeMode == ThemeMode.dark;
Widget? _buildBackgroundWidget(ChatWallpaperType type, Color color1, Color color2, String? imagePath) {
switch (type) {
case ChatWallpaperType.solid:
return Container(color: color1);
case ChatWallpaperType.gradient:
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [color1, color2],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
);
case ChatWallpaperType.image:
if (imagePath != null) {
return Image.file(
File(imagePath),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
);
}
return null;
case ChatWallpaperType.video:
return null;
}
}
final drawerTopBackground = _buildBackgroundWidget(
themeProvider.drawerTopBackgroundType,
themeProvider.drawerTopBackgroundColor1,
themeProvider.drawerTopBackgroundColor2,
themeProvider.drawerTopBackgroundImagePath,
);
final drawerBottomBackground = _buildBackgroundWidget(
themeProvider.drawerBottomBackgroundType,
themeProvider.drawerBottomBackgroundColor1,
themeProvider.drawerBottomBackgroundColor2,
themeProvider.drawerBottomBackgroundImagePath,
);
return Drawer( return Drawer(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -2171,136 +2038,132 @@ class _ChatsScreenState extends State<ChatsScreen>
final currentAccount = accountManager.currentAccount; final currentAccount = accountManager.currentAccount;
final hasMultipleAccounts = accounts.length > 1; final hasMultipleAccounts = accounts.length > 1;
return Stack( return Column(
children: [ children: [
if (drawerTopBackground != null) Container(
Positioned.fill(child: drawerTopBackground), width: double.infinity,
Column( padding: EdgeInsets.only(
children: [ top: MediaQuery.of(context).padding.top + 16.0,
Container( left: 16.0,
width: double.infinity, right: 16.0,
padding: EdgeInsets.only( bottom: 16.0,
top: MediaQuery.of(context).padding.top + 16.0, ),
left: 16.0, decoration: BoxDecoration(color: colors.primaryContainer),
right: 16.0, child: Column(
bottom: 16.0, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
decoration: BoxDecoration(color: drawerTopBackground != null ? Colors.transparent : colors.primaryContainer), Row(
child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( CircleAvatar(
mainAxisAlignment: MainAxisAlignment.spaceBetween, radius: 30, // Чуть крупнее
crossAxisAlignment: CrossAxisAlignment.start, backgroundColor: colors.primary,
children: [ backgroundImage:
CircleAvatar( _isProfileLoading ||
radius: 30, // Чуть крупнее _myProfile?.photoBaseUrl == null
backgroundColor: colors.primary, ? null
backgroundImage: : NetworkImage(_myProfile!.photoBaseUrl!),
_isProfileLoading || child: _isProfileLoading
_myProfile?.photoBaseUrl == null ? const SizedBox(
? null width: 20,
: NetworkImage(_myProfile!.photoBaseUrl!), height: 20,
child: _isProfileLoading child: CircularProgressIndicator(
? const SizedBox( strokeWidth: 2,
width: 20, color: Colors.white,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: (_myProfile?.photoBaseUrl == null
? Text(
_myProfile
?.displayName
.isNotEmpty ==
true
? _myProfile!.displayName[0]
.toUpperCase()
: '?',
style: TextStyle(
color: colors.onPrimary,
fontSize: 28, // Крупнее
),
)
: null),
),
IconButton(
icon: Icon(
isDarkMode
? Icons.brightness_7
: Icons.brightness_4, // Солнце / Луна
color: colors.onPrimaryContainer,
size: 26,
),
onPressed: () {
themeProvider.toggleTheme();
},
tooltip: isDarkMode
? 'Светлая тема'
: 'Темная тема',
),
],
),
const SizedBox(height: 12),
Text(
_myProfile?.displayName ?? 'Загрузка...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colors.onPrimaryContainer,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
_myProfile?.formattedPhone ?? '',
style: TextStyle(
color: colors.onPrimaryContainer.withOpacity(
0.8,
), ),
fontSize: 14, )
), : (_myProfile?.photoBaseUrl == null
), ? Text(
), _myProfile
InkWell( ?.displayName
onTap: () { .isNotEmpty ==
setState(() { true
_isAccountsExpanded = !_isAccountsExpanded; ? _myProfile!.displayName[0]
}); .toUpperCase()
}, : '?',
child: Padding( style: TextStyle(
padding: const EdgeInsets.only(left: 8.0), color: colors.onPrimary,
child: Icon( fontSize: 28, // Крупнее
_isAccountsExpanded ),
? Icons.expand_less )
: Icons.expand_more, : null),
color: colors.onPrimaryContainer, ),
size: 24, IconButton(
), icon: Icon(
), isDarkMode
), ? Icons.brightness_7
], : Icons.brightness_4, // Солнце / Луна
color: colors.onPrimaryContainer,
size: 26,
),
onPressed: () {
themeProvider.toggleTheme();
},
tooltip: isDarkMode
? 'Светлая тема'
: 'Темная тема',
), ),
], ],
), ),
), const SizedBox(height: 12),
ClipRect( Text(
child: AnimatedSize( _myProfile?.displayName ?? 'Загрузка...',
duration: const Duration(milliseconds: 300), style: TextStyle(
curve: Curves.easeInOutCubic, fontSize: 16,
child: _isAccountsExpanded fontWeight: FontWeight.bold,
? Column( color: colors.onPrimaryContainer,
children: [ ),
if (hasMultipleAccounts) ),
...accounts.map((account) { const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
_myProfile?.formattedPhone ?? '',
style: TextStyle(
color: colors.onPrimaryContainer.withOpacity(
0.8,
),
fontSize: 14,
),
),
),
InkWell(
onTap: () {
setState(() {
_isAccountsExpanded = !_isAccountsExpanded;
});
},
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Icon(
_isAccountsExpanded
? Icons.expand_less
: Icons.expand_more,
color: colors.onPrimaryContainer,
size: 24,
),
),
),
],
),
],
),
),
ClipRect(
child: AnimatedSize(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutCubic,
child: _isAccountsExpanded
? Column(
children: [
if (hasMultipleAccounts)
...accounts.map((account) {
final isCurrent = final isCurrent =
account.id == currentAccount?.id; account.id == currentAccount?.id;
return ListTile( return ListTile(
@@ -2426,22 +2289,16 @@ class _ChatsScreenState extends State<ChatsScreen>
], ],
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
),
],
), ),
], ],
); );
}, },
), ),
Expanded( Expanded(
child: Stack( child: Column(
children: [ children: [
if (drawerBottomBackground != null) _buildAccountsSection(context, colors),
Positioned.fill(child: drawerBottomBackground),
Column(
children: [
_buildAccountsSection(context, colors),
ListTile( ListTile(
leading: const Icon(Icons.person_outline), leading: const Icon(Icons.person_outline),
title: const Text('Мой профиль'), title: const Text('Мой профиль'),
@@ -2584,8 +2441,6 @@ class _ChatsScreenState extends State<ChatsScreen>
}, },
), ),
const SizedBox(height: 8), // Небольшой отступ снизу const SizedBox(height: 8), // Небольшой отступ снизу
],
),
], ],
), ),
), ),

View File

@@ -536,37 +536,6 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_ExpandableSection(
title: "Отступ внизу чата",
initiallyExpanded: false,
children: [
_SliderTile(
icon: Icons.vertical_align_bottom,
label: "Доп. отступ снизу (мобилы)",
value: theme.mobileChatBottomPadding,
min: 60,
max: 240,
divisions: 18,
onChanged: (value) =>
theme.setMobileChatBottomPadding(value),
displayValue:
"${theme.mobileChatBottomPadding.toStringAsFixed(0)} px",
),
const SizedBox(height: 8),
_CustomSettingTile(
icon: Icons.keyboard,
title: "Убирать отступ при открытой клавиатуре",
subtitle:
"Только Android. Когда вводите текст — отступ снизу не добавляется",
child: Switch(
value: theme.ignoreMobileBottomPaddingWhenKeyboard,
onChanged: (value) => theme
.setIgnoreMobileBottomPaddingWhenKeyboard(value),
),
),
],
),
// Развернуть настройки // Развернуть настройки
_ExpandableSection( _ExpandableSection(
title: "Настройки", title: "Настройки",
@@ -613,296 +582,6 @@ class _CustomizationScreenState extends State<CustomizationScreen> {
), ),
], ],
), ),
const SizedBox(height: 24),
_ModernSection(
title: "Экспериментальные настройки фона",
children: [
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: colors.errorContainer.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: colors.error.withOpacity(0.5),
width: 1,
),
),
child: Row(
children: [
Icon(Icons.science, color: colors.error, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
"Экспериментальные функции. Могут работать нестабильно.",
style: TextStyle(
fontSize: 12,
color: colors.onErrorContainer,
),
),
),
],
),
),
_ExpandableSection(
title: "Фон списка чатов",
initiallyExpanded: false,
children: [
_CustomSettingTile(
icon: Icons.science,
title: "Использовать экспериментальный фон",
subtitle: "Фон для экрана со списком чатов",
child: Switch(
value: theme.useExperimentalChatsListBackground,
onChanged: (value) =>
theme.setUseExperimentalChatsListBackground(value),
),
),
if (theme.useExperimentalChatsListBackground) ...[
const Divider(height: 24),
_CustomSettingTile(
icon: Icons.image,
title: "Тип фона",
child: DropdownButton<ChatWallpaperType>(
value: theme.experimentalChatsListBackgroundType,
onChanged: (value) {
if (value != null) {
theme.setExperimentalChatsListBackgroundType(value);
}
},
items: [
ChatWallpaperType.solid,
ChatWallpaperType.gradient,
ChatWallpaperType.image,
].map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.displayName),
);
}).toList(),
),
),
if (theme.experimentalChatsListBackgroundType ==
ChatWallpaperType.solid ||
theme.experimentalChatsListBackgroundType ==
ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 1",
subtitle: "Основной цвет",
color: theme.experimentalChatsListBackgroundColor1,
onColorChanged: (color) =>
theme.setExperimentalChatsListBackgroundColor1(color),
),
],
if (theme.experimentalChatsListBackgroundType ==
ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 2",
subtitle: "Дополнительный цвет для градиента",
color: theme.experimentalChatsListBackgroundColor2,
onColorChanged: (color) =>
theme.setExperimentalChatsListBackgroundColor2(color),
),
],
if (theme.experimentalChatsListBackgroundType ==
ChatWallpaperType.image) ...[
const Divider(height: 24),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.photo_library_outlined),
title: const Text("Выбрать изображение"),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final picker = ImagePicker();
final image = await picker.pickImage(
source: ImageSource.gallery,
);
if (image != null) {
theme.setExperimentalChatsListBackgroundImagePath(
image.path,
);
}
},
),
if (theme.experimentalChatsListBackgroundImagePath
?.isNotEmpty ==
true) ...[
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.delete_outline),
title: const Text("Удалить изображение"),
onTap: () {
theme.setExperimentalChatsListBackgroundImagePath(null);
},
),
],
],
],
],
),
const SizedBox(height: 16),
_ExpandableSection(
title: "Фон боковой панели - верхняя часть (профиль)",
initiallyExpanded: false,
children: [
_CustomSettingTile(
icon: Icons.image,
title: "Тип фона",
child: DropdownButton<ChatWallpaperType>(
value: theme.drawerTopBackgroundType,
onChanged: (value) {
if (value != null) {
theme.setDrawerTopBackgroundType(value);
}
},
items: [
ChatWallpaperType.solid,
ChatWallpaperType.gradient,
ChatWallpaperType.image,
].map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.displayName),
);
}).toList(),
),
),
if (theme.drawerTopBackgroundType == ChatWallpaperType.solid ||
theme.drawerTopBackgroundType == ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 1",
subtitle: "Основной цвет",
color: theme.drawerTopBackgroundColor1,
onColorChanged: (color) =>
theme.setDrawerTopBackgroundColor1(color),
),
],
if (theme.drawerTopBackgroundType == ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 2",
subtitle: "Дополнительный цвет для градиента",
color: theme.drawerTopBackgroundColor2,
onColorChanged: (color) =>
theme.setDrawerTopBackgroundColor2(color),
),
],
if (theme.drawerTopBackgroundType == ChatWallpaperType.image) ...[
const Divider(height: 24),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.photo_library_outlined),
title: const Text("Выбрать изображение"),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final picker = ImagePicker();
final image = await picker.pickImage(
source: ImageSource.gallery,
);
if (image != null) {
theme.setDrawerTopBackgroundImagePath(image.path);
}
},
),
if (theme.drawerTopBackgroundImagePath?.isNotEmpty == true) ...[
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.delete_outline),
title: const Text("Удалить изображение"),
onTap: () {
theme.setDrawerTopBackgroundImagePath(null);
},
),
],
],
],
),
const SizedBox(height: 16),
_ExpandableSection(
title: "Фон боковой панели - нижняя часть (меню)",
initiallyExpanded: false,
children: [
_CustomSettingTile(
icon: Icons.image,
title: "Тип фона",
child: DropdownButton<ChatWallpaperType>(
value: theme.drawerBottomBackgroundType,
onChanged: (value) {
if (value != null) {
theme.setDrawerBottomBackgroundType(value);
}
},
items: [
ChatWallpaperType.solid,
ChatWallpaperType.gradient,
ChatWallpaperType.image,
].map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.displayName),
);
}).toList(),
),
),
if (theme.drawerBottomBackgroundType == ChatWallpaperType.solid ||
theme.drawerBottomBackgroundType == ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 1",
subtitle: "Основной цвет",
color: theme.drawerBottomBackgroundColor1,
onColorChanged: (color) =>
theme.setDrawerBottomBackgroundColor1(color),
),
],
if (theme.drawerBottomBackgroundType == ChatWallpaperType.gradient) ...[
const SizedBox(height: 16),
_ColorPickerTile(
title: "Цвет 2",
subtitle: "Дополнительный цвет для градиента",
color: theme.drawerBottomBackgroundColor2,
onColorChanged: (color) =>
theme.setDrawerBottomBackgroundColor2(color),
),
],
if (theme.drawerBottomBackgroundType == ChatWallpaperType.image) ...[
const Divider(height: 24),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.photo_library_outlined),
title: const Text("Выбрать изображение"),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final picker = ImagePicker();
final image = await picker.pickImage(
source: ImageSource.gallery,
);
if (image != null) {
theme.setDrawerBottomBackgroundImagePath(image.path);
}
},
),
if (theme.drawerBottomBackgroundImagePath?.isNotEmpty == true) ...[
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.delete_outline),
title: const Text("Удалить изображение"),
onTap: () {
theme.setDrawerBottomBackgroundImagePath(null);
},
),
],
],
],
),
],
),
], ],
), ),
); );

View File

@@ -104,25 +104,6 @@ class CustomThemePreset {
bool useDesktopLayout; bool useDesktopLayout;
bool useAutoReplyColor; bool useAutoReplyColor;
Color? customReplyColor; Color? customReplyColor;
double mobileChatBottomPadding;
bool ignoreMobileBottomPaddingWhenKeyboard;
// Экспериментальные настройки фона
bool useExperimentalChatsListBackground;
ChatWallpaperType experimentalChatsListBackgroundType;
Color experimentalChatsListBackgroundColor1;
Color experimentalChatsListBackgroundColor2;
String? experimentalChatsListBackgroundImagePath;
ChatWallpaperType drawerTopBackgroundType;
Color drawerTopBackgroundColor1;
Color drawerTopBackgroundColor2;
String? drawerTopBackgroundImagePath;
ChatWallpaperType drawerBottomBackgroundType;
Color drawerBottomBackgroundColor1;
Color drawerBottomBackgroundColor2;
String? drawerBottomBackgroundImagePath;
CustomThemePreset({ CustomThemePreset({
required this.id, required this.id,
@@ -174,21 +155,6 @@ class CustomThemePreset {
this.useDesktopLayout = true, this.useDesktopLayout = true,
this.useAutoReplyColor = true, this.useAutoReplyColor = true,
this.customReplyColor, this.customReplyColor,
this.mobileChatBottomPadding = 140.0,
this.ignoreMobileBottomPaddingWhenKeyboard = true,
this.useExperimentalChatsListBackground = false,
this.experimentalChatsListBackgroundType = ChatWallpaperType.solid,
this.experimentalChatsListBackgroundColor1 = const Color(0xFF101010),
this.experimentalChatsListBackgroundColor2 = const Color(0xFF202020),
this.experimentalChatsListBackgroundImagePath,
this.drawerTopBackgroundType = ChatWallpaperType.solid,
this.drawerTopBackgroundColor1 = const Color(0xFF1E1E1E),
this.drawerTopBackgroundColor2 = const Color(0xFF2E2E2E),
this.drawerTopBackgroundImagePath,
this.drawerBottomBackgroundType = ChatWallpaperType.solid,
this.drawerBottomBackgroundColor1 = const Color(0xFF1E1E1E),
this.drawerBottomBackgroundColor2 = const Color(0xFF2E2E2E),
this.drawerBottomBackgroundImagePath,
}); });
factory CustomThemePreset.createDefault() { factory CustomThemePreset.createDefault() {
@@ -245,21 +211,6 @@ class CustomThemePreset {
bool? useDesktopLayout, bool? useDesktopLayout,
bool? useAutoReplyColor, bool? useAutoReplyColor,
Color? customReplyColor, Color? customReplyColor,
double? mobileChatBottomPadding,
bool? ignoreMobileBottomPaddingWhenKeyboard,
bool? useExperimentalChatsListBackground,
ChatWallpaperType? experimentalChatsListBackgroundType,
Color? experimentalChatsListBackgroundColor1,
Color? experimentalChatsListBackgroundColor2,
String? experimentalChatsListBackgroundImagePath,
ChatWallpaperType? drawerTopBackgroundType,
Color? drawerTopBackgroundColor1,
Color? drawerTopBackgroundColor2,
String? drawerTopBackgroundImagePath,
ChatWallpaperType? drawerBottomBackgroundType,
Color? drawerBottomBackgroundColor1,
Color? drawerBottomBackgroundColor2,
String? drawerBottomBackgroundImagePath,
}) { }) {
return CustomThemePreset( return CustomThemePreset(
id: id ?? this.id, id: id ?? this.id,
@@ -320,35 +271,6 @@ class CustomThemePreset {
useDesktopLayout: useDesktopLayout ?? this.useDesktopLayout, useDesktopLayout: useDesktopLayout ?? this.useDesktopLayout,
useAutoReplyColor: useAutoReplyColor ?? this.useAutoReplyColor, useAutoReplyColor: useAutoReplyColor ?? this.useAutoReplyColor,
customReplyColor: customReplyColor ?? this.customReplyColor, customReplyColor: customReplyColor ?? this.customReplyColor,
mobileChatBottomPadding:
mobileChatBottomPadding ?? this.mobileChatBottomPadding,
ignoreMobileBottomPaddingWhenKeyboard:
ignoreMobileBottomPaddingWhenKeyboard ??
this.ignoreMobileBottomPaddingWhenKeyboard,
useExperimentalChatsListBackground: useExperimentalChatsListBackground ??
this.useExperimentalChatsListBackground,
experimentalChatsListBackgroundType: experimentalChatsListBackgroundType ??
this.experimentalChatsListBackgroundType,
experimentalChatsListBackgroundColor1: experimentalChatsListBackgroundColor1 ??
this.experimentalChatsListBackgroundColor1,
experimentalChatsListBackgroundColor2: experimentalChatsListBackgroundColor2 ??
this.experimentalChatsListBackgroundColor2,
experimentalChatsListBackgroundImagePath: experimentalChatsListBackgroundImagePath ??
this.experimentalChatsListBackgroundImagePath,
drawerTopBackgroundType: drawerTopBackgroundType ?? this.drawerTopBackgroundType,
drawerTopBackgroundColor1:
drawerTopBackgroundColor1 ?? this.drawerTopBackgroundColor1,
drawerTopBackgroundColor2:
drawerTopBackgroundColor2 ?? this.drawerTopBackgroundColor2,
drawerTopBackgroundImagePath:
drawerTopBackgroundImagePath ?? this.drawerTopBackgroundImagePath,
drawerBottomBackgroundType: drawerBottomBackgroundType ?? this.drawerBottomBackgroundType,
drawerBottomBackgroundColor1:
drawerBottomBackgroundColor1 ?? this.drawerBottomBackgroundColor1,
drawerBottomBackgroundColor2:
drawerBottomBackgroundColor2 ?? this.drawerBottomBackgroundColor2,
drawerBottomBackgroundImagePath:
drawerBottomBackgroundImagePath ?? this.drawerBottomBackgroundImagePath,
); );
} }
@@ -403,22 +325,6 @@ class CustomThemePreset {
'useDesktopLayout': useDesktopLayout, 'useDesktopLayout': useDesktopLayout,
'useAutoReplyColor': useAutoReplyColor, 'useAutoReplyColor': useAutoReplyColor,
'customReplyColor': customReplyColor?.value, 'customReplyColor': customReplyColor?.value,
'mobileChatBottomPadding': mobileChatBottomPadding,
'ignoreMobileBottomPaddingWhenKeyboard':
ignoreMobileBottomPaddingWhenKeyboard,
'useExperimentalChatsListBackground': useExperimentalChatsListBackground,
'experimentalChatsListBackgroundType': experimentalChatsListBackgroundType.index,
'experimentalChatsListBackgroundColor1': experimentalChatsListBackgroundColor1.value,
'experimentalChatsListBackgroundColor2': experimentalChatsListBackgroundColor2.value,
'experimentalChatsListBackgroundImagePath': experimentalChatsListBackgroundImagePath,
'drawerTopBackgroundType': drawerTopBackgroundType.index,
'drawerTopBackgroundColor1': drawerTopBackgroundColor1.value,
'drawerTopBackgroundColor2': drawerTopBackgroundColor2.value,
'drawerTopBackgroundImagePath': drawerTopBackgroundImagePath,
'drawerBottomBackgroundType': drawerBottomBackgroundType.index,
'drawerBottomBackgroundColor1': drawerBottomBackgroundColor1.value,
'drawerBottomBackgroundColor2': drawerBottomBackgroundColor2.value,
'drawerBottomBackgroundImagePath': drawerBottomBackgroundImagePath,
}; };
} }
@@ -516,44 +422,6 @@ class CustomThemePreset {
customReplyColor: json['customReplyColor'] != null customReplyColor: json['customReplyColor'] != null
? Color(json['customReplyColor'] as int) ? Color(json['customReplyColor'] as int)
: null, : null,
mobileChatBottomPadding:
(json['mobileChatBottomPadding'] as double? ?? 140.0).clamp(
60.0,
240.0,
),
ignoreMobileBottomPaddingWhenKeyboard:
json['ignoreMobileBottomPaddingWhenKeyboard'] as bool? ?? true,
useExperimentalChatsListBackground: json['useExperimentalChatsListBackground'] as bool? ?? false,
experimentalChatsListBackgroundType: () {
final index = json['experimentalChatsListBackgroundType'] as int?;
if (index == null || index < 0 || index >= ChatWallpaperType.values.length) {
return ChatWallpaperType.solid;
}
return ChatWallpaperType.values[index];
}(),
experimentalChatsListBackgroundColor1: Color(json['experimentalChatsListBackgroundColor1'] as int? ?? const Color(0xFF101010).value),
experimentalChatsListBackgroundColor2: Color(json['experimentalChatsListBackgroundColor2'] as int? ?? const Color(0xFF202020).value),
experimentalChatsListBackgroundImagePath: json['experimentalChatsListBackgroundImagePath'] as String?,
drawerTopBackgroundType: () {
final index = json['drawerTopBackgroundType'] as int?;
if (index == null || index < 0 || index >= ChatWallpaperType.values.length) {
return ChatWallpaperType.solid;
}
return ChatWallpaperType.values[index];
}(),
drawerTopBackgroundColor1: Color(json['drawerTopBackgroundColor1'] as int? ?? const Color(0xFF1E1E1E).value),
drawerTopBackgroundColor2: Color(json['drawerTopBackgroundColor2'] as int? ?? const Color(0xFF2E2E2E).value),
drawerTopBackgroundImagePath: json['drawerTopBackgroundImagePath'] as String?,
drawerBottomBackgroundType: () {
final index = json['drawerBottomBackgroundType'] as int?;
if (index == null || index < 0 || index >= ChatWallpaperType.values.length) {
return ChatWallpaperType.solid;
}
return ChatWallpaperType.values[index];
}(),
drawerBottomBackgroundColor1: Color(json['drawerBottomBackgroundColor1'] as int? ?? const Color(0xFF1E1E1E).value),
drawerBottomBackgroundColor2: Color(json['drawerBottomBackgroundColor2'] as int? ?? const Color(0xFF2E2E2E).value),
drawerBottomBackgroundImagePath: json['drawerBottomBackgroundImagePath'] as String?,
); );
} }
} }
@@ -661,26 +529,6 @@ class ThemeProvider with ChangeNotifier {
bool get debugShowMessageCount => _debugShowMessageCount; bool get debugShowMessageCount => _debugShowMessageCount;
bool get debugReadOnEnter => _debugReadOnEnter; bool get debugReadOnEnter => _debugReadOnEnter;
bool get debugReadOnAction => _debugReadOnAction; bool get debugReadOnAction => _debugReadOnAction;
double get mobileChatBottomPadding => _activeTheme.mobileChatBottomPadding;
bool get ignoreMobileBottomPaddingWhenKeyboard =>
_activeTheme.ignoreMobileBottomPaddingWhenKeyboard;
// Экспериментальные настройки фона
bool get useExperimentalChatsListBackground => _activeTheme.useExperimentalChatsListBackground;
ChatWallpaperType get experimentalChatsListBackgroundType => _activeTheme.experimentalChatsListBackgroundType;
Color get experimentalChatsListBackgroundColor1 => _activeTheme.experimentalChatsListBackgroundColor1;
Color get experimentalChatsListBackgroundColor2 => _activeTheme.experimentalChatsListBackgroundColor2;
String? get experimentalChatsListBackgroundImagePath => _activeTheme.experimentalChatsListBackgroundImagePath;
ChatWallpaperType get drawerTopBackgroundType => _activeTheme.drawerTopBackgroundType;
Color get drawerTopBackgroundColor1 => _activeTheme.drawerTopBackgroundColor1;
Color get drawerTopBackgroundColor2 => _activeTheme.drawerTopBackgroundColor2;
String? get drawerTopBackgroundImagePath => _activeTheme.drawerTopBackgroundImagePath;
ChatWallpaperType get drawerBottomBackgroundType => _activeTheme.drawerBottomBackgroundType;
Color get drawerBottomBackgroundColor1 => _activeTheme.drawerBottomBackgroundColor1;
Color get drawerBottomBackgroundColor2 => _activeTheme.drawerBottomBackgroundColor2;
String? get drawerBottomBackgroundImagePath => _activeTheme.drawerBottomBackgroundImagePath;
TransitionOption get chatTransition => _activeTheme.ultraOptimizeChats TransitionOption get chatTransition => _activeTheme.ultraOptimizeChats
? TransitionOption.systemDefault ? TransitionOption.systemDefault
@@ -1364,101 +1212,6 @@ class ThemeProvider with ChangeNotifier {
await _saveActiveTheme(); await _saveActiveTheme();
} }
Future<void> setMobileChatBottomPadding(double value) async {
final clamped = value.clamp(60.0, 240.0);
_activeTheme = _activeTheme.copyWith(
mobileChatBottomPadding: clamped,
);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setIgnoreMobileBottomPaddingWhenKeyboard(bool value) async {
_activeTheme = _activeTheme.copyWith(
ignoreMobileBottomPaddingWhenKeyboard: value,
);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setUseExperimentalChatsListBackground(bool value) async {
_activeTheme = _activeTheme.copyWith(useExperimentalChatsListBackground: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setExperimentalChatsListBackgroundType(ChatWallpaperType value) async {
_activeTheme = _activeTheme.copyWith(experimentalChatsListBackgroundType: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setExperimentalChatsListBackgroundColor1(Color value) async {
_activeTheme = _activeTheme.copyWith(experimentalChatsListBackgroundColor1: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setExperimentalChatsListBackgroundColor2(Color value) async {
_activeTheme = _activeTheme.copyWith(experimentalChatsListBackgroundColor2: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setExperimentalChatsListBackgroundImagePath(String? path) async {
_activeTheme = _activeTheme.copyWith(experimentalChatsListBackgroundImagePath: path);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerTopBackgroundType(ChatWallpaperType value) async {
_activeTheme = _activeTheme.copyWith(drawerTopBackgroundType: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerTopBackgroundColor1(Color value) async {
_activeTheme = _activeTheme.copyWith(drawerTopBackgroundColor1: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerTopBackgroundColor2(Color value) async {
_activeTheme = _activeTheme.copyWith(drawerTopBackgroundColor2: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerTopBackgroundImagePath(String? path) async {
_activeTheme = _activeTheme.copyWith(drawerTopBackgroundImagePath: path);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerBottomBackgroundType(ChatWallpaperType value) async {
_activeTheme = _activeTheme.copyWith(drawerBottomBackgroundType: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerBottomBackgroundColor1(Color value) async {
_activeTheme = _activeTheme.copyWith(drawerBottomBackgroundColor1: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerBottomBackgroundColor2(Color value) async {
_activeTheme = _activeTheme.copyWith(drawerBottomBackgroundColor2: value);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setDrawerBottomBackgroundImagePath(String? path) async {
_activeTheme = _activeTheme.copyWith(drawerBottomBackgroundImagePath: path);
notifyListeners();
await _saveActiveTheme();
}
Future<void> setCustomReplyColor(Color? color) async { Future<void> setCustomReplyColor(Color? color) async {
_activeTheme = _activeTheme.copyWith(customReplyColor: color); _activeTheme = _activeTheme.copyWith(customReplyColor: color);
notifyListeners(); notifyListeners();