Added Dark Mode (Provider) and simple ChatPage (using Firestore as Database).
parent
3f3ef66fcf
commit
8cec856e1c
|
@ -0,0 +1,37 @@
|
|||
import 'package:cofounderella/themes/theme_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ChatBubble extends StatelessWidget {
|
||||
final String message;
|
||||
final bool isCurrentUser;
|
||||
|
||||
const ChatBubble({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// control bubble color according to light or dark mode
|
||||
bool isDarkMode =
|
||||
Provider.of<ThemeProvider>(context, listen: false).isDarkMode;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
// TODO check colors for bubbles and text below
|
||||
color: isCurrentUser
|
||||
? (isDarkMode ? Colors.green.shade600 : Colors.green.shade500)
|
||||
: (isDarkMode ? Colors.grey.shade300 : Colors.grey.shade700),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
margin: const EdgeInsets.symmetric(vertical: 2.5, horizontal: 25),
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cofounderella/auth/auth_service.dart';
|
||||
import 'package:cofounderella/services/auth/auth_service.dart';
|
||||
import 'package:cofounderella/pages/settings_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
|
@ -2,14 +2,16 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class MyTextField extends StatelessWidget {
|
||||
final String hintText;
|
||||
final bool hideText;
|
||||
final bool obscureText;
|
||||
final TextEditingController controller;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
const MyTextField({
|
||||
super.key,
|
||||
required this.hintText,
|
||||
required this.hideText,
|
||||
required this.obscureText,
|
||||
required this.controller,
|
||||
this.focusNode,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -17,8 +19,9 @@ class MyTextField extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(25.0),
|
||||
child: TextField(
|
||||
obscureText: hideText,
|
||||
obscureText: obscureText,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class UserTile extends StatelessWidget {
|
||||
final String text;
|
||||
final void Function()? onTap;
|
||||
|
||||
const UserTile({super.key, required this.text, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 25),
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
// icon
|
||||
const Icon(Icons.person),
|
||||
|
||||
const SizedBox(width: 20),
|
||||
|
||||
// user name
|
||||
Text(text),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:cofounderella/auth/auth_gate.dart';
|
||||
import 'package:cofounderella/themes/light_mode.dart';
|
||||
import 'package:cofounderella/services/auth/auth_gate.dart';
|
||||
import 'package:cofounderella/themes/theme_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
|
||||
void main() async {
|
||||
// Firebase stuff
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
@ -12,7 +12,12 @@ void main() async {
|
|||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
// Standard stuff
|
||||
runApp(const MyApp());
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => ThemeProvider(),
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
@ -24,10 +29,8 @@ class MyApp extends StatelessWidget {
|
|||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Flutter Demo', // TODO change title
|
||||
theme: lightMode,
|
||||
theme: Provider.of<ThemeProvider>(context).themeData,
|
||||
home: const AuthGate(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class Message {
|
||||
final String senderID;
|
||||
final String senderEmail;
|
||||
final String receiverID;
|
||||
final String message;
|
||||
final Timestamp timestamp;
|
||||
|
||||
Message({
|
||||
required this.senderID,
|
||||
required this.senderEmail,
|
||||
required this.receiverID,
|
||||
required this.message,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
// convert to a map
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'senderID': senderID,
|
||||
'senderEmail': senderEmail,
|
||||
'receiverID': receiverID,
|
||||
'message': message,
|
||||
'timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:cofounderella/components/chat_bubble.dart';
|
||||
import 'package:cofounderella/components/my_textfield.dart';
|
||||
import 'package:cofounderella/services/auth/auth_service.dart';
|
||||
import 'package:cofounderella/services/chat/chat_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatPage extends StatefulWidget {
|
||||
final String receiverEmail;
|
||||
final String receiverID;
|
||||
|
||||
const ChatPage({
|
||||
super.key,
|
||||
required this.receiverEmail,
|
||||
required this.receiverID,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ChatPage> createState() => _ChatPageState();
|
||||
}
|
||||
|
||||
class _ChatPageState extends State<ChatPage> {
|
||||
// text controller
|
||||
final TextEditingController _messageController = TextEditingController();
|
||||
|
||||
// chat and auth services
|
||||
final ChatService _chatService = ChatService();
|
||||
final AuthService _authService = AuthService();
|
||||
|
||||
// for textfield focus
|
||||
FocusNode myFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// add listener to focus node
|
||||
myFocusNode.addListener(() {
|
||||
if (myFocusNode.hasFocus) {
|
||||
// cause a delay so that the keyboard has time to show up
|
||||
// then the amount of remaining space will be calculated
|
||||
// then scroll down
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => scrollDown(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// wait a bit for listview to be built, then scroll to bottom
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => scrollDown(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myFocusNode.dispose();
|
||||
_messageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// scroll controller
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
void scrollDown() {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
}
|
||||
|
||||
void sendMessage() async {
|
||||
// send message if not empty
|
||||
if (_messageController.text.isNotEmpty) {
|
||||
await _chatService.sendMessage(
|
||||
widget.receiverID, _messageController.text);
|
||||
|
||||
// clear text controller
|
||||
_messageController.clear();
|
||||
}
|
||||
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.receiverEmail)),
|
||||
body: Column(
|
||||
children: [
|
||||
// display all messages
|
||||
Expanded(
|
||||
child: _buildMessageList(),
|
||||
),
|
||||
|
||||
// user input
|
||||
_buildUserInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageList() {
|
||||
String senderID = _authService.getCurrentUser()!.uid;
|
||||
return StreamBuilder(
|
||||
stream: _chatService.getMessages(senderID, widget.receiverID),
|
||||
builder: (context, snapshot) {
|
||||
// errors
|
||||
if (snapshot.hasError) {
|
||||
return Text("Error: ${snapshot.error}");
|
||||
}
|
||||
|
||||
// loading
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Text("Loading..");
|
||||
}
|
||||
|
||||
// return list view
|
||||
return ListView(
|
||||
controller: _scrollController,
|
||||
children:
|
||||
snapshot.data!.docs.map((doc) => _buildMessageItem(doc)).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageItem(DocumentSnapshot doc) {
|
||||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
||||
|
||||
// align message to the right if sender is current user, otherwise left
|
||||
bool isCurrentUser = data['senderID'] == _authService.getCurrentUser()!.uid;
|
||||
var alignment =
|
||||
isCurrentUser ? Alignment.centerRight : Alignment.centerLeft;
|
||||
|
||||
return Container(
|
||||
alignment: alignment,
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
ChatBubble(
|
||||
message: data["message"],
|
||||
isCurrentUser: isCurrentUser,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserInput() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50.0),
|
||||
child: Row(
|
||||
children: [
|
||||
// text should take up most of space
|
||||
Expanded(
|
||||
child: MyTextField(
|
||||
controller: _messageController,
|
||||
hintText: "Type a message",
|
||||
obscureText: false, // TODO make this optional
|
||||
focusNode: myFocusNode,
|
||||
),
|
||||
),
|
||||
|
||||
// send button
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 25),
|
||||
child: IconButton(
|
||||
onPressed: sendMessage,
|
||||
icon: const Icon(Icons.send),
|
||||
color: Colors.white, // TODO check colors
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,91 +1,80 @@
|
|||
import 'package:cofounderella/components/my_drawer.dart';
|
||||
import 'package:cofounderella/components/user_tile.dart';
|
||||
import 'package:cofounderella/services/auth/auth_service.dart';
|
||||
import 'package:cofounderella/services/chat/chat_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
import 'chat_page.dart';
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
class HomePage extends StatelessWidget {
|
||||
HomePage({super.key});
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
// chat and auth service
|
||||
final ChatService _chatService = ChatService();
|
||||
final AuthService _authService = AuthService();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
title: const Text("Home"),
|
||||
// TODO appbar style: remove background and set elevation to 0 ?
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.grey.shade800,
|
||||
elevation: 0,
|
||||
),
|
||||
drawer: const MyDrawer(),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
body: _buildUserList(),
|
||||
);
|
||||
}
|
||||
|
||||
// build a list of users except for the current logged in user
|
||||
Widget _buildUserList() {
|
||||
return StreamBuilder(
|
||||
stream: _chatService.getUsersStream(),
|
||||
builder: (context, snapshot) {
|
||||
// error
|
||||
if (snapshot.hasError) {
|
||||
return const Text("Error");
|
||||
}
|
||||
|
||||
//loading
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Text("Loading..");
|
||||
}
|
||||
|
||||
// return list view
|
||||
return ListView(
|
||||
children: snapshot.data!
|
||||
.map<Widget>(
|
||||
(userData) => _buildUserListItem(userData, context))
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// build individual user list item
|
||||
Widget _buildUserListItem(
|
||||
Map<String, dynamic> userData, BuildContext context) {
|
||||
// display all users except current user
|
||||
if (userData["email"] != _authService.getCurrentUser()!.email) {
|
||||
return UserTile(
|
||||
text: userData["email"],
|
||||
onTap: () {
|
||||
// tapped on a user -> go to chat page // TODO
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatPage(
|
||||
receiverEmail: userData["email"],
|
||||
receiverID: userData["uid"],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cofounderella/auth/auth_service.dart';
|
||||
import 'package:cofounderella/services/auth/auth_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cofounderella/components/my_button.dart';
|
||||
import 'package:cofounderella/components/my_textfield.dart';
|
||||
|
@ -63,7 +63,7 @@ class LoginPage extends StatelessWidget {
|
|||
// email textfield
|
||||
MyTextField(
|
||||
hintText: "E-Mail",
|
||||
hideText: false,
|
||||
obscureText: false,
|
||||
controller: _emailController,
|
||||
),
|
||||
|
||||
|
@ -72,7 +72,7 @@ class LoginPage extends StatelessWidget {
|
|||
// password textfield
|
||||
MyTextField(
|
||||
hintText: "Password",
|
||||
hideText: true,
|
||||
obscureText: true,
|
||||
controller: _passwordController,
|
||||
),
|
||||
|
||||
|
@ -94,7 +94,7 @@ class LoginPage extends StatelessWidget {
|
|||
),
|
||||
GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Text(
|
||||
child: const Text(
|
||||
"Register now",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cofounderella/auth/auth_service.dart';
|
||||
import 'package:cofounderella/services/auth/auth_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../components/my_button.dart';
|
||||
|
@ -20,12 +20,12 @@ class RegisterPage extends StatelessWidget {
|
|||
// register method
|
||||
void register(BuildContext context) {
|
||||
// get auth service
|
||||
final _auth = AuthService();
|
||||
final auth = AuthService();
|
||||
|
||||
// check if passwords match
|
||||
if (_passwordController.text == _confirmPassController.text) {
|
||||
try {
|
||||
_auth.signUpWithEmailPassword(
|
||||
auth.signUpWithEmailPassword(
|
||||
_emailController.text,
|
||||
_passwordController.text,
|
||||
);
|
||||
|
@ -76,19 +76,19 @@ class RegisterPage extends StatelessWidget {
|
|||
// name text field
|
||||
MyTextField(
|
||||
hintText: "First Name",
|
||||
hideText: false,
|
||||
obscureText: false,
|
||||
controller: _nameController,
|
||||
),
|
||||
MyTextField(
|
||||
hintText: "Last Name",
|
||||
hideText: false,
|
||||
obscureText: false,
|
||||
controller: _lastnameController,
|
||||
),
|
||||
|
||||
// email text field
|
||||
MyTextField(
|
||||
hintText: "E-Mail",
|
||||
hideText: false,
|
||||
obscureText: false,
|
||||
controller: _emailController,
|
||||
),
|
||||
|
||||
|
@ -97,13 +97,13 @@ class RegisterPage extends StatelessWidget {
|
|||
// password text field
|
||||
MyTextField(
|
||||
hintText: "Password",
|
||||
hideText: true,
|
||||
obscureText: true,
|
||||
controller: _passwordController,
|
||||
),
|
||||
|
||||
MyTextField(
|
||||
hintText: "Confirm Password",
|
||||
hideText: true,
|
||||
obscureText: true,
|
||||
controller: _confirmPassController,
|
||||
),
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import 'package:cofounderella/themes/theme_provider.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
@ -6,9 +9,32 @@ class SettingsPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
appBar: AppBar(
|
||||
title: const Text("Settings"),
|
||||
),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.all(25),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// dark mode switch
|
||||
const Text("Dark Mode"),
|
||||
CupertinoSwitch(
|
||||
value:
|
||||
Provider.of<ThemeProvider>(context, listen: false).isDarkMode,
|
||||
onChanged: (value) =>
|
||||
Provider.of<ThemeProvider>(context, listen: false)
|
||||
.toggleTheme(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cofounderella/auth/login_or_register.dart';
|
||||
import 'package:cofounderella/services/auth/login_or_register.dart';
|
||||
import 'package:cofounderella/pages/home_page.dart';
|
||||
|
||||
class AuthGate extends StatelessWidget{
|
||||
|
@ -15,13 +15,11 @@ class AuthGate extends StatelessWidget{
|
|||
|
||||
// check if user is logged in or not
|
||||
if(snapshot.hasData){
|
||||
return const MyHomePage(title: "MyHomePage Test Title",);
|
||||
return HomePage();
|
||||
}
|
||||
else {
|
||||
return const LoginOrRegister();
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
),
|
||||
);
|
|
@ -1,8 +1,15 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
class AuthService {
|
||||
// instance of auth
|
||||
// instance of auth and firestore
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
// get current user
|
||||
User? getCurrentUser() {
|
||||
return _auth.currentUser;
|
||||
}
|
||||
|
||||
//sign in
|
||||
Future<UserCredential> signInWithEmailPassword(String email, password) async {
|
||||
|
@ -11,6 +18,16 @@ class AuthService {
|
|||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
// save user info if it does not already exist
|
||||
// TODO TESTING - same code snippet as for sign up
|
||||
_firestore.collection("Users").doc(userCredential.user!.uid).set(
|
||||
{
|
||||
'uid': userCredential.user!.uid,
|
||||
'email': email,
|
||||
},
|
||||
);
|
||||
|
||||
return userCredential;
|
||||
} on FirebaseAuthException catch (e) {
|
||||
throw Exception(e.code);
|
||||
|
@ -20,11 +37,21 @@ class AuthService {
|
|||
// sign up (register)
|
||||
Future<UserCredential> signUpWithEmailPassword(String email, password) async {
|
||||
try {
|
||||
// create user
|
||||
UserCredential userCredential =
|
||||
await _auth.createUserWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
// save user info in a document
|
||||
_firestore.collection("Users").doc(userCredential.user!.uid).set(
|
||||
{
|
||||
'uid': userCredential.user!.uid,
|
||||
'email': email,
|
||||
},
|
||||
);
|
||||
|
||||
return userCredential;
|
||||
} on FirebaseAuthException catch (e) {
|
||||
throw Exception(e.code);
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:cofounderella/models/message.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
class ChatService {
|
||||
// get instance of firestore and auth
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
// get user stream
|
||||
Stream<List<Map<String, dynamic>>> getUsersStream() {
|
||||
return _firestore.collection("Users").snapshots().map((snapshot) {
|
||||
return snapshot.docs.map((doc) {
|
||||
// iterate each user
|
||||
final user = doc.data();
|
||||
|
||||
//return user
|
||||
return user;
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
// send message
|
||||
Future<void> sendMessage(String receiverID, message) async {
|
||||
// get current user info
|
||||
final String currentUserID = _auth.currentUser!.uid;
|
||||
final String currentUserEmail = _auth.currentUser!.email!;
|
||||
final Timestamp timestamp = Timestamp.now();
|
||||
|
||||
// create new message
|
||||
Message newMessage = Message(
|
||||
senderID: currentUserID,
|
||||
senderEmail: currentUserEmail,
|
||||
receiverID: receiverID,
|
||||
message: message,
|
||||
timestamp: timestamp,
|
||||
);
|
||||
|
||||
// construct chat room ID for the two users (sorted to ensure uniqueness)
|
||||
List<String> ids = [currentUserID, receiverID];
|
||||
ids.sort(); // sort to ensure the chatroomID is the same for any 2 users
|
||||
String chatRoomID = ids.join('_');
|
||||
|
||||
// add new message to database
|
||||
await _firestore
|
||||
.collection("chat_rooms")
|
||||
.doc(chatRoomID)
|
||||
.collection("messages")
|
||||
.add(newMessage.toMap());
|
||||
}
|
||||
|
||||
// get messages
|
||||
Stream<QuerySnapshot> getMessages(String userID, otherUserID) {
|
||||
// TODO create chat room ID -- same code snippet as above
|
||||
// construct chat room ID for the two users (sorted to ensure uniqueness)
|
||||
List<String> ids = [userID, otherUserID];
|
||||
ids.sort(); // sort to ensure the chatroomID is the same for any 2 users
|
||||
String chatRoomID = ids.join('_');
|
||||
|
||||
return _firestore
|
||||
.collection("chat_rooms")
|
||||
.doc(chatRoomID)
|
||||
.collection("messages")
|
||||
.orderBy("timestamp", descending: false)
|
||||
.snapshots();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:cofounderella/themes/dark_mode.dart';
|
||||
import 'package:cofounderella/themes/light_mode.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThemeProvider extends ChangeNotifier {
|
||||
ThemeData _themeData = lightMode;
|
||||
|
||||
ThemeData get themeData => _themeData;
|
||||
|
||||
bool get isDarkMode => _themeData == darkMode;
|
||||
set themeData(ThemeData themeData) {
|
||||
_themeData = themeData;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void toggleTheme() {
|
||||
if(_themeData == lightMode) {
|
||||
themeData = darkMode;
|
||||
} else {
|
||||
themeData = lightMode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,8 @@ dependencies:
|
|||
cupertino_icons: ^1.0.6
|
||||
firebase_core: ^2.30.1
|
||||
firebase_auth: ^4.19.4
|
||||
cloud_firestore: ^4.17.2
|
||||
provider: ^6.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue