💫 Animated height padding in inputs
This commit is contained in:
@@ -146,6 +146,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
final inputKey = useMemoized(() => GlobalKey());
|
final inputKey = useMemoized(() => GlobalKey());
|
||||||
final inputHeight = useState<double>(80.0);
|
final inputHeight = useState<double>(80.0);
|
||||||
|
|
||||||
|
// Track previous height for smooth animations
|
||||||
|
final previousInputHeight = usePrevious<double>(inputHeight.value);
|
||||||
|
|
||||||
// Periodic height measurement for dynamic sizing
|
// Periodic height measurement for dynamic sizing
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final timer = Timer.periodic(const Duration(milliseconds: 50), (_) {
|
final timer = Timer.periodic(const Duration(milliseconds: 50), (_) {
|
||||||
@@ -611,184 +614,428 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget chatMessageListWidget(
|
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
||||||
List<LocalChatMessage> messageList,
|
previousInputHeight != null && previousInputHeight != inputHeight.value
|
||||||
) => SuperListView.builder(
|
? TweenAnimationBuilder<double>(
|
||||||
listController: listController,
|
tween: Tween<double>(
|
||||||
padding: EdgeInsets.only(
|
begin: previousInputHeight,
|
||||||
top: 16,
|
end: inputHeight.value,
|
||||||
bottom:
|
),
|
||||||
MediaQuery.of(context).padding.bottom +
|
duration: const Duration(milliseconds: 200),
|
||||||
8 +
|
curve: Curves.easeOut,
|
||||||
inputHeight.value, // Leave space for chat input
|
builder:
|
||||||
),
|
(context, height, child) => SuperListView.builder(
|
||||||
controller: scrollController,
|
listController: listController,
|
||||||
reverse: true, // Show newest messages at the bottom
|
padding: EdgeInsets.only(
|
||||||
itemCount: messageList.length,
|
top: 16,
|
||||||
findChildIndexCallback: (key) {
|
bottom:
|
||||||
if (key is! ValueKey<String>) return null;
|
MediaQuery.of(context).padding.bottom + 8 + height,
|
||||||
final messageId = key.value.substring(messageKeyPrefix.length);
|
),
|
||||||
final index = messageList.indexWhere(
|
controller: scrollController,
|
||||||
(m) => (m.nonce ?? m.id) == messageId,
|
reverse: true, // Show newest messages at the bottom
|
||||||
);
|
itemCount: messageList.length,
|
||||||
// Return null for invalid indices to let SuperListView handle it properly
|
findChildIndexCallback: (key) {
|
||||||
return index >= 0 ? index : null;
|
if (key is! ValueKey<String>) return null;
|
||||||
},
|
final messageId = key.value.substring(
|
||||||
extentEstimation: (_, _) => 40,
|
messageKeyPrefix.length,
|
||||||
itemBuilder: (context, index) {
|
);
|
||||||
final message = messageList[index];
|
final index = messageList.indexWhere(
|
||||||
final nextMessage =
|
(m) => (m.nonce ?? m.id) == messageId,
|
||||||
index < messageList.length - 1 ? messageList[index + 1] : null;
|
);
|
||||||
final isLastInGroup =
|
// Return null for invalid indices to let SuperListView handle it properly
|
||||||
nextMessage == null ||
|
return index >= 0 ? index : null;
|
||||||
nextMessage.senderId != message.senderId ||
|
},
|
||||||
nextMessage.createdAt
|
extentEstimation: (_, _) => 40,
|
||||||
.difference(message.createdAt)
|
itemBuilder: (context, index) {
|
||||||
.inMinutes
|
final message = messageList[index];
|
||||||
.abs() >
|
final nextMessage =
|
||||||
3;
|
index < messageList.length - 1
|
||||||
|
? messageList[index + 1]
|
||||||
|
: null;
|
||||||
|
final isLastInGroup =
|
||||||
|
nextMessage == null ||
|
||||||
|
nextMessage.senderId != message.senderId ||
|
||||||
|
nextMessage.createdAt
|
||||||
|
.difference(message.createdAt)
|
||||||
|
.inMinutes
|
||||||
|
.abs() >
|
||||||
|
3;
|
||||||
|
|
||||||
// Use a stable animation key that doesn't change during message lifecycle
|
// Use a stable animation key that doesn't change during message lifecycle
|
||||||
final key = Key('$messageKeyPrefix${message.nonce ?? message.id}');
|
final key = Key(
|
||||||
|
'$messageKeyPrefix${message.nonce ?? message.id}',
|
||||||
|
);
|
||||||
|
|
||||||
final messageWidget = chatIdentity.when(
|
final messageWidget = chatIdentity.when(
|
||||||
skipError: true,
|
skipError: true,
|
||||||
data:
|
data:
|
||||||
(identity) => GestureDetector(
|
(identity) => GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (!isSelectionMode.value) {
|
if (!isSelectionMode.value) {
|
||||||
toggleSelectionMode();
|
toggleSelectionMode();
|
||||||
toggleMessageSelection(message.id);
|
toggleMessageSelection(message.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (isSelectionMode.value) {
|
if (isSelectionMode.value) {
|
||||||
toggleMessageSelection(message.id);
|
toggleMessageSelection(message.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color:
|
color:
|
||||||
selectedMessages.value.contains(message.id)
|
selectedMessages.value.contains(message.id)
|
||||||
? Theme.of(
|
? Theme.of(context)
|
||||||
context,
|
.colorScheme
|
||||||
).colorScheme.primaryContainer.withOpacity(0.3)
|
.primaryContainer
|
||||||
: null,
|
.withOpacity(0.3)
|
||||||
child: Stack(
|
: null,
|
||||||
children: [
|
child: Stack(
|
||||||
MessageItem(
|
children: [
|
||||||
key: settings.disableAnimation ? key : null,
|
MessageItem(
|
||||||
message: message,
|
key:
|
||||||
isCurrentUser: identity?.id == message.senderId,
|
settings.disableAnimation
|
||||||
onAction:
|
? key
|
||||||
isSelectionMode.value
|
: null,
|
||||||
? null
|
message: message,
|
||||||
: (action) {
|
isCurrentUser:
|
||||||
switch (action) {
|
identity?.id == message.senderId,
|
||||||
case MessageItemAction.delete:
|
onAction:
|
||||||
messagesNotifier.deleteMessage(
|
isSelectionMode.value
|
||||||
message.id,
|
? null
|
||||||
);
|
: (action) {
|
||||||
case MessageItemAction.edit:
|
switch (action) {
|
||||||
messageEditingTo.value =
|
case MessageItemAction.delete:
|
||||||
message.toRemoteMessage();
|
messagesNotifier
|
||||||
messageController.text =
|
.deleteMessage(
|
||||||
messageEditingTo.value?.content ?? '';
|
message.id,
|
||||||
attachments.value =
|
);
|
||||||
messageEditingTo.value!.attachments
|
case MessageItemAction.edit:
|
||||||
.map(
|
messageEditingTo.value =
|
||||||
(e) =>
|
message
|
||||||
UniversalFile.fromAttachment(
|
.toRemoteMessage();
|
||||||
e,
|
messageController.text =
|
||||||
),
|
messageEditingTo
|
||||||
)
|
.value
|
||||||
.toList();
|
?.content ??
|
||||||
case MessageItemAction.forward:
|
'';
|
||||||
messageForwardingTo.value =
|
attachments.value =
|
||||||
message.toRemoteMessage();
|
messageEditingTo
|
||||||
case MessageItemAction.reply:
|
.value!
|
||||||
messageReplyingTo.value =
|
.attachments
|
||||||
message.toRemoteMessage();
|
.map(
|
||||||
case MessageItemAction.resend:
|
(e) =>
|
||||||
messagesNotifier.retryMessage(message.id);
|
UniversalFile.fromAttachment(
|
||||||
}
|
e,
|
||||||
},
|
),
|
||||||
onJump: (messageId) {
|
)
|
||||||
scrollToMessage(
|
.toList();
|
||||||
messageId: messageId,
|
case MessageItemAction
|
||||||
messageList: messageList,
|
.forward:
|
||||||
messagesNotifier: messagesNotifier,
|
messageForwardingTo.value =
|
||||||
listController: listController,
|
message
|
||||||
scrollController: scrollController,
|
.toRemoteMessage();
|
||||||
ref: ref,
|
case MessageItemAction.reply:
|
||||||
|
messageReplyingTo.value =
|
||||||
|
message
|
||||||
|
.toRemoteMessage();
|
||||||
|
case MessageItemAction.resend:
|
||||||
|
messagesNotifier
|
||||||
|
.retryMessage(
|
||||||
|
message.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onJump: (messageId) {
|
||||||
|
scrollToMessage(
|
||||||
|
messageId: messageId,
|
||||||
|
messageList: messageList,
|
||||||
|
messagesNotifier: messagesNotifier,
|
||||||
|
listController: listController,
|
||||||
|
scrollController: scrollController,
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
progress:
|
||||||
|
attachmentProgress.value[message.id],
|
||||||
|
showAvatar: isLastInGroup,
|
||||||
|
isSelectionMode: isSelectionMode.value,
|
||||||
|
isSelected: selectedMessages.value
|
||||||
|
.contains(message.id),
|
||||||
|
onToggleSelection: toggleMessageSelection,
|
||||||
|
onEnterSelectionMode: () {
|
||||||
|
if (!isSelectionMode.value) {
|
||||||
|
toggleSelectionMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (selectedMessages.value.contains(
|
||||||
|
message.id,
|
||||||
|
))
|
||||||
|
...([
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.check,
|
||||||
|
size: 12,
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => MessageItem(
|
||||||
|
message: message,
|
||||||
|
isCurrentUser: false,
|
||||||
|
onAction: null,
|
||||||
|
progress: null,
|
||||||
|
showAvatar: false,
|
||||||
|
onJump: (_) {},
|
||||||
|
),
|
||||||
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return settings.disableAnimation
|
||||||
|
? messageWidget
|
||||||
|
: TweenAnimationBuilder<double>(
|
||||||
|
key: key,
|
||||||
|
tween: Tween<double>(begin: 0.0, end: 1.0),
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 400 + (index % 5) * 50,
|
||||||
|
), // Staggered delay
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
builder: (context, animationValue, child) {
|
||||||
|
return Transform.translate(
|
||||||
|
offset: Offset(
|
||||||
|
0,
|
||||||
|
20 * (1 - animationValue),
|
||||||
|
), // Slide up from bottom
|
||||||
|
child: Opacity(
|
||||||
|
opacity: animationValue,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: messageWidget,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
progress: attachmentProgress.value[message.id],
|
),
|
||||||
showAvatar: isLastInGroup,
|
)
|
||||||
isSelectionMode: isSelectionMode.value,
|
: SuperListView.builder(
|
||||||
isSelected: selectedMessages.value.contains(message.id),
|
listController: listController,
|
||||||
onToggleSelection: toggleMessageSelection,
|
padding: EdgeInsets.only(
|
||||||
onEnterSelectionMode: () {
|
top: 16,
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom +
|
||||||
|
8 +
|
||||||
|
inputHeight.value,
|
||||||
|
),
|
||||||
|
controller: scrollController,
|
||||||
|
reverse: true, // Show newest messages at the bottom
|
||||||
|
itemCount: messageList.length,
|
||||||
|
findChildIndexCallback: (key) {
|
||||||
|
if (key is! ValueKey<String>) return null;
|
||||||
|
final messageId = key.value.substring(messageKeyPrefix.length);
|
||||||
|
final index = messageList.indexWhere(
|
||||||
|
(m) => (m.nonce ?? m.id) == messageId,
|
||||||
|
);
|
||||||
|
// Return null for invalid indices to let SuperListView handle it properly
|
||||||
|
return index >= 0 ? index : null;
|
||||||
|
},
|
||||||
|
extentEstimation: (_, _) => 40,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final message = messageList[index];
|
||||||
|
final nextMessage =
|
||||||
|
index < messageList.length - 1
|
||||||
|
? messageList[index + 1]
|
||||||
|
: null;
|
||||||
|
final isLastInGroup =
|
||||||
|
nextMessage == null ||
|
||||||
|
nextMessage.senderId != message.senderId ||
|
||||||
|
nextMessage.createdAt
|
||||||
|
.difference(message.createdAt)
|
||||||
|
.inMinutes
|
||||||
|
.abs() >
|
||||||
|
3;
|
||||||
|
|
||||||
|
// Use a stable animation key that doesn't change during message lifecycle
|
||||||
|
final key = Key(
|
||||||
|
'$messageKeyPrefix${message.nonce ?? message.id}',
|
||||||
|
);
|
||||||
|
|
||||||
|
final messageWidget = chatIdentity.when(
|
||||||
|
skipError: true,
|
||||||
|
data:
|
||||||
|
(identity) => GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
if (!isSelectionMode.value) {
|
if (!isSelectionMode.value) {
|
||||||
toggleSelectionMode();
|
toggleSelectionMode();
|
||||||
|
toggleMessageSelection(message.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
onTap: () {
|
||||||
if (selectedMessages.value.contains(message.id))
|
if (isSelectionMode.value) {
|
||||||
Positioned(
|
toggleMessageSelection(message.id);
|
||||||
top: 8,
|
}
|
||||||
right: 8,
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 16,
|
color:
|
||||||
height: 16,
|
selectedMessages.value.contains(message.id)
|
||||||
decoration: BoxDecoration(
|
? Theme.of(context)
|
||||||
color: Theme.of(context).colorScheme.primary,
|
.colorScheme
|
||||||
shape: BoxShape.circle,
|
.primaryContainer
|
||||||
),
|
.withOpacity(0.3)
|
||||||
child: Icon(
|
: null,
|
||||||
Icons.check,
|
child: Stack(
|
||||||
size: 12,
|
children: [
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
MessageItem(
|
||||||
),
|
key: settings.disableAnimation ? key : null,
|
||||||
|
message: message,
|
||||||
|
isCurrentUser: identity?.id == message.senderId,
|
||||||
|
onAction:
|
||||||
|
isSelectionMode.value
|
||||||
|
? null
|
||||||
|
: (action) {
|
||||||
|
switch (action) {
|
||||||
|
case MessageItemAction.delete:
|
||||||
|
messagesNotifier.deleteMessage(
|
||||||
|
message.id,
|
||||||
|
);
|
||||||
|
case MessageItemAction.edit:
|
||||||
|
messageEditingTo.value =
|
||||||
|
message.toRemoteMessage();
|
||||||
|
messageController.text =
|
||||||
|
messageEditingTo
|
||||||
|
.value
|
||||||
|
?.content ??
|
||||||
|
'';
|
||||||
|
attachments.value =
|
||||||
|
messageEditingTo
|
||||||
|
.value!
|
||||||
|
.attachments
|
||||||
|
.map(
|
||||||
|
(e) =>
|
||||||
|
UniversalFile.fromAttachment(
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
case MessageItemAction.forward:
|
||||||
|
messageForwardingTo.value =
|
||||||
|
message.toRemoteMessage();
|
||||||
|
case MessageItemAction.reply:
|
||||||
|
messageReplyingTo.value =
|
||||||
|
message.toRemoteMessage();
|
||||||
|
case MessageItemAction.resend:
|
||||||
|
messagesNotifier.retryMessage(
|
||||||
|
message.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onJump: (messageId) {
|
||||||
|
scrollToMessage(
|
||||||
|
messageId: messageId,
|
||||||
|
messageList: messageList,
|
||||||
|
messagesNotifier: messagesNotifier,
|
||||||
|
listController: listController,
|
||||||
|
scrollController: scrollController,
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
progress: attachmentProgress.value[message.id],
|
||||||
|
showAvatar: isLastInGroup,
|
||||||
|
isSelectionMode: isSelectionMode.value,
|
||||||
|
isSelected: selectedMessages.value.contains(
|
||||||
|
message.id,
|
||||||
|
),
|
||||||
|
onToggleSelection: toggleMessageSelection,
|
||||||
|
onEnterSelectionMode: () {
|
||||||
|
if (!isSelectionMode.value) {
|
||||||
|
toggleSelectionMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (selectedMessages.value.contains(message.id))
|
||||||
|
...([
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.check,
|
||||||
|
size: 12,
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
loading:
|
||||||
),
|
() => MessageItem(
|
||||||
),
|
message: message,
|
||||||
loading:
|
isCurrentUser: false,
|
||||||
() => MessageItem(
|
onAction: null,
|
||||||
message: message,
|
progress: null,
|
||||||
isCurrentUser: false,
|
showAvatar: false,
|
||||||
onAction: null,
|
onJump: (_) {},
|
||||||
progress: null,
|
),
|
||||||
showAvatar: false,
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
onJump: (_) {},
|
|
||||||
),
|
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return settings.disableAnimation
|
|
||||||
? messageWidget
|
|
||||||
: TweenAnimationBuilder<double>(
|
|
||||||
key: key,
|
|
||||||
tween: Tween<double>(begin: 0.0, end: 1.0),
|
|
||||||
duration: Duration(
|
|
||||||
milliseconds: 400 + (index % 5) * 50,
|
|
||||||
), // Staggered delay
|
|
||||||
curve: Curves.easeOutCubic,
|
|
||||||
builder: (context, animationValue, child) {
|
|
||||||
return Transform.translate(
|
|
||||||
offset: Offset(
|
|
||||||
0,
|
|
||||||
20 * (1 - animationValue),
|
|
||||||
), // Slide up from bottom
|
|
||||||
child: Opacity(opacity: animationValue, child: child),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return settings.disableAnimation
|
||||||
|
? messageWidget
|
||||||
|
: TweenAnimationBuilder<double>(
|
||||||
|
key: key,
|
||||||
|
tween: Tween<double>(begin: 0.0, end: 1.0),
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 400 + (index % 5) * 50,
|
||||||
|
), // Staggered delay
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
builder: (context, animationValue, child) {
|
||||||
|
return Transform.translate(
|
||||||
|
offset: Offset(
|
||||||
|
0,
|
||||||
|
20 * (1 - animationValue),
|
||||||
|
), // Slide up from bottom
|
||||||
|
child: Opacity(opacity: animationValue, child: child),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: messageWidget,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: messageWidget,
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|||||||
@@ -407,6 +407,9 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
final inputKey = useMemoized(() => GlobalKey());
|
final inputKey = useMemoized(() => GlobalKey());
|
||||||
final inputHeight = useState<double>(80.0);
|
final inputHeight = useState<double>(80.0);
|
||||||
|
|
||||||
|
// Track previous height for smooth animations
|
||||||
|
final previousInputHeight = usePrevious<double>(inputHeight.value);
|
||||||
|
|
||||||
final chatState = useThoughtChat(
|
final chatState = useThoughtChat(
|
||||||
ref,
|
ref,
|
||||||
initialSequenceId: initialSequenceId,
|
initialSequenceId: initialSequenceId,
|
||||||
@@ -440,34 +443,86 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SuperListView.builder(
|
child:
|
||||||
listController: chatState.listController,
|
previousInputHeight != null &&
|
||||||
controller: chatState.scrollController,
|
previousInputHeight != inputHeight.value
|
||||||
padding: EdgeInsets.only(
|
? TweenAnimationBuilder<double>(
|
||||||
top: 16,
|
tween: Tween<double>(
|
||||||
bottom:
|
begin: previousInputHeight,
|
||||||
MediaQuery.of(context).padding.bottom +
|
end: inputHeight.value,
|
||||||
8 +
|
),
|
||||||
inputHeight.value, // Leave space for thought input
|
duration: const Duration(milliseconds: 200),
|
||||||
),
|
curve: Curves.easeOut,
|
||||||
reverse: true,
|
builder:
|
||||||
itemCount:
|
(context, height, child) =>
|
||||||
chatState.localThoughts.value.length +
|
SuperListView.builder(
|
||||||
(chatState.isStreaming.value ? 1 : 0),
|
listController: chatState.listController,
|
||||||
itemBuilder: (context, index) {
|
controller: chatState.scrollController,
|
||||||
if (chatState.isStreaming.value && index == 0) {
|
padding: EdgeInsets.only(
|
||||||
return ThoughtItem(
|
top: 16,
|
||||||
isStreaming: true,
|
bottom:
|
||||||
streamingItems: chatState.streamingItems.value,
|
MediaQuery.of(
|
||||||
);
|
context,
|
||||||
}
|
).padding.bottom +
|
||||||
final thoughtIndex =
|
8 +
|
||||||
chatState.isStreaming.value ? index - 1 : index;
|
height,
|
||||||
final thought =
|
),
|
||||||
chatState.localThoughts.value[thoughtIndex];
|
reverse: true,
|
||||||
return ThoughtItem(thought: thought);
|
itemCount:
|
||||||
},
|
chatState.localThoughts.value.length +
|
||||||
),
|
(chatState.isStreaming.value ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (chatState.isStreaming.value &&
|
||||||
|
index == 0) {
|
||||||
|
return ThoughtItem(
|
||||||
|
isStreaming: true,
|
||||||
|
streamingItems:
|
||||||
|
chatState.streamingItems.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final thoughtIndex =
|
||||||
|
chatState.isStreaming.value
|
||||||
|
? index - 1
|
||||||
|
: index;
|
||||||
|
final thought =
|
||||||
|
chatState
|
||||||
|
.localThoughts
|
||||||
|
.value[thoughtIndex];
|
||||||
|
return ThoughtItem(thought: thought);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SuperListView.builder(
|
||||||
|
listController: chatState.listController,
|
||||||
|
controller: chatState.scrollController,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 16,
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom +
|
||||||
|
8 +
|
||||||
|
inputHeight.value,
|
||||||
|
),
|
||||||
|
reverse: true,
|
||||||
|
itemCount:
|
||||||
|
chatState.localThoughts.value.length +
|
||||||
|
(chatState.isStreaming.value ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (chatState.isStreaming.value && index == 0) {
|
||||||
|
return ThoughtItem(
|
||||||
|
isStreaming: true,
|
||||||
|
streamingItems:
|
||||||
|
chatState.streamingItems.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final thoughtIndex =
|
||||||
|
chatState.isStreaming.value
|
||||||
|
? index - 1
|
||||||
|
: index;
|
||||||
|
final thought =
|
||||||
|
chatState.localThoughts.value[thoughtIndex];
|
||||||
|
return ThoughtItem(thought: thought);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user