From 94f8b088b917f5797f0971fe7e350186d483b10b Mon Sep 17 00:00:00 2001 From: 20120454 Date: Sun, 10 Dec 2023 11:15:12 +0700 Subject: [PATCH] chat screen --- lib/constants/app_theme.dart | 4 + lib/core/widgets/chat_thread.dart | 10 +- lib/core/widgets/main_drawer.dart | 133 ++++++++++++-- .../widgets/progress_indicator_widget.dart | 29 +-- lib/presentation/chat_screen/chat_screen.dart | 171 ++++++++++++++++++ lib/presentation/home/home_screen.dart | 20 +- 6 files changed, 322 insertions(+), 45 deletions(-) create mode 100644 lib/presentation/chat_screen/chat_screen.dart diff --git a/lib/constants/app_theme.dart b/lib/constants/app_theme.dart index 5ed9593..d6086b8 100644 --- a/lib/constants/app_theme.dart +++ b/lib/constants/app_theme.dart @@ -118,5 +118,9 @@ class AppThemeData { bodyMedium: GoogleFonts.roboto(fontWeight: _regular, fontSize: 16.0), titleLarge: GoogleFonts.roboto(fontWeight: _bold, fontSize: 20.0), labelLarge: GoogleFonts.roboto(fontWeight: _semiBold, fontSize: 14.0), + displayMedium: GoogleFonts.roboto( + fontWeight: _regular, + fontSize: 14.0, + ), ); } diff --git a/lib/core/widgets/chat_thread.dart b/lib/core/widgets/chat_thread.dart index 8699f26..47da53f 100644 --- a/lib/core/widgets/chat_thread.dart +++ b/lib/core/widgets/chat_thread.dart @@ -1,8 +1,12 @@ +import 'dart:ffi'; + import 'package:flutter/material.dart'; class ChatThread extends StatefulWidget { final String name; - const ChatThread({Key? key, required this.name}) : super(key: key); + final VoidCallback onTap; + const ChatThread({Key? key, required this.name, required this.onTap}) + : super(key: key); @override State createState() => _ChatThreadState(); @@ -49,7 +53,9 @@ class _ChatThreadState extends State { }, child: ListTile( title: Text(widget.name), - onTap: () {}, + onTap: () { + widget.onTap(); + }, ), ); } diff --git a/lib/core/widgets/main_drawer.dart b/lib/core/widgets/main_drawer.dart index dc5bce9..f55cba4 100644 --- a/lib/core/widgets/main_drawer.dart +++ b/lib/core/widgets/main_drawer.dart @@ -3,7 +3,8 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; class MainDrawer extends StatelessWidget { - const MainDrawer({super.key}); + final Function setChatThreadId; + const MainDrawer({super.key, required this.setChatThreadId}); @override Widget build(BuildContext context) { @@ -22,30 +23,122 @@ class MainDrawer extends StatelessWidget { ), title: Text('New chat'), minLeadingWidth: 10, - onTap: () {}, + onTap: () { + setChatThreadId(null); + }, ), Expanded( child: ListView( padding: EdgeInsets.zero, children: [ - ChatThread(name: 'GraphQL in Flutter'), - ChatThread(name: 'REST API Explanation'), - ChatThread(name: 'Jitsi Meet in Flutter'), - ChatThread(name: 'Websockets in Flutter'), - ChatThread(name: 'ChatGPT Flutter App'), - ChatThread(name: 'GraphQL in Flutter'), - ChatThread(name: 'REST API Explanation'), - ChatThread(name: 'Jitsi Meet in Flutter'), - ChatThread(name: 'Websockets in Flutter'), - ChatThread(name: 'GraphQL in Flutter'), - ChatThread(name: 'REST API Explanation'), - ChatThread(name: 'Jitsi Meet in Flutter'), - ChatThread(name: 'Websockets in Flutter'), - ChatThread(name: 'ChatGPT Flutter App'), - ChatThread(name: 'GraphQL in Flutter'), - ChatThread(name: 'REST API Explanation'), - ChatThread(name: 'Jitsi Meet in Flutter'), - ChatThread(name: 'Websockets in Flutter'), + ChatThread( + name: 'GraphQL in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'REST API Explanation', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Jitsi Meet in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Websockets in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'ChatGPT Flutter App', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'GraphQL in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'REST API Explanation', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Jitsi Meet in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Websockets in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'GraphQL in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'REST API Explanation', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Jitsi Meet in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Websockets in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'ChatGPT Flutter App', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'GraphQL in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'REST API Explanation', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Jitsi Meet in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), + ChatThread( + name: 'Websockets in Flutter', + onTap: () { + setChatThreadId('1'); + }, + ), ], ), ), diff --git a/lib/core/widgets/progress_indicator_widget.dart b/lib/core/widgets/progress_indicator_widget.dart index a11eeb5..265ca62 100644 --- a/lib/core/widgets/progress_indicator_widget.dart +++ b/lib/core/widgets/progress_indicator_widget.dart @@ -7,28 +7,17 @@ class CustomProgressIndicatorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Align( - alignment: Alignment.center, - child: Container( - height: 100, - constraints: BoxConstraints.expand(), - child: FittedBox( - fit: BoxFit.none, - child: SizedBox( - height: 100, - width: 100, - child: Card( - child: Padding( - padding: const EdgeInsets.all(25.0), - child: CircularProgressIndicator(), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0)), - ), + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator( + strokeWidth: 1, + color: Theme.of(context).colorScheme.primary, ), ), - decoration: BoxDecoration( - color: Color.fromARGB(100, 105, 105, 105)), ), ); } diff --git a/lib/presentation/chat_screen/chat_screen.dart b/lib/presentation/chat_screen/chat_screen.dart new file mode 100644 index 0000000..e70c3ab --- /dev/null +++ b/lib/presentation/chat_screen/chat_screen.dart @@ -0,0 +1,171 @@ +import 'package:boilerplate/core/widgets/chat_input.dart'; +import 'package:boilerplate/core/widgets/progress_indicator_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class Message { + final String content; + final bool isGPT; + final DateTime createdAt; + + Message({ + required this.content, + required this.isGPT, + required this.createdAt, + }); +} + +class ChatScreen extends StatefulWidget { + final String chatThreadId; + const ChatScreen({super.key, required this.chatThreadId}); + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + int showTimeAtIndex = -1; + final ScrollController _controller = ScrollController(); + + void scrollToBottom() { + if (_controller.hasClients) { + _controller.jumpTo( + _controller.position.maxScrollExtent, + ); + } + } + + void scrollToPosition(double position) { + if (_controller.hasClients) { + _controller.jumpTo( + position, + ); + } + } + + List messages = [ + Message( + content: 'How can I help you?', isGPT: true, createdAt: DateTime.now()), + Message( + content: 'I want to learn about Flutter', + isGPT: false, + createdAt: DateTime.now()), + Message( + content: 'What do you want to learn about Flutter?', + isGPT: true, + createdAt: DateTime.now()), + Message( + content: 'I want to learn about Bloc', + isGPT: false, + createdAt: DateTime.now()), + Message( + content: 'What do you want to learn about Bloc?', + isGPT: true, + createdAt: DateTime.now()), + Message( + content: 'I want to learn about Bloc', + isGPT: false, + createdAt: DateTime.now()), + ]; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + controller: _controller, + itemCount: messages.length + 1, + shrinkWrap: true, + padding: EdgeInsets.zero, + itemBuilder: (context, index) { + // if (index == state.messages?.length) { + // return (state is! ConversationDone) + // ? Center( + // child: SizedBox( + // width: 30, + // height: 30, + // child: CircularProgressIndicator( + // strokeWidth: 1.5, + // color: Theme.of(context).primaryColor, + // ), + // ), + // ) + // : const SizedBox(); + // } + if (index == 0) { + return CustomProgressIndicatorWidget(); + } + return Container( + padding: EdgeInsets.only( + left: (messages[index - 1].isGPT) ? 14 : 56, + right: (messages[index - 1].isGPT) ? 56 : 14, + top: 10, + bottom: 10), + child: Column( + crossAxisAlignment: (messages[index - 1].isGPT) + ? CrossAxisAlignment.start + : CrossAxisAlignment.end, + children: [ + if (index - 1 == showTimeAtIndex) + Center( + child: Padding( + padding: const EdgeInsets.only( + bottom: 5, + ), + child: Text( + DateFormat(null, 'en') + .format(messages[index - 1].createdAt), + style: Theme.of(context).textTheme.displayMedium, + ), + ), + ), + InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + setState(() { + if (showTimeAtIndex != index - 1) { + showTimeAtIndex = index - 1; + } else { + showTimeAtIndex = -1; + } + }); + }, + child: Material( + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: (messages[index - 1].isGPT) + ? Colors.grey.shade300 + : Theme.of(context).colorScheme.primary), + padding: const EdgeInsets.only( + left: 16, right: 16, top: 16, bottom: 16), + child: Text( + messages[index - 1].content, + style: TextStyle( + color: (messages[index - 1].isGPT) + ? Colors.black + : Colors.white), + ), + ), + ), + ), + ], + ), + ); + }, + ), + ), + ChatInput(onSend: () {}), + ], + ); + } +} diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 417e234..4d30701 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -1,11 +1,18 @@ import 'package:boilerplate/core/widgets/main_app_bar.dart'; import 'package:boilerplate/core/widgets/main_drawer.dart'; +import 'package:boilerplate/presentation/chat_screen/chat_screen.dart'; import 'package:boilerplate/presentation/new_chat/new_chat_screen.dart'; import 'package:flutter/material.dart'; -class HomeScreen extends StatelessWidget { +class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + String? chatThreadId; @override Widget build(BuildContext context) { return Scaffold( @@ -13,8 +20,15 @@ class HomeScreen extends StatelessWidget { preferredSize: Size.fromHeight(60), child: MainAppBar(), ), - drawer: MainDrawer(), - body: NewChatScreen(), + drawer: MainDrawer(setChatThreadId: (id) { + setState(() { + chatThreadId = id; + }); + Navigator.pop(context); + }), + body: chatThreadId != null + ? ChatScreen(chatThreadId: chatThreadId!) + : NewChatScreen(), ); } }