From 3299146f64345c6366550af1d89b98b3f8406a2b Mon Sep 17 00:00:00 2001 From: Gabriel_T Date: Wed, 23 Apr 2025 13:55:13 -0700 Subject: [PATCH 1/2] start of messaging --- lib/chat_page.dart | 99 ++++++++++++++++++++++++++++++++++++++++++++++ lib/matches.dart | 2 +- lib/message.dart | 31 +++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 lib/chat_page.dart create mode 100644 lib/message.dart diff --git a/lib/chat_page.dart b/lib/chat_page.dart new file mode 100644 index 0000000..e682a5b --- /dev/null +++ b/lib/chat_page.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class ChatPage extends StatefulWidget { + final String otherUserId; + final String otherUserName; + + const ChatPage( + {super.key, required this.otherUserId, required this.otherUserName}); + + @override + State createState() => _ChatPageState(); +} + +class _ChatPageState extends State { + final TextEditingController _controller = TextEditingController(); + final currentUser = FirebaseAuth.instance.currentUser; + + Stream getMessagesStream() { + return FirebaseFirestore.instance + .collection('messages') + .where('chatId', + isEqualTo: _getChatId(currentUser!.uid, widget.otherUserId)) + .orderBy('timestamp') + .snapshots(); + } + + String _getChatId(String uid1, String uid2) { + return uid1.hashCode <= uid2.hashCode ? '$uid1\_$uid2' : '$uid2\_$uid1'; + } + + void sendMessage() async { + final text = _controller.text.trim(); + if (text.isEmpty) return; + + final chatId = _getChatId(currentUser!.uid, widget.otherUserId); + + await FirebaseFirestore.instance.collection('messages').add({ + 'chatId': chatId, + 'senderId': currentUser!.uid, + 'receiverId': widget.otherUserId, + 'text': text, + 'timestamp': DateTime.now().toUtc().toIso8601String(), + }); + + _controller.clear(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Chat with ${widget.otherUserName}")), + body: Column( + children: [ + Expanded( + child: StreamBuilder( + stream: getMessagesStream(), + builder: (context, snapshot) { + if (!snapshot.hasData) + return Center(child: CircularProgressIndicator()); + + final messages = snapshot.data!.docs; + + return ListView( + children: messages.map((doc) { + final data = doc.data() as Map; + final isMine = data['senderId'] == currentUser!.uid; + return ListTile( + title: Align( + alignment: isMine + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + padding: EdgeInsets.all(10), + color: isMine ? Colors.blue[100] : Colors.grey[300], + child: Text(data['text']), + ), + ), + ); + }).toList(), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + Expanded(child: TextField(controller: _controller)), + IconButton(onPressed: sendMessage, icon: Icon(Icons.send)), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/matches.dart b/lib/matches.dart index 16d4cf1..a809d7a 100644 --- a/lib/matches.dart +++ b/lib/matches.dart @@ -12,7 +12,7 @@ import 'profile.dart'; class MatchesPage extends StatelessWidget { final List profiles; - MatchesPage(this.profiles, { super.key }); + const MatchesPage(this.profiles, { super.key }); @override Widget build(BuildContext context) { diff --git a/lib/message.dart b/lib/message.dart new file mode 100644 index 0000000..6af6d55 --- /dev/null +++ b/lib/message.dart @@ -0,0 +1,31 @@ +class Message { + final String senderId; + final String receiverId; + final String text; + final DateTime timestamp; + + Message({ + required this.senderId, + required this.receiverId, + required this.text, + required this.timestamp, + }); + + Map toMap() { + return { + 'senderId': senderId, + 'receiverId': receiverId, + 'text': text, + 'timestamp': timestamp.toIso8601String(), + }; + } + + factory Message.fromMap(Map map) { + return Message( + senderId: map['senderId'], + receiverId: map['receiverId'], + text: map['text'], + timestamp: DateTime.parse(map['timestamp']), + ); + } +} From 39307beb51065afed77df2c0e0af72f0bdd8d358 Mon Sep 17 00:00:00 2001 From: Gabriel_T Date: Mon, 28 Apr 2025 10:27:48 -0700 Subject: [PATCH 2/2] additions for messiging with cloud firestone --- lib/main.dart | 96 ++++++++++--------- lib/matches.dart | 75 ++++++++------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 95 insertions(+), 83 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2b427e0..012ffc7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,64 +7,68 @@ void main() { } class Root extends StatelessWidget { - const Root({ super.key }); - + const Root({super.key}); + @override Widget build(BuildContext ctx) { - return MaterialApp( - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: App() - ); - } + return MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: App()); + } } + class App extends StatefulWidget { - const App({ super.key }); - - @override - State createState() => AppState(); + const App({super.key}); + + @override + State createState() => AppState(); } + class AppState extends State { - int pageIdx = 2; - List pageBuilders = [ - () => Center(child: Text("Settings Placeholder")), - () => ProfileView(Profile("My Profile", DateTime.now(), "My Bio", ["My1", "My2", "My3"])), + int pageIdx = 2; + List pageBuilders = [ + () => Center(child: Text("Settings Placeholder")), + () => ProfileView( + Profile("My Profile", DateTime.now(), "My Bio", ["My1", "My2", "My3"])), //() => ProfileView(Profile("Example Profile", DateTime.now(), "Example Bio", ["Ex1", "Ex2", "Ex3", "Ex4"])), () => ExplorePage([ - Profile("Example Profile #2", DateTime.now(), "Example Bio #2", ["Interest #1", "Interest #2", "Interest #3", "Interest #4"]), - Profile("Example Profile #1", DateTime.now(), "Example Bio", ["Ex1", "Ex2", "Ex3", "Ex4"]), - ]), - () => MatchesPage([ - Profile("Match 1", DateTime.now(), "Example Bio", ["I1", "I2"]), - Profile("Match 2", DateTime.now(), "Example Bio", ["I1", "I2"]), - Profile("Match 3", DateTime.now(), "Example Bio", ["I1", "I2"]), - ]) + Profile("Example Profile #2", DateTime.now(), "Example Bio #2", + ["Interest #1", "Interest #2", "Interest #3", "Interest #4"]), + Profile("Example Profile #1", DateTime.now(), "Example Bio", + ["Ex1", "Ex2", "Ex3", "Ex4"]), + ]), + () => MatchesPage([ + Profile("Match 1", DateTime.now(), "Example Bio", ["I1", "I2"]), + Profile("Match 2", DateTime.now(), "Example Bio", ["I1", "I2"]), + Profile("Match 3", DateTime.now(), "Example Bio", ["I1", "I2"]), + ]) ]; - + @override - Widget build(BuildContext context) { - return Scaffold( + Widget build(BuildContext context) { + return Scaffold( body: pageBuilders[pageIdx](), - bottomNavigationBar: BottomNavigationBar( + bottomNavigationBar: BottomNavigationBar( // for unknown reasons the navbar becomes (mostly) invisible when in "shifting" mode type: BottomNavigationBarType.fixed, - currentIndex: pageIdx, - onTap: (int idx) { - setState(() { - pageIdx = idx; - }); - }, - items: [ - BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"), + currentIndex: pageIdx, + onTap: (int idx) { + setState(() { + pageIdx = idx; + }); + }, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.settings), label: "Settings"), BottomNavigationBarItem(icon: Icon(Icons.person), label: "Profile"), - BottomNavigationBarItem(icon: Icon(Icons.star), label: "Explore"), - BottomNavigationBarItem(icon: Icon(Icons.heart_broken), label: "Matches"), - ], - ), - ); - } + BottomNavigationBarItem(icon: Icon(Icons.star), label: "Explore"), + BottomNavigationBarItem( + icon: Icon(Icons.heart_broken), label: "Matches"), + ], + ), + ); + } } - - diff --git a/lib/matches.dart b/lib/matches.dart index a809d7a..8ba3f48 100644 --- a/lib/matches.dart +++ b/lib/matches.dart @@ -1,41 +1,42 @@ - import 'package:flutter/material.dart'; -import 'profile.dart'; - - -/*class Match { - Profile profile; - //Profile? profile; // We may not have their profile from the server yet -}*/ - +import 'profile.dart'; // Assuming Profile is already defined elsewhere +import 'chat_page.dart'; // <- We will create this next class MatchesPage extends StatelessWidget { - - final List profiles; - const MatchesPage(this.profiles, { super.key }); - - @override - Widget build(BuildContext context) { - - Widget profileEntryBuilder(Profile profile) { - return ListTile( - title: Text(profile.name), - subtitle: Text("[latest message...]"), - leading: SizedBox.fromSize( - size: Size(40.0, 40.0), - child: Placeholder() - ), - trailing: Icon(Icons.arrow_right) - ); - } - - return ListView.separated( - itemCount: profiles.length, - itemBuilder: (context, i) => profileEntryBuilder(profiles[i]), - separatorBuilder: (context, i) => Divider(color: Colors.blueGrey[700], thickness: 2, indent: 10, endIndent: 10) - ); - } + final List profiles; + const MatchesPage(this.profiles, {super.key}); + + @override + Widget build(BuildContext context) { + Widget profileEntryBuilder(Profile profile) { + return ListTile( + title: Text(profile.name), + subtitle: Text("[latest message...]"), + leading: SizedBox.fromSize( + size: const Size(40.0, 40.0), + child: const Placeholder(), + ), + trailing: const Icon(Icons.arrow_right), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatPage(profile: profile), + ), + ); + }, + ); + } + + return ListView.separated( + itemCount: profiles.length, + itemBuilder: (context, i) => profileEntryBuilder(profiles[i]), + separatorBuilder: (context, i) => Divider( + color: Colors.blueGrey[700], + thickness: 2, + indent: 10, + endIndent: 10, + ), + ); + } } - - - diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7b9be20..804ef2f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import cloud_firestore import firebase_auth import firebase_core func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) } diff --git a/pubspec.yaml b/pubspec.yaml index 62983fd..0f1ffd5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: firebase_auth: ^5.5.1 logger: ^2.5.0 firebase_core: ^3.12.1 + cloud_firestore: ^5.6.7 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d141b74..bf6d21a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + CloudFirestorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); FirebaseAuthPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 29944d5..b83b40a 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + cloud_firestore firebase_auth firebase_core )