Merge pull request #19 from KometTeam/fix/tab-switch-freeze
Fix/tab switch freeze
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../../main.dart' show api;
|
||||
@@ -166,10 +167,11 @@ class _CallsTabState extends State<CallsTab> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: call.avatarUrl != null && call.avatarUrl!.isNotEmpty
|
||||
? Image.network(
|
||||
call.avatarUrl!,
|
||||
? CachedNetworkImage(
|
||||
imageUrl: call.avatarUrl!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (context, url, error) =>
|
||||
_buildPlaceholderAvatar(cs, call.name),
|
||||
)
|
||||
: _buildPlaceholderAvatar(cs, call.name),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:komet/backend/modules/messages.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -93,6 +94,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
final List<ScrollController> _folderChatScrollControllers = [];
|
||||
final List<VoidCallback> _folderChatScrollListenerFns = [];
|
||||
final Set<String> _selectedChats = {};
|
||||
final Set<int> _inflightContactIds = {};
|
||||
|
||||
DateTime _storiesRevealLayoutSettleUntil =
|
||||
DateTime.fromMillisecondsSinceEpoch(0);
|
||||
@@ -157,14 +159,11 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 350),
|
||||
);
|
||||
_navPageAnimController =
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 350),
|
||||
value: 1.0,
|
||||
)..addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
_navPageAnimController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 350),
|
||||
value: 1.0,
|
||||
);
|
||||
_shimmerController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
@@ -262,6 +261,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
}
|
||||
_isInitialLoading = false;
|
||||
});
|
||||
_prefetchContactsForChats(chats);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_jumpFolderPageToSelection();
|
||||
@@ -315,6 +315,33 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _prefetchContactsForChats(List<CachedChat> chats) {
|
||||
final myId = _profile?.id;
|
||||
final ids = <int>{};
|
||||
for (final chat in chats) {
|
||||
if (chat.type == 'DIALOG' && chat.id != 0) {
|
||||
for (final entry in chat.participants.entries) {
|
||||
if (entry.key != myId) {
|
||||
ids.add(entry.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
final senderId = chat.lastMsgSenderId;
|
||||
if (senderId != null) ids.add(senderId);
|
||||
}
|
||||
ids.removeWhere((id) => ContactCache.get(id) != null);
|
||||
ids.removeAll(_inflightContactIds);
|
||||
if (ids.isEmpty) return;
|
||||
_inflightContactIds.addAll(ids);
|
||||
for (final id in ids) {
|
||||
messagesModule.searchContactById(id).whenComplete(() {
|
||||
_inflightContactIds.remove(id);
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<CachedChat> _chatsForPageIndex(int pageIndex) {
|
||||
if (_folders.isEmpty) return _chats;
|
||||
if (pageIndex < 0 || pageIndex >= _folders.length) return _chats;
|
||||
@@ -1028,7 +1055,10 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
final chat = chats[index];
|
||||
|
||||
if (chat.type.isNotEmpty && chat.type == "DIALOG" && chat.id != 0) {
|
||||
final secondId = chat.participants.entries.where((entry) => entry.key != _profile?.id).first.key;
|
||||
final secondId = chat.participants.entries
|
||||
.where((entry) => entry.key != _profile?.id)
|
||||
.first
|
||||
.key;
|
||||
final name = ContactCache.get(secondId);
|
||||
final avatar = ContactCache.getAvatar(secondId);
|
||||
|
||||
@@ -1354,17 +1384,6 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
return lo + 4;
|
||||
}
|
||||
|
||||
final pageDisplayT = _effectivePageNavRowT(
|
||||
inactiveWidth: inactiveWidth,
|
||||
bubbleLeftForIndex: bubbleLeftForPageT,
|
||||
);
|
||||
|
||||
final showChatsFab =
|
||||
!_isSelectionMode &&
|
||||
(_navDragging || _navPageAnimController.isAnimating
|
||||
? pageDisplayT < 1.0
|
||||
: _currentNavIndex == 0);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
ClipRect(
|
||||
@@ -1378,8 +1397,8 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
child: SizedBox(
|
||||
width: pageW * 4,
|
||||
height: pageH,
|
||||
child: Transform.translate(
|
||||
offset: Offset(-pageDisplayT * pageW, 0),
|
||||
child: AnimatedBuilder(
|
||||
animation: _navPageAnimController,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -1413,15 +1432,37 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (context, child) {
|
||||
final pageDisplayT = _effectivePageNavRowT(
|
||||
inactiveWidth: inactiveWidth,
|
||||
bubbleLeftForIndex: bubbleLeftForPageT,
|
||||
);
|
||||
return Transform.translate(
|
||||
offset: Offset(-pageDisplayT * pageW, 0),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildDockedBottomNav(cs, navInnerW, bottomInset),
|
||||
ListenableBuilder(
|
||||
listenable: _fabController,
|
||||
builder: (context, child) {
|
||||
AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
_fabController,
|
||||
_navPageAnimController,
|
||||
]),
|
||||
builder: (context, _) {
|
||||
final pageDisplayT = _effectivePageNavRowT(
|
||||
inactiveWidth: inactiveWidth,
|
||||
bubbleLeftForIndex: bubbleLeftForPageT,
|
||||
);
|
||||
final showChatsFab =
|
||||
!_isSelectionMode &&
|
||||
(_navDragging || _navPageAnimController.isAnimating
|
||||
? pageDisplayT < 1.0
|
||||
: _currentNavIndex == 0);
|
||||
final double val = Curves.easeOutCubic.transform(
|
||||
_fabController.value,
|
||||
);
|
||||
@@ -1563,7 +1604,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 26,
|
||||
backgroundImage: NetworkImage(imageUrl),
|
||||
backgroundImage: CachedNetworkImageProvider(imageUrl),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
@@ -1717,7 +1758,7 @@ Navigator.push(
|
||||
radius: 24,
|
||||
backgroundColor: cs.surfaceContainerHighest,
|
||||
backgroundImage: imageUrl.isNotEmpty
|
||||
? NetworkImage(imageUrl)
|
||||
? CachedNetworkImageProvider(imageUrl)
|
||||
: null,
|
||||
child: imageUrl.isEmpty
|
||||
? Text(
|
||||
@@ -2019,7 +2060,7 @@ Navigator.push(
|
||||
),
|
||||
child: CircleAvatar(
|
||||
radius: 12,
|
||||
backgroundImage: NetworkImage(imageUrl),
|
||||
backgroundImage: CachedNetworkImageProvider(imageUrl),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:komet/backend/modules/chats.dart';
|
||||
import 'package:komet/frontend/screens/chats/chat_info_screen.dart';
|
||||
@@ -258,18 +259,18 @@ class _ChatScreenState extends State<ChatScreen>
|
||||
return Scaffold(
|
||||
backgroundColor: cs.surface,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(kToolbarHeight),
|
||||
preferredSize: Size.fromHeight(kToolbarHeight),
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => ChatInfoScreen(
|
||||
chatId: widget.chatId,
|
||||
chatId: widget.chatId,
|
||||
name: widget.name,
|
||||
imageUrl: widget.imageUrl,
|
||||
chatType: widget.chatType)
|
||||
)
|
||||
),
|
||||
child: AppBar(
|
||||
child: AppBar(
|
||||
backgroundColor: cs.surfaceContainerHigh,
|
||||
foregroundColor: cs.onSurface,
|
||||
elevation: 0,
|
||||
@@ -285,7 +286,7 @@ class _ChatScreenState extends State<ChatScreen>
|
||||
if (widget.imageUrl.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 18,
|
||||
backgroundImage: NetworkImage(widget.imageUrl),
|
||||
backgroundImage: CachedNetworkImageProvider(widget.imageUrl),
|
||||
)
|
||||
else
|
||||
CircleAvatar(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../../core/storage/app_database.dart';
|
||||
@@ -84,10 +85,11 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
),
|
||||
child: ClipOval(
|
||||
child: contact.baseUrl != null && contact.baseUrl!.isNotEmpty
|
||||
? Image.network(
|
||||
contact.baseUrl!,
|
||||
? CachedNetworkImage(
|
||||
imageUrl: contact.baseUrl!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (context, url, error) =>
|
||||
_buildPlaceholderAvatar(cs, nameToDisplay),
|
||||
)
|
||||
: _buildPlaceholderAvatar(cs, nameToDisplay),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -333,10 +334,11 @@ child: _buildSection(
|
||||
),
|
||||
child: ClipOval(
|
||||
child: _profile?.baseUrl != null && _profile!.baseUrl!.isNotEmpty
|
||||
? Image.network(
|
||||
_profile!.baseUrl!,
|
||||
? CachedNetworkImage(
|
||||
imageUrl: _profile!.baseUrl!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (context, url, error) =>
|
||||
_buildPlaceholderAvatar(cs, name),
|
||||
)
|
||||
: _buildPlaceholderAvatar(cs, name),
|
||||
@@ -514,7 +516,21 @@ class _PhoneSpoilerState extends State<_PhoneSpoiler>
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
)..repeat();
|
||||
);
|
||||
if (!widget.isVisible) {
|
||||
_controller.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _PhoneSpoiler oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.isVisible == oldWidget.isVisible) return;
|
||||
if (widget.isVisible) {
|
||||
_controller.stop();
|
||||
} else if (!_controller.isAnimating) {
|
||||
_controller.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:komet/backend/modules/chats.dart';
|
||||
import 'package:komet/backend/modules/contacts.dart';
|
||||
@@ -314,7 +315,7 @@ class MessageBubble extends StatelessWidget {
|
||||
? [
|
||||
CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
]
|
||||
@@ -455,7 +456,7 @@ class MessageBubble extends StatelessWidget {
|
||||
if (senderAvatar != null && senderAvatar.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 10,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
else
|
||||
@@ -707,7 +708,7 @@ class MessageBubble extends StatelessWidget {
|
||||
if (senderAvatar != null && senderAvatar.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 10,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
else
|
||||
@@ -780,7 +781,7 @@ class MessageBubble extends StatelessWidget {
|
||||
if (senderAvatar != null && senderAvatar.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 10,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
else
|
||||
@@ -846,7 +847,7 @@ class MessageBubble extends StatelessWidget {
|
||||
if (senderAvatar != null && senderAvatar.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 10,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
else
|
||||
@@ -912,12 +913,13 @@ class MessageBubble extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
if (imageUrl.isNotEmpty)
|
||||
Image.network(
|
||||
imageUrl,
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
width: constrainedWidth,
|
||||
height: constrainedHeight,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => _buildPhotoPlaceholder(
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) => _buildPhotoPlaceholder(
|
||||
ctx,
|
||||
constrainedWidth,
|
||||
constrainedHeight,
|
||||
@@ -1016,12 +1018,13 @@ class MessageBubble extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
if (imageUrl.isNotEmpty)
|
||||
Image.network(
|
||||
imageUrl,
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) =>
|
||||
_buildPhotoPlaceholder(ctx, 100, 100),
|
||||
)
|
||||
else
|
||||
@@ -1050,12 +1053,13 @@ class MessageBubble extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
if (imageUrl.isNotEmpty)
|
||||
Image.network(
|
||||
imageUrl,
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) =>
|
||||
_buildPhotoPlaceholder(ctx, 100, 100),
|
||||
)
|
||||
else
|
||||
@@ -1277,12 +1281,13 @@ class MessageBubble extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
if (preview.isNotEmpty)
|
||||
Image.network(
|
||||
preview,
|
||||
CachedNetworkImage(
|
||||
imageUrl: preview,
|
||||
width: 150,
|
||||
height: 150,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) =>
|
||||
_buildPhotoPlaceholder(ctx, 150, 150),
|
||||
)
|
||||
else
|
||||
@@ -1334,10 +1339,11 @@ class MessageBubble extends StatelessWidget {
|
||||
child: photoUrl != null && photoUrl.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Image.network(
|
||||
photoUrl,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: photoUrl,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Icon(
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) => Icon(
|
||||
Symbols.person,
|
||||
color: isMe ? Colors.white : cs.primary,
|
||||
size: 24,
|
||||
@@ -1436,7 +1442,7 @@ class MessageBubble extends StatelessWidget {
|
||||
if (senderAvatar != null && senderAvatar.isNotEmpty)
|
||||
CircleAvatar(
|
||||
radius: 10,
|
||||
backgroundImage: NetworkImage(senderAvatar),
|
||||
backgroundImage: CachedNetworkImageProvider(senderAvatar),
|
||||
backgroundColor: cs.primaryContainer,
|
||||
)
|
||||
else
|
||||
@@ -1479,10 +1485,11 @@ class MessageBubble extends StatelessWidget {
|
||||
child: photoUrl != null && photoUrl.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Image.network(
|
||||
photoUrl,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: photoUrl,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Icon(
|
||||
fadeInDuration: const Duration(milliseconds: 120),
|
||||
errorWidget: (_, __, ___) => Icon(
|
||||
Symbols.person,
|
||||
color: isMe ? Colors.white : cs.primary,
|
||||
size: 24,
|
||||
|
||||
@@ -53,6 +53,7 @@ dependencies:
|
||||
shared_preferences: ^2.5.4
|
||||
package_info_plus: ^9.0.1
|
||||
mobile_scanner: ^7.2.0
|
||||
cached_network_image: ^3.4.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user