diff --git a/mobile/lib/core/model/message.dart b/mobile/lib/core/model/message.dart index 0aa4399..4927bf9 100644 --- a/mobile/lib/core/model/message.dart +++ b/mobile/lib/core/model/message.dart @@ -56,22 +56,6 @@ class MessageRequest with _$MessageRequest { _$MessageRequestFromJson(json); } -extension SupabaseMessageResponseListExtension - on List { - List toUserMessageList({required String userId}) => map( - (message) => UserMessage( - alias: message.user?.alias ?? message.sender, - message: Message( - id: message.id, - body: message.body, - sender: message.sender, - createdAt: message.createdAt, - ), - isFromCurrentUser: userId == message.sender, - ), - ).toList(); -} - extension MessageListExtension on List { List toUserMessageList({ required List users, diff --git a/mobile/lib/core/model/user_message.dart b/mobile/lib/core/model/user_message.dart index 4a29d5c..d24911b 100644 --- a/mobile/lib/core/model/user_message.dart +++ b/mobile/lib/core/model/user_message.dart @@ -1,4 +1,5 @@ import 'package:flutter_template/core/model/message.dart'; +import 'package:flutter_template/core/model/user.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'user_message.freezed.dart'; @@ -19,6 +20,27 @@ class UserMessage with _$UserMessage { static List fromJsonList(List json) => json.map((e) => UserMessage.fromJson(e)).toList(); + + static List fromResponse({ + required List json, + required String userId, + }) { + final messageResponse = SupabaseMessageResponse.fromJsonList(json); + return messageResponse + .map( + (message) => UserMessage( + alias: message.user?.alias ?? message.sender, + message: Message( + id: message.id, + body: message.body, + sender: message.sender, + createdAt: message.createdAt, + ), + isFromCurrentUser: userId == message.sender, + ), + ) + .toList(); + } } Message _messageSerializer(dynamic data) => Message.fromJson(data); diff --git a/mobile/lib/core/source/messages_remote_source.dart b/mobile/lib/core/source/messages_remote_source.dart index b923527..34246c1 100644 --- a/mobile/lib/core/source/messages_remote_source.dart +++ b/mobile/lib/core/source/messages_remote_source.dart @@ -22,11 +22,7 @@ class MessagesRemoteSourceImpl implements MessagesRemoteSource { .from('messages') .select('*, user:sender(id, alias)') .order('created_at', ascending: true); - // Json to UserMessages - final messageResponse = SupabaseMessageResponse.fromJsonList(response); - return messageResponse.toUserMessageList( - userId: currentUserId, - ); + return UserMessage.fromResponse(json: response, userId: currentUserId); } @override diff --git a/mobile/lib/ui/home/home_screen.dart b/mobile/lib/ui/home/home_screen.dart index 60c0b74..7cd0c21 100644 --- a/mobile/lib/ui/home/home_screen.dart +++ b/mobile/lib/ui/home/home_screen.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_template/core/model/user_message.dart'; @@ -30,10 +31,12 @@ class _HomeContentScreen extends StatefulWidget { class _HomeContentScreenState extends State<_HomeContentScreen> { final _textController = TextEditingController(); + final _scrollController = ScrollController(); @override void dispose() { _textController.dispose(); + _scrollController.dispose(); super.dispose(); } @@ -52,7 +55,9 @@ class _HomeContentScreenState extends State<_HomeContentScreen> { Expanded( child: state.messages.isEmpty ? const _EmptyStateSection() - : _MessagesSection(messages: state.messages), + : _MessagesSection( + messages: state.messages, + scrollController: _scrollController), ), _TextFieldSection(textController: _textController), SizedBox(height: 24.h), @@ -101,13 +106,46 @@ class _EmptyStateSection extends StatelessWidget { ); } -class _MessagesSection extends StatelessWidget { +class _MessagesSection extends StatefulWidget { final List messages; + final ScrollController scrollController; const _MessagesSection({ required this.messages, + required this.scrollController, }); + @override + State<_MessagesSection> createState() => _MessagesSectionState(); +} + +class _MessagesSectionState extends State<_MessagesSection> { + void _scrollDown() { + widget.scrollController.animateTo( + widget.scrollController.position.maxScrollExtent, + duration: Duration(milliseconds: 100), + curve: Curves.fastOutSlowIn, + ); + } + + @override + void initState() { + super.initState(); + + SchedulerBinding.instance.addPostFrameCallback((_) { + _scrollDown(); + }); + } + + @override + void didUpdateWidget(covariant _MessagesSection oldWidget) { + super.didUpdateWidget(oldWidget); + + SchedulerBinding.instance.addPostFrameCallback((_) { + _scrollDown(); + }); + } + @override Widget build(BuildContext context) => RefreshIndicator( onRefresh: () => context.read().refreshMessages(), @@ -117,9 +155,10 @@ class _MessagesSection extends StatelessWidget { ), child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), - itemCount: messages.length, + controller: widget.scrollController, + itemCount: widget.messages.length, itemBuilder: (context, index) { - final userMessage = messages[index]; + final userMessage = widget.messages[index]; return MessageBox( userMessage: userMessage, uppercaseMessage: context.read().uppercaseMessage,