From 1a74d16d854c49b103194773250ade67608a6ea2 Mon Sep 17 00:00:00 2001 From: Agboola Onasanya Date: Sun, 16 Feb 2025 18:37:42 +0000 Subject: [PATCH] Recent Conversations Page, Conversations Page implemented' --- README.md | 2 +- lib/components/constants.dart | 20 +- lib/components/messaging_provider.dart | 81 ++++++-- lib/models/chats.dart | 38 ++++ lib/models/conversations.dart | 4 +- lib/models/details.dart | 17 +- lib/models/messages.dart | 33 +++ lib/models/users.dart | 14 +- lib/pages/conversations_page.dart | 234 ++++++++++++++++++++++ lib/pages/home_page.dart | 20 +- lib/pages/login_page.dart | 2 +- lib/pages/profile_page.dart | 3 + lib/pages/recent_converstations.dart | 95 --------- lib/pages/recent_converstations_page.dart | 120 +++++++++++ lib/pages/registeration_page.dart | 4 +- lib/pages/search_page.dart | 168 ++++++++++++++++ lib/service/snackbar_service.dart | 2 - 17 files changed, 707 insertions(+), 150 deletions(-) create mode 100644 lib/models/messages.dart create mode 100644 lib/pages/conversations_page.dart delete mode 100644 lib/pages/recent_converstations.dart create mode 100644 lib/pages/recent_converstations_page.dart create mode 100644 lib/pages/search_page.dart diff --git a/README.md b/README.md index 72918e6..e396281 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,6 @@ # A chat app built using Flutter for Both iOS and Android. -Backend is being covered by Appwrite. It is opensource and can be selfhosted, which is the main reason for it being used a backend. +Backend is being covered by Appwrite. It is opensource and can be selfhosted, which is the main reason for it being used as a backend. diff --git a/lib/components/constants.dart b/lib/components/constants.dart index 640218d..49e4664 100644 --- a/lib/components/constants.dart +++ b/lib/components/constants.dart @@ -1,10 +1,14 @@ //const endPoint = 'http://10.15.45.11/v1'; //const endPoint = 'https://api.theonasanyas.com/v1'; -const endPoint = 'https://cloud.appwrite.io/v1'; -const projectId = '678bdc7c00203eff8af5'; -const selfSigned = false; -const databaseId = '678c8659000a7d6b5bff'; -const userCollectionId = '678c865e002fe1cddd06'; -const chatsCollectionId = '678c866c0038822276ad'; -const convCollectionId = '678c8667001944dee794'; -const imageStorageBucket = '678d2162002a6770aaad'; \ No newline at end of file +class Constants { + static Constants instance = Constants(); + + var endPoint = 'https://cloud.appwrite.io/v1'; + var projectId = '678bdc7c00203eff8af5'; + var selfSigned = false; + var databaseId = '678c8659000a7d6b5bff'; + var userCollectionId = '678c865e002fe1cddd06'; + var chatsCollectionId = '678c866c0038822276ad'; + var convCollectionId = '678c8667001944dee794'; + var imageStorageBucket = '678d2162002a6770aaad'; +} diff --git a/lib/components/messaging_provider.dart b/lib/components/messaging_provider.dart index 7ce4c62..fdf3e8a 100644 --- a/lib/components/messaging_provider.dart +++ b/lib/components/messaging_provider.dart @@ -4,6 +4,7 @@ import 'package:appwrite/models.dart' as model; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:messaging_app/components/constants.dart'; +import 'package:messaging_app/models/chats.dart'; import 'package:messaging_app/models/conversations.dart'; import 'package:messaging_app/models/users.dart'; import 'package:messaging_app/service/navigation_service.dart'; @@ -25,9 +26,8 @@ class MessagingProviders extends ChangeNotifier { late final Storage storage; // Auth Status - late AuthStatus status; + AuthStatus? status; late model.User? user; - late model.Session session; static MessagingProviders instance = MessagingProviders(); @@ -37,7 +37,10 @@ class MessagingProviders extends ChangeNotifier { } _init() { - client.setEndpoint(endPoint).setProject(projectId).setSelfSigned(); + client + .setEndpoint(Constants.instance.endPoint) + .setProject(Constants.instance.projectId) + .setSelfSigned(); account = Account(client); database = Databases(client); storage = Storage(client); @@ -48,6 +51,7 @@ class MessagingProviders extends ChangeNotifier { user = await account.get(); if (user != null) { status = AuthStatus.authenticated; + await updateLastSeen(); _autoLogin(); } } catch (e) { @@ -69,13 +73,14 @@ class MessagingProviders extends ChangeNotifier { try { status = AuthStatus.authenticating; - session = await account.createEmailPasswordSession( + await account.createEmailPasswordSession( email: _email, password: _password); user = await account.get(); status = AuthStatus.authenticated; SnackBarService.instance.showSnackBarSuccess("Welcome! ${user!.name}"); + await updateLastSeen(); NavigationService.instance.navigateToReplacement("home"); } on AppwriteException catch (e) { SnackBarService.instance.showSnackBarError(e.message!); @@ -109,7 +114,7 @@ class MessagingProviders extends ChangeNotifier { } Future createUser(String _email, String _password, String _name, - Future onSuccess(String _uid)) async { + Future Function(String _uid) onSuccess) async { notifyListeners(); try { user = await account.create( @@ -118,6 +123,7 @@ class MessagingProviders extends ChangeNotifier { status = AuthStatus.authenticated; await onSuccess(user!.$id); SnackBarService.instance.showSnackBarSuccess("Welcome! ${user!.name}"); + await updateLastSeen(); NavigationService.instance.goBack(); NavigationService.instance.navigateToReplacement("home"); } on AppwriteException catch (e) { @@ -136,8 +142,8 @@ class MessagingProviders extends ChangeNotifier { notifyListeners(); try { await database.createDocument( - databaseId: databaseId, - collectionId: userCollectionId, + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.userCollectionId, documentId: _documentId, data: { "name": _name, @@ -166,18 +172,51 @@ class MessagingProviders extends ChangeNotifier { notifyListeners(); } + Future updateLastSeen() async { + await database.updateDocument( + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.userCollectionId, + documentId: user!.$id, + data: {"lastSeen": DateTime.now().toUtc().toIso8601String()}, + ); + } + Stream getUser(String userId) { var _userData = database.getDocument( - databaseId: databaseId, - collectionId: userCollectionId, + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.userCollectionId, documentId: userId, ); - return _userData.asStream().map((_snapshot) { - return Users.fromData(_snapshot); + return _userData.asStream().map((_document) { + return Users.fromData(_document); }); } + Stream> getAllUsers(String _searchText) { + List _returnValue = []; + if (_searchText.isNotEmpty) { + var _usersData = database.listDocuments( + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.userCollectionId, + queries: [ + Query.contains("name", _searchText), + ], + ); + + return _usersData.asStream().map( + (_documentList) { + for (var _document in _documentList.documents) { + _returnValue.add(Users.fromData(_document)); + } + return _returnValue; + }, + ); + } else { + return Stream.empty(); + } + } + Stream> getUserConversation(String userId) { // List _chatId = []; @@ -185,8 +224,8 @@ class MessagingProviders extends ChangeNotifier { List _returnValue = []; var _conversationData = database.getDocument( - databaseId: databaseId, - collectionId: convCollectionId, + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.convCollectionId, documentId: userId.trim(), ); @@ -203,12 +242,24 @@ class MessagingProviders extends ChangeNotifier { ); } + Stream getChat(String _chatId) { + var _chat = database.getDocument( + databaseId: Constants.instance.databaseId, + collectionId: Constants.instance.chatsCollectionId, + documentId: _chatId, + ); + + return _chat.asStream().map((_document) { + return Chats.fromData(_document); + }); + } + uploadUserImage(XFile? file, String _uid) async { notifyListeners(); try { File _file = File(file!.path); var _message = await storage.createFile( - bucketId: imageStorageBucket, + bucketId: Constants.instance.imageStorageBucket, fileId: ID.unique(), file: InputFile.fromPath(path: _file.path, filename: file.name), permissions: [ @@ -217,7 +268,7 @@ class MessagingProviders extends ChangeNotifier { ], ); - return "$endPoint/storage/buckets/${_message.bucketId}/files/${_message.$id}/view?project=$projectId"; + return "${Constants.instance.endPoint}/storage/buckets/${_message.bucketId}/files/${_message.$id}/view?project=${Constants.instance.projectId}"; } on AppwriteException catch (e) { SnackBarService.instance.showSnackBarError(e.message!); // diff --git a/lib/models/chats.dart b/lib/models/chats.dart index e69de29..9241b8b 100644 --- a/lib/models/chats.dart +++ b/lib/models/chats.dart @@ -0,0 +1,38 @@ +import 'package:appwrite/models.dart'; +import 'package:messaging_app/models/messages.dart'; + +class Chats { + String ownerId; + List members; + String? type; + List? messages; + + Chats({ + required this.ownerId, + required this.members, + required this.type, + required this.messages, + }); + + factory Chats.fromData(Document _document) { + List _outMessages = []; + List _outMembers = []; + List _messages = _document.data['messages']; + + for (var _message in _messages) { + _outMessages.add(Messages.fromData(_message)); + } + + List _members = _document.data['members']; + for (var _member in _members) { + _outMembers.add(_member); + } + + return Chats( + ownerId: _document.data['ownerId'], + members: _outMembers, + type: _document.data['type'], + messages: _outMessages, + ); + } +} diff --git a/lib/models/conversations.dart b/lib/models/conversations.dart index dc5c1a2..43c684c 100644 --- a/lib/models/conversations.dart +++ b/lib/models/conversations.dart @@ -9,10 +9,10 @@ class Conversations { required this.details, }); - factory Conversations.fromData(String _chatId, String details) { + factory Conversations.fromData(String _chatId, String _details) { return Conversations( chatId: _chatId, - details: Details.fromData(details), + details: Details.fromData(_details), ); } } diff --git a/lib/models/details.dart b/lib/models/details.dart index e226b85..7f91fb1 100644 --- a/lib/models/details.dart +++ b/lib/models/details.dart @@ -3,8 +3,8 @@ import 'dart:convert'; class Details { String name; String image; - int? unseenCount; - String lastMessage; + int unseenCount; + String? lastMessage; DateTime? timeStamp; Details({ @@ -15,14 +15,13 @@ class Details { required this.unseenCount, }); - factory Details.fromData(String _data) { - Map _decodedData = json.decode(_data); + factory Details.fromData(String _details) { return Details( - name: _decodedData['name'], - image: _decodedData['image'], - lastMessage: _decodedData['lastMessage'], - timeStamp: DateTime.parse(_decodedData['timeStamp'] ?? ""), - unseenCount: _decodedData['unseenCount'], + name: json.decode(_details)['name'], + image: json.decode(_details)['image'], + lastMessage: json.decode(_details)['lastMessage'], + timeStamp: DateTime.parse(json.decode(_details)['timeStamp'] ?? ""), + unseenCount: json.decode(_details)['unseenCount'], ); } } diff --git a/lib/models/messages.dart b/lib/models/messages.dart new file mode 100644 index 0000000..0fd113d --- /dev/null +++ b/lib/models/messages.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; + +enum MessageType { + Text, + Image, +} + +class Messages { + String message; + String senderId; + MessageType type; + DateTime timeStamp; + + Messages({ + required this.message, + required this.senderId, + required this.type, + required this.timeStamp, + }); + + factory Messages.fromData(String _message) { + var _messageType = json.decode(_message)['type'] == "text" + ? MessageType.Text + : MessageType.Image; + + return Messages( + message: json.decode(_message)['message'], + senderId: json.decode(_message)['senderId'], + type: _messageType, + timeStamp: DateTime.parse(json.decode(_message)['timeStamp'] ?? ""), + ); + } +} diff --git a/lib/models/users.dart b/lib/models/users.dart index 37022ff..5ed8162 100644 --- a/lib/models/users.dart +++ b/lib/models/users.dart @@ -1,25 +1,27 @@ import 'package:appwrite/models.dart'; class Users { + String id; String name; String email; String image; DateTime lastSeen; Users({ + required this.id, required this.name, required this.email, required this.image, required this.lastSeen, }); - factory Users.fromData(Document _snapshot) { + factory Users.fromData(Document _document) { return Users( - name: _snapshot.data["name"], - email: _snapshot.data["email"], - image: _snapshot.data["image"], - lastSeen: DateTime.parse(_snapshot.data["lastSeen"]), + id: _document.data['\$id'], + name: _document.data['name'], + email: _document.data['email'], + image: _document.data['image'], + lastSeen: DateTime.parse(_document.data['lastSeen']), ); } - } diff --git a/lib/pages/conversations_page.dart b/lib/pages/conversations_page.dart new file mode 100644 index 0000000..5905e3b --- /dev/null +++ b/lib/pages/conversations_page.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; +import 'package:messaging_app/components/messaging_provider.dart'; +import 'package:messaging_app/models/chats.dart'; +import 'package:messaging_app/models/messages.dart'; +import 'package:provider/provider.dart'; +import 'package:timeago/timeago.dart' as timeago; + +class ConversationsPage extends StatefulWidget { + ConversationsPage({ + super.key, + required this.conversationId, + // required this.receiverId, + required this.chatImage, + required this.chatName, + }); + + String conversationId; + // String receiverId; + String chatName; + String chatImage; + + @override + State createState() { + return _ConversationsPageState(); + } +} + +class _ConversationsPageState extends State { + // + + late double _deviceHeight; + late double _deviceWidth; + late MessagingProviders _auth; + + @override + Widget build(BuildContext context) { + // + _deviceHeight = MediaQuery.of(context).size.height; + _deviceWidth = MediaQuery.of(context).size.width; + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + title: Text(widget.chatName), + titleTextStyle: TextStyle(fontSize: 16), + ), + body: ChangeNotifierProvider.value( + value: MessagingProviders.instance, + child: _conversationPageUI(), + ), + ); + } + + Widget _conversationPageUI() { + return Builder(builder: (_context) { + _auth = Provider.of(_context); + return Stack( + clipBehavior: Clip.none, + children: [ + _messageListView(), + _messageInputField(_context), + ], + ); + }); + } + + Widget _messageListView() { + return Container( + height: _deviceHeight * 0.75, + width: _deviceWidth, + child: StreamBuilder( + stream: MessagingProviders.instance.getChat(widget.conversationId), + builder: (_context, _snapshot) { + return _snapshot.hasData + ? ListView.builder( + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + itemCount: _snapshot.data!.messages!.length, + itemBuilder: (BuildContext _context, int _index) { + var _message = _snapshot.data!.messages![_index]; + return _messageView(_message); + }, + ) + : Text(""); + }), + ); + } + + Widget _messageView(Messages _message) { + bool _isOwnMessage = false; + if (_message.senderId == _auth.user!.$id) { + _isOwnMessage = true; + } + + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: + _isOwnMessage ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + _isOwnMessage ? Container() : _imageViewBubble(), + _textMessageBubble(_isOwnMessage, _message), + ], + ), + ); + } + + Widget _textMessageBubble(bool _isOwnMessage, Messages _message) { + List _colorScheme = _isOwnMessage + ? [Colors.blue, Color.fromRGBO(42, 117, 188, 1)] + : [Color.fromRGBO(49, 69, 69, 1), Color.fromRGBO(43, 43, 43, 1)]; + return Container( + height: _deviceHeight * 0.10, + width: _deviceWidth * 0.75, + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + colors: _colorScheme, + stops: [0.25, 0.85], + begin: Alignment.bottomLeft, + end: Alignment.bottomRight), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_message.message), + Text( + timeago.format(_message.timeStamp), + style: TextStyle( + color: Colors.white70, + ), + ), + ], + ), + ); + } + + Widget _imageViewBubble() { + return Container( + height: _deviceHeight * 0.05, + width: _deviceHeight * 0.05, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(500), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage( + widget.chatImage, + ), + ), + ), + ); + } + + Widget _messageInputField(BuildContext _context) { + return Align( + alignment: Alignment.bottomCenter, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + children: [ + _messageText(), + _imageUpload(), + _sendMessage(_context), + ], + ), + ); + } + + Widget _messageText() { + return Container( + padding: EdgeInsets.only(left: 10, right: 10), + decoration: BoxDecoration( + color: Color.fromRGBO(43, 43, 43, 1), + borderRadius: BorderRadius.circular(30) + ), + margin: EdgeInsets.symmetric( + horizontal: _deviceWidth * 0.04, + vertical: _deviceHeight * 0.02 + ), + width: _deviceWidth * 0.70, + child: TextFormField( + validator: (_input) { + if (_input == null) { + return ""; + } + }, + onChanged: (_input) { + // + }, + onSaved: (_input) { + // + }, + cursorColor: Colors.white, + decoration: InputDecoration( + border: InputBorder.none, + hintText: "type a message", + ), + autocorrect: false, + ), + ); + } + + Widget _sendMessage(BuildContext context) { + return Container( + height: _deviceHeight * 0.045, + width: _deviceHeight * 0.045, + child: IconButton( + onPressed: () {}, + icon: Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + } + + Widget _imageUpload() { + return SizedBox( + height: _deviceHeight * 0.045, + width: _deviceHeight * 0.045, + child: FloatingActionButton(onPressed: () {}, + backgroundColor: Colors.blue, + child: Icon(Icons.camera_enhance),), + ); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 9d7a32c..9064b09 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:messaging_app/pages/profile_page.dart'; -import 'package:messaging_app/pages/recent_converstations.dart'; +import 'package:messaging_app/pages/recent_converstations_page.dart'; +import 'package:messaging_app/pages/search_page.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -14,10 +15,11 @@ class HomePage extends StatefulWidget { class _HomePageState extends State with SingleTickerProviderStateMixin { +//TickerProviderStateMixin // // late double _deviceHeight; late double _deviceWidth; - late TabController _tabController; + late final TabController _tabController; @override void initState() { @@ -42,10 +44,10 @@ class _HomePageState extends State _deviceWidth = MediaQuery.of(context).size.width; return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, + // backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Slick Messenger"), + // backgroundColor: Theme.of(context).scaffoldBackgroundColor, + title: Text("the messenger"), titleTextStyle: TextStyle(fontSize: 16), bottom: TabBar( unselectedLabelColor: Colors.grey, @@ -56,19 +58,19 @@ class _HomePageState extends State Tab( icon: Icon( Icons.people_outline, - size: 25, + size: 30, ), ), Tab( icon: Icon( Icons.chat_bubble_outline, - size: 25, + size: 30, ), ), Tab( icon: Icon( Icons.person_outline, - size: 25, + size: 30, ), ), ], @@ -82,7 +84,7 @@ class _HomePageState extends State return TabBarView( controller: _tabController, children: [ - Placeholder(), + SearchPage( deviceHeight: _deviceHeight , deviceWidth: _deviceWidth,), RecentConverstationsPage(_deviceHeight, _deviceWidth), ProfilePage(_deviceHeight, _deviceWidth), ], diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 3bc0b8d..e752aa9 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -176,7 +176,7 @@ class _LoginPageState extends State { }, color: Colors.blue, child: Text( - 'Login'.toUpperCase(), + 'LOGIN', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, diff --git a/lib/pages/profile_page.dart b/lib/pages/profile_page.dart index c1458de..ab8b7e5 100644 --- a/lib/pages/profile_page.dart +++ b/lib/pages/profile_page.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:messaging_app/components/messaging_provider.dart'; import 'package:messaging_app/models/users.dart'; +import 'package:messaging_app/service/snackbar_service.dart'; import 'package:provider/provider.dart'; +// ignore: must_be_immutable class ProfilePage extends StatelessWidget { // @@ -27,6 +29,7 @@ class ProfilePage extends StatelessWidget { Widget _profilePageUI() { return Builder(builder: (BuildContext _context) { + SnackBarService.instance.buildContext = _context; _auth = Provider.of(_context); return StreamBuilder( stream: MessagingProviders.instance.getUser(_auth.user!.$id), diff --git a/lib/pages/recent_converstations.dart b/lib/pages/recent_converstations.dart deleted file mode 100644 index d10366f..0000000 --- a/lib/pages/recent_converstations.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:messaging_app/components/messaging_provider.dart'; -import 'package:messaging_app/models/conversations.dart'; -import 'package:provider/provider.dart'; -import 'package:timeago/timeago.dart' as timeago; - -class RecentConverstationsPage extends StatelessWidget { - // - - final double _deviceHeight; - final double _deviceWidth; - late MessagingProviders _auth; - - RecentConverstationsPage(this._deviceHeight, this._deviceWidth); - - @override - Widget build(BuildContext context) { - return Container( - height: _deviceHeight, - width: _deviceWidth, - child: ChangeNotifierProvider.value( - value: MessagingProviders.instance, - child: _conversationView(), - ), - ); - } - - Widget _conversationView() { - return Builder(builder: (_context) { - _auth = Provider.of(_context); - return Container( - height: _deviceHeight, - width: _deviceWidth, - child: StreamBuilder>( - stream: MessagingProviders.instance - .getUserConversation(_auth.user!.$id), - builder: (_, _snapshot) { - return _snapshot.hasData - ? ListView.builder( - itemCount: _snapshot.data!.length, - itemBuilder: (_context, _index) { - var _details = _snapshot.data![_index].details; - return ListTile( - onTap: () { - // - }, - title: Text(_details.name), - subtitle: Text(_details.lastMessage), - leading: Container( - width: 50, - height: 50, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage(_details.image), - ), - ), - ), - trailing: _listTrailingWidget(_details.timeStamp), - ); - }, - ) - : SpinKitWanderingCubes( - color: Colors.blue, - size: 50.0, - ); - }), - ); - }); - } - - Widget _listTrailingWidget(DateTime? _date) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - timeago.format(_date!), - style: TextStyle(fontSize: 15), - ), - Container( - height: 12, - width: 12, - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.circular(100), - ), - ) - ], - ); - } -} diff --git a/lib/pages/recent_converstations_page.dart b/lib/pages/recent_converstations_page.dart new file mode 100644 index 0000000..1cda9a5 --- /dev/null +++ b/lib/pages/recent_converstations_page.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:messaging_app/components/messaging_provider.dart'; +import 'package:messaging_app/models/conversations.dart'; +import 'package:messaging_app/pages/conversations_page.dart'; +import 'package:messaging_app/service/navigation_service.dart'; +import 'package:messaging_app/service/snackbar_service.dart'; +import 'package:provider/provider.dart'; +import 'package:timeago/timeago.dart' as timeago; + +// ignore: must_be_immutable +class RecentConverstationsPage extends StatelessWidget { + // + final double _deviceHeight; + final double _deviceWidth; + late MessagingProviders _auth; + + RecentConverstationsPage(this._deviceHeight, this._deviceWidth); + + @override + Widget build(BuildContext context) { + return Container( + height: _deviceHeight, + width: _deviceWidth, + child: ChangeNotifierProvider.value( + value: MessagingProviders.instance, + child: _conversationView(), + ), + ); + } + + Widget _conversationView() { + return Builder(builder: (_context) { + SnackBarService.instance.buildContext = _context; + _auth = Provider.of(_context); + return SizedBox( + height: _deviceHeight, + width: _deviceWidth, + child: StreamBuilder>( + stream: MessagingProviders.instance + .getUserConversation(_auth.user!.$id), + builder: (_, _snapshot) { + if (_snapshot.hasData) { + return ListView.builder( + itemCount: _snapshot.data!.length, + itemBuilder: (_context, _index) { + var _details = _snapshot.data![_index].details; + var _chatId = _snapshot.data![_index].chatId; + return ListTile( + onTap: () { + // + NavigationService.instance.navigateToRoute( + MaterialPageRoute( + builder: (BuildContext _context) { + return ConversationsPage( + conversationId: _chatId, + chatName: _details.name, + chatImage: _details.image, + ); + }, + ), + ); + }, + title: Text(_details.name), + subtitle: Text(_details.lastMessage!), + leading: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(_details.image), + ), + ), + ), + trailing: _listTrailingWidget(_details.timeStamp), + ); + }, + ); + } + if (_snapshot.hasError) { + return Align( + child: Text( + "no active conversation", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ); + } else { + return SpinKitWanderingCubes( + color: Colors.blue, + size: 50.0, + ); + } + }), + ); + }); + } + + Widget _listTrailingWidget(DateTime? _date) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "last message", + style: TextStyle(fontSize: 15), + ), + Text( + timeago.format(_date!), + style: TextStyle(fontSize: 15), + ) + ], + ); + } +} diff --git a/lib/pages/registeration_page.dart b/lib/pages/registeration_page.dart index 6693b3a..5239cca 100644 --- a/lib/pages/registeration_page.dart +++ b/lib/pages/registeration_page.dart @@ -226,7 +226,7 @@ class _RegisterationPageState extends State { onPressed: () { if (_image == null) { SnackBarService.instance - .showSnackBarError("Please Select Image"); + .showSnackBarError("Please Select an Image"); } if (_formKey.currentState!.validate() && _image != null) { // @@ -247,7 +247,7 @@ class _RegisterationPageState extends State { }, color: Colors.blue, child: Text( - 'Register'.toUpperCase(), + "REGISTER", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart new file mode 100644 index 0000000..d02ae30 --- /dev/null +++ b/lib/pages/search_page.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:messaging_app/components/messaging_provider.dart'; +import 'package:messaging_app/models/users.dart'; +import 'package:provider/provider.dart'; +import 'package:timeago/timeago.dart' as timeago; + +class SearchPage extends StatefulWidget { + SearchPage({ + super.key, + required this.deviceHeight, + required this.deviceWidth, + }); + + double deviceHeight; + double deviceWidth; + late MessagingProviders _auth; + + @override + State createState() => _SearchPageState(); +} + +class _SearchPageState extends State { + late String _searchText; + late List _usersData; + @override + void initState() { + super.initState(); + _searchText = ''; + _usersData = []; + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: widget.deviceHeight, + width: widget.deviceWidth, + child: ChangeNotifierProvider.value( + value: MessagingProviders.instance, + child: _searchPaheUI(), + ), + ); + } + + Widget _searchPaheUI() { + return Builder(builder: (_context) { + widget._auth = Provider.of(_context); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _searchPageUIField(), + _searchPageUIList(), + ], + ); + }); + } + + Widget _searchPageUIField() { + return Container( + height: widget.deviceHeight * 0.08, + width: widget.deviceWidth, + padding: EdgeInsets.symmetric(vertical: widget.deviceHeight * 0.02), + child: TextField( + autocorrect: false, + style: TextStyle( + color: Colors.white, + ), + onChanged: (_input) { + setState(() { + _searchText = _input; + }); + }, + decoration: InputDecoration( + prefixIcon: Icon(Icons.search), + labelText: "search", + labelStyle: TextStyle(color: Colors.white), + border: OutlineInputBorder(borderSide: BorderSide.none), + ), + ), + ); + } + + Widget _searchPageUIList() { + return StreamBuilder>( + stream: MessagingProviders.instance.getAllUsers(_searchText.trim()), + builder: (_, _snapshot) { + if (_snapshot.hasData) { + _usersData = _snapshot.data! + .where((_data) => _data.id != widget._auth.user!.$id) + .toList(); + } + return _snapshot.hasData + ? SizedBox( + height: widget.deviceHeight * 0.70, + child: ListView.builder( + itemCount: _usersData.length, + itemBuilder: (_, _index) { + var _userData = _usersData[_index]; + var _isUserActive = _userData.lastSeen.isBefore( + DateTime.now().toUtc().subtract( + Duration( + minutes: 1, + ), + ), + ); + return ListTile( + title: Text(_userData.name), + leading: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(_userData.image), + ), + ), + ), + trailing: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _isUserActive + ? Text( + "last seen", + style: TextStyle( + fontSize: 15, + ), + ) + : Text( + "active", + style: TextStyle( + fontSize: 15, + ), + ), + _isUserActive + ? Text( + timeago.format( + _userData.lastSeen, + ), + style: TextStyle(fontSize: 15), + ) + : Container( + height: 10, + width: 10, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: + BorderRadius.circular(100), + ), + ) + ], + ), + ); + }), + ) + : Container() + // : SpinKitWanderingCubes( + // color: Colors.blue, + // size: 50.0, + // ); + ; + }); + } +} diff --git a/lib/service/snackbar_service.dart b/lib/service/snackbar_service.dart index e7cfc18..7375cda 100644 --- a/lib/service/snackbar_service.dart +++ b/lib/service/snackbar_service.dart @@ -6,8 +6,6 @@ class SnackBarService { BuildContext? _buildContext; static SnackBarService instance = SnackBarService(); - SnackBarService() {} - set buildContext(BuildContext _context) { _buildContext = _context; }