немножко оптiмiзации и чистка кала за коки
This commit is contained in:
@@ -2056,7 +2056,15 @@ class _ChatListScreenState extends State<ChatListScreen>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (_isSelectionMode) {
|
if (_isSelectionMode) {
|
||||||
_toggleSelection(id);
|
_toggleSelection(id);
|
||||||
} else if (widget.onChatSelected != null) {
|
return;
|
||||||
|
}
|
||||||
|
if (imageUrl.isNotEmpty) {
|
||||||
|
unawaited(precacheImage(
|
||||||
|
CachedNetworkImageProvider(imageUrl, maxWidth: 144, maxHeight: 144),
|
||||||
|
context,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (widget.onChatSelected != null) {
|
||||||
widget.onChatSelected!(DesktopChatSelection(
|
widget.onChatSelected!(DesktopChatSelection(
|
||||||
chatId: int.parse(id),
|
chatId: int.parse(id),
|
||||||
name: name,
|
name: name,
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
|
|
||||||
String _nextTempId() => 'temp_${++_tempIdCounter}_${DateTime.now().microsecondsSinceEpoch}';
|
String _nextTempId() => 'temp_${++_tempIdCounter}_${DateTime.now().microsecondsSinceEpoch}';
|
||||||
late AnimationController _shimmerController;
|
late AnimationController _shimmerController;
|
||||||
|
Timer? _shimmerStartTimer;
|
||||||
|
bool _historyKickedOff = false;
|
||||||
List<CachedMessage> _messages = [];
|
List<CachedMessage> _messages = [];
|
||||||
int _myId = 0;
|
int _myId = 0;
|
||||||
CachedChat? chat;
|
CachedChat? chat;
|
||||||
@@ -117,7 +119,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_shimmerController = AnimationController(
|
_shimmerController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 1500),
|
duration: const Duration(milliseconds: 1500),
|
||||||
)..repeat();
|
);
|
||||||
_attachAnim = AnimationController(
|
_attachAnim = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 320),
|
duration: const Duration(milliseconds: 320),
|
||||||
@@ -141,9 +143,52 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
reverseCurve: Curves.easeIn,
|
reverseCurve: Curves.easeIn,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(_onFirstFrameRendered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onFirstFrameRendered(Duration _) {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (widget.embedded) {
|
||||||
|
_kickoffHistory();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final anim = ModalRoute.of(context)?.animation;
|
||||||
|
if (anim == null || anim.status == AnimationStatus.completed) {
|
||||||
|
_kickoffHistory();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Timer? safety;
|
||||||
|
void onStatus(AnimationStatus status) {
|
||||||
|
if (status != AnimationStatus.completed) return;
|
||||||
|
anim.removeStatusListener(onStatus);
|
||||||
|
safety?.cancel();
|
||||||
|
if (!mounted) return;
|
||||||
|
_kickoffHistory();
|
||||||
|
}
|
||||||
|
anim.addStatusListener(onStatus);
|
||||||
|
safety = Timer(const Duration(milliseconds: 400), () {
|
||||||
|
anim.removeStatusListener(onStatus);
|
||||||
|
if (!mounted) return;
|
||||||
|
_kickoffHistory();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _kickoffHistory() {
|
||||||
|
if (_historyKickedOff) return;
|
||||||
|
_historyKickedOff = true;
|
||||||
|
_shimmerStartTimer = Timer(const Duration(milliseconds: 150), () {
|
||||||
|
if (!mounted || !_isLoading) return;
|
||||||
|
_shimmerController.repeat();
|
||||||
|
});
|
||||||
_loadHistory();
|
_loadHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onLoadingFinished() {
|
||||||
|
_shimmerStartTimer?.cancel();
|
||||||
|
_shimmerStartTimer = null;
|
||||||
|
if (_shimmerController.isAnimating) _shimmerController.stop();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadHistory() async {
|
Future<void> _loadHistory() async {
|
||||||
final activeProfile = await AppDatabase.loadActiveProfile();
|
final activeProfile = await AppDatabase.loadActiveProfile();
|
||||||
_myId = activeProfile?.id ?? 0;
|
_myId = activeProfile?.id ?? 0;
|
||||||
@@ -157,19 +202,35 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
unawaited(_loadOtherPresence());
|
unawaited(_loadOtherPresence());
|
||||||
}
|
}
|
||||||
|
|
||||||
final cachedRows = await AppDatabase.loadMessages(
|
final firstRows = await AppDatabase.loadMessages(
|
||||||
|
_myId,
|
||||||
|
widget.chatId,
|
||||||
|
limit: 20,
|
||||||
|
);
|
||||||
|
if (mounted && firstRows.isNotEmpty) {
|
||||||
|
final first = firstRows.reversed
|
||||||
|
.map((r) => CachedMessage.fromDbRow(r))
|
||||||
|
.toList();
|
||||||
|
setState(() {
|
||||||
|
_messages = first;
|
||||||
|
if (api.state == SessionState.online) {
|
||||||
|
_isLoading = false;
|
||||||
|
_onLoadingFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unawaited(_loadRemainingHistory());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRemainingHistory() async {
|
||||||
|
final fullRows = await AppDatabase.loadMessages(
|
||||||
_myId,
|
_myId,
|
||||||
widget.chatId,
|
widget.chatId,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
);
|
);
|
||||||
if (mounted && cachedRows.isNotEmpty) {
|
if (mounted && fullRows.length > _messages.length) {
|
||||||
setState(() {
|
_applyMergedMessages(fullRows);
|
||||||
_messages = cachedRows.map((r) => CachedMessage.fromDbRow(r)).toList();
|
|
||||||
_messages.sort((a, b) => a.time.compareTo(b.time));
|
|
||||||
if (api.state == SessionState.online) {
|
|
||||||
_isLoading = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -180,13 +241,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
limit: 100,
|
limit: 100,
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
_applyMergedMessages(updatedRows, markLoaded: true);
|
||||||
_messages = updatedRows
|
|
||||||
.map((r) => CachedMessage.fromDbRow(r))
|
|
||||||
.toList();
|
|
||||||
_messages.sort((a, b) => a.time.compareTo(b.time));
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
_loadForwardedSenderNames();
|
_loadForwardedSenderNames();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -194,11 +249,53 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
_onLoadingFinished();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _applyMergedMessages(
|
||||||
|
List<Map<String, dynamic>> rowsDesc, {
|
||||||
|
bool markLoaded = false,
|
||||||
|
}) {
|
||||||
|
final byId = <String, CachedMessage>{
|
||||||
|
for (final m in _messages) m.id: m,
|
||||||
|
};
|
||||||
|
final merged = <CachedMessage>[];
|
||||||
|
for (final row in rowsDesc.reversed) {
|
||||||
|
final fresh = CachedMessage.fromDbRow(row);
|
||||||
|
final old = byId[fresh.id];
|
||||||
|
merged.add(old != null && _sameMessage(old, fresh) ? old : fresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
final changed = !_listsEquivalent(_messages, merged);
|
||||||
|
if (!changed && !markLoaded) return;
|
||||||
|
setState(() {
|
||||||
|
if (changed) _messages = merged;
|
||||||
|
if (markLoaded) {
|
||||||
|
_isLoading = false;
|
||||||
|
_onLoadingFinished();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _sameMessage(CachedMessage a, CachedMessage b) {
|
||||||
|
return a.id == b.id &&
|
||||||
|
a.time == b.time &&
|
||||||
|
a.status == b.status &&
|
||||||
|
a.text == b.text &&
|
||||||
|
a.senderId == b.senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _listsEquivalent(List<CachedMessage> a, List<CachedMessage> b) {
|
||||||
|
if (a.length != b.length) return false;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
if (!identical(a[i], b[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_messageController.removeListener(_onTextChanged);
|
_messageController.removeListener(_onTextChanged);
|
||||||
@@ -221,6 +318,7 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
_attachAnim.dispose();
|
_attachAnim.dispose();
|
||||||
_messageController.dispose();
|
_messageController.dispose();
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
|
_shimmerStartTimer?.cancel();
|
||||||
_shimmerController.dispose();
|
_shimmerController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -906,16 +1004,20 @@ class _ChatScreenState extends State<ChatScreen>
|
|||||||
child: bubble,
|
child: bubble,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (message.id == _lastSentId) {
|
final Widget child = message.id == _lastSentId
|
||||||
return _SentMessageAnimation(
|
? _SentMessageAnimation(
|
||||||
key: ValueKey('anim_${message.id}'),
|
key: ValueKey('anim_${message.id}'),
|
||||||
onComplete: () {
|
onComplete: () {
|
||||||
if (mounted) setState(() => _lastSentId = null);
|
if (mounted) setState(() => _lastSentId = null);
|
||||||
},
|
},
|
||||||
child: pressable,
|
child: pressable,
|
||||||
);
|
)
|
||||||
}
|
: pressable;
|
||||||
return pressable;
|
|
||||||
|
return RepaintBoundary(
|
||||||
|
key: ValueKey('msg_${message.id}'),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@@ -60,6 +63,12 @@ class _AdaptiveShellState extends State<AdaptiveShell> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onChatSelected(DesktopChatSelection chat) {
|
void _onChatSelected(DesktopChatSelection chat) {
|
||||||
|
if (chat.imageUrl.isNotEmpty) {
|
||||||
|
unawaited(precacheImage(
|
||||||
|
CachedNetworkImageProvider(chat.imageUrl, maxWidth: 144, maxHeight: 144),
|
||||||
|
context,
|
||||||
|
));
|
||||||
|
}
|
||||||
setState(() => _selected = chat);
|
setState(() => _selected = chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +93,7 @@ class _AdaptiveShellState extends State<AdaptiveShell> {
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
if (constraints.maxWidth < _breakpoint) {
|
if (constraints.maxWidth < _breakpoint) {
|
||||||
return ChatListScreen(onChatSelected: _onChatSelected);
|
return const ChatListScreen();
|
||||||
}
|
}
|
||||||
final totalWidth = constraints.maxWidth;
|
final totalWidth = constraints.maxWidth;
|
||||||
final effectiveListWidth = _listWidth.clamp(
|
final effectiveListWidth = _listWidth.clamp(
|
||||||
|
|||||||
Reference in New Issue
Block a user