feat: Implement character avatar upload and refactor character form fields and API calls.
This commit is contained in:
@@ -71,8 +71,9 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
|
||||
void _scrollToBottom() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_scrollController.hasClients) {
|
||||
// 使用 reverse: true 后,0.0 就是列表底部(最新消息处)
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
@@ -158,11 +159,20 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
|
||||
|
||||
final avatarUrl = CharacterRepository.getAvatarUrl(_character!.avatarPath);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF2E1065),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2E1065), Color(0xFF0F172A)],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
flexibleSpace: ClipRRect(
|
||||
@@ -229,30 +239,27 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF2E1065),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF2E1065), Color(0xFF0F172A)],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
reverse: true,
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.only(top: 120, bottom: 20, left: 16, right: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
||||
itemCount: _messages.length + (_isTyping ? 1 : 0),
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 16),
|
||||
itemBuilder: (context, index) {
|
||||
// 如果是正在输入的消息
|
||||
if (_isTyping && index == _messages.length) {
|
||||
return _buildTypingBubble();
|
||||
// reverse: true 模式下,索引 0 是列表的最底部
|
||||
if (_isTyping) {
|
||||
if (index == 0) return _buildTypingBubble();
|
||||
final msg = _messages[_messages.length - index];
|
||||
return _buildMessageBubble(msg, avatarUrl);
|
||||
}
|
||||
|
||||
final msg = _messages[index];
|
||||
final msg = _messages[_messages.length - 1 - index];
|
||||
return _buildMessageBubble(msg, avatarUrl);
|
||||
},
|
||||
),
|
||||
@@ -263,15 +270,16 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
if (_isVoiceMode && _character != null)
|
||||
VoiceModeOverlay(
|
||||
character: _character!,
|
||||
onClose: () => setState(() => _isVoiceMode = false),
|
||||
),
|
||||
],
|
||||
|
||||
|
||||
if (_isVoiceMode && _character != null)
|
||||
VoiceModeOverlay(
|
||||
character: _character!,
|
||||
onClose: () => setState(() => _isVoiceMode = false),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -397,7 +405,10 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isVoiceMode = true),
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
setState(() => _isVoiceMode = true);
|
||||
},
|
||||
child: Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
|
||||
Reference in New Issue
Block a user