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:cofounderella/pages/settings_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,16 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MyTextField extends StatelessWidget {
|
class MyTextField extends StatelessWidget {
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final bool hideText;
|
final bool obscureText;
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
|
||||||
const MyTextField({
|
const MyTextField({
|
||||||
super.key,
|
super.key,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
required this.hideText,
|
required this.obscureText,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
|
this.focusNode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,8 +19,9 @@ class MyTextField extends StatelessWidget {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(25.0),
|
padding: const EdgeInsets.all(25.0),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
obscureText: hideText,
|
obscureText: obscureText,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide:
|
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/services/auth/auth_gate.dart';
|
||||||
import 'package:cofounderella/themes/light_mode.dart';
|
import 'package:cofounderella/themes/theme_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// Firebase stuff
|
// Firebase stuff
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
@ -12,7 +12,12 @@ void main() async {
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
// Standard stuff
|
// Standard stuff
|
||||||
runApp(const MyApp());
|
runApp(
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => ThemeProvider(),
|
||||||
|
child: const MyApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
@ -24,10 +29,8 @@ class MyApp extends StatelessWidget {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Flutter Demo', // TODO change title
|
title: 'Flutter Demo', // TODO change title
|
||||||
theme: lightMode,
|
theme: Provider.of<ThemeProvider>(context).themeData,
|
||||||
home: const AuthGate(),
|
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/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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
import 'chat_page.dart';
|
||||||
const MyHomePage({super.key, required this.title});
|
|
||||||
|
|
||||||
// This widget is the home page of your application. It is stateful, meaning
|
class HomePage extends StatelessWidget {
|
||||||
// that it has a State object (defined below) that contains fields that affect
|
HomePage({super.key});
|
||||||
// how it looks.
|
|
||||||
|
|
||||||
// This class is the configuration for the state. It holds the values (in this
|
// chat and auth service
|
||||||
// case the title) provided by the parent (in this case the App widget) and
|
final ChatService _chatService = ChatService();
|
||||||
// used by the build method of the State. Fields in a Widget subclass are
|
final AuthService _authService = AuthService();
|
||||||
// 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++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
// TRY THIS: Try changing the color here to a specific color (to
|
title: const Text("Home"),
|
||||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
// TODO appbar style: remove background and set elevation to 0 ?
|
||||||
// change color while the other colors stay the same.
|
backgroundColor: Colors.transparent,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
foregroundColor: Colors.grey.shade800,
|
||||||
// Here we take the value from the MyHomePage object that was created by
|
elevation: 0,
|
||||||
// the App.build method, and use it to set our appbar title.
|
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
),
|
||||||
drawer: const MyDrawer(),
|
drawer: const MyDrawer(),
|
||||||
body: Center(
|
body: _buildUserList(),
|
||||||
// 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.
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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:flutter/material.dart';
|
||||||
import 'package:cofounderella/components/my_button.dart';
|
import 'package:cofounderella/components/my_button.dart';
|
||||||
import 'package:cofounderella/components/my_textfield.dart';
|
import 'package:cofounderella/components/my_textfield.dart';
|
||||||
|
@ -63,7 +63,7 @@ class LoginPage extends StatelessWidget {
|
||||||
// email textfield
|
// email textfield
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "E-Mail",
|
hintText: "E-Mail",
|
||||||
hideText: false,
|
obscureText: false,
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class LoginPage extends StatelessWidget {
|
||||||
// password textfield
|
// password textfield
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "Password",
|
hintText: "Password",
|
||||||
hideText: true,
|
obscureText: true,
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class LoginPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Text(
|
child: const Text(
|
||||||
"Register now",
|
"Register now",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
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 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../components/my_button.dart';
|
import '../components/my_button.dart';
|
||||||
|
@ -20,12 +20,12 @@ class RegisterPage extends StatelessWidget {
|
||||||
// register method
|
// register method
|
||||||
void register(BuildContext context) {
|
void register(BuildContext context) {
|
||||||
// get auth service
|
// get auth service
|
||||||
final _auth = AuthService();
|
final auth = AuthService();
|
||||||
|
|
||||||
// check if passwords match
|
// check if passwords match
|
||||||
if (_passwordController.text == _confirmPassController.text) {
|
if (_passwordController.text == _confirmPassController.text) {
|
||||||
try {
|
try {
|
||||||
_auth.signUpWithEmailPassword(
|
auth.signUpWithEmailPassword(
|
||||||
_emailController.text,
|
_emailController.text,
|
||||||
_passwordController.text,
|
_passwordController.text,
|
||||||
);
|
);
|
||||||
|
@ -76,19 +76,19 @@ class RegisterPage extends StatelessWidget {
|
||||||
// name text field
|
// name text field
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "First Name",
|
hintText: "First Name",
|
||||||
hideText: false,
|
obscureText: false,
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
),
|
),
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "Last Name",
|
hintText: "Last Name",
|
||||||
hideText: false,
|
obscureText: false,
|
||||||
controller: _lastnameController,
|
controller: _lastnameController,
|
||||||
),
|
),
|
||||||
|
|
||||||
// email text field
|
// email text field
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "E-Mail",
|
hintText: "E-Mail",
|
||||||
hideText: false,
|
obscureText: false,
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -97,13 +97,13 @@ class RegisterPage extends StatelessWidget {
|
||||||
// password text field
|
// password text field
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "Password",
|
hintText: "Password",
|
||||||
hideText: true,
|
obscureText: true,
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
),
|
),
|
||||||
|
|
||||||
MyTextField(
|
MyTextField(
|
||||||
hintText: "Confirm Password",
|
hintText: "Confirm Password",
|
||||||
hideText: true,
|
obscureText: true,
|
||||||
controller: _confirmPassController,
|
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:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
class SettingsPage extends StatelessWidget {
|
||||||
const SettingsPage({super.key});
|
const SettingsPage({super.key});
|
||||||
|
@ -6,9 +9,32 @@ class SettingsPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Settings"),
|
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:flutter/material.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.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';
|
import 'package:cofounderella/pages/home_page.dart';
|
||||||
|
|
||||||
class AuthGate extends StatelessWidget{
|
class AuthGate extends StatelessWidget{
|
||||||
|
@ -15,13 +15,11 @@ class AuthGate extends StatelessWidget{
|
||||||
|
|
||||||
// check if user is logged in or not
|
// check if user is logged in or not
|
||||||
if(snapshot.hasData){
|
if(snapshot.hasData){
|
||||||
return const MyHomePage(title: "MyHomePage Test Title",);
|
return HomePage();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return const LoginOrRegister();
|
return const LoginOrRegister();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
|
@ -1,8 +1,15 @@
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
// instance of auth
|
// instance of auth and firestore
|
||||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
// get current user
|
||||||
|
User? getCurrentUser() {
|
||||||
|
return _auth.currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
//sign in
|
//sign in
|
||||||
Future<UserCredential> signInWithEmailPassword(String email, password) async {
|
Future<UserCredential> signInWithEmailPassword(String email, password) async {
|
||||||
|
@ -11,6 +18,16 @@ class AuthService {
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
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;
|
return userCredential;
|
||||||
} on FirebaseAuthException catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
throw Exception(e.code);
|
throw Exception(e.code);
|
||||||
|
@ -20,11 +37,21 @@ class AuthService {
|
||||||
// sign up (register)
|
// sign up (register)
|
||||||
Future<UserCredential> signUpWithEmailPassword(String email, password) async {
|
Future<UserCredential> signUpWithEmailPassword(String email, password) async {
|
||||||
try {
|
try {
|
||||||
|
// create user
|
||||||
UserCredential userCredential =
|
UserCredential userCredential =
|
||||||
await _auth.createUserWithEmailAndPassword(
|
await _auth.createUserWithEmailAndPassword(
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// save user info in a document
|
||||||
|
_firestore.collection("Users").doc(userCredential.user!.uid).set(
|
||||||
|
{
|
||||||
|
'uid': userCredential.user!.uid,
|
||||||
|
'email': email,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return userCredential;
|
return userCredential;
|
||||||
} on FirebaseAuthException catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
throw Exception(e.code);
|
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
|
cupertino_icons: ^1.0.6
|
||||||
firebase_core: ^2.30.1
|
firebase_core: ^2.30.1
|
||||||
firebase_auth: ^4.19.4
|
firebase_auth: ^4.19.4
|
||||||
|
cloud_firestore: ^4.17.2
|
||||||
|
provider: ^6.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue