I'm trying to make a task app with Flutter and Firebase. I'm not the best for designing so I took a design from Dribbble that i'm trying to copy for my app.
Here is the link of the design. Dribbble link
Here is the page I'm trying to copy : Page screenshot
Here is the code I did at the moment : (it's actually not working)
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final String userID = FirebaseAuth.instance.currentUser!.uid;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 25.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 25),
child: Align(
alignment: Alignment.centerLeft,
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return const Text("Erreur");
}
return Text.rich(
TextSpan(
text: "Bonjour,\n",
style: GoogleFonts.exo2(
fontSize: 40,
height: 1.25,
),
children: [
TextSpan(
text: snapshot.data!.data()!["name"],
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
);
},
),
),
),
Row(
children: [
// Column de gauche
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: ListView(
children: const [],
),
),
// Column de droite
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Column(
children: [
Container(),
],
),
),
],
),
],
),
),
),
);
}
}
I don't really know how to do the same layout cause I have to split the screen in 2 parts and there is ListView.builder (in my code there is only a ListView) who needs a fixed size.
Edit :
Here is the looking of my app now (cause I wasn't able to find a solution by my own)
Here is the full code of the page (don't mind of the Firebase code) :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:db_todoapp/components/my_task_tile.dart';
import 'package:db_todoapp/pages/account_page.dart';
import 'package:db_todoapp/utilities/task_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shimmer/shimmer.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final String userID = FirebaseAuth.instance.currentUser!.uid;
final TaskService _ts = TaskService();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 25.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Message de bienvenue
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Shimmer.fromColors(
baseColor: const Color.fromRGBO(224, 224, 224, 1),
highlightColor: Colors.grey,
child: Text.rich(
TextSpan(
text: "Bonjour,\n",
style: GoogleFonts.exo2(
fontSize: 40,
height: 1.25,
),
children: const [
TextSpan(
text: "Nom",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
if (snapshot.hasError) {
return const Text("Erreur");
}
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountPage()));
},
child: Text.rich(
TextSpan(
text: "Bonjour,\n",
style: GoogleFonts.exo2(
fontSize: 40,
height: 1.25,
),
children: [
TextSpan(
text: snapshot.data!.data()!["name"],
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
},
),
// Bouton pour ajouter une tâche
GestureDetector(
onTap: () {
_ts.addTask(context);
},
child: Container(
padding: const EdgeInsets.all(25.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
bottom: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
left: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
),
borderRadius: BorderRadius.horizontal(
left: Radius.circular(18.0),
),
),
child: Column(
children: [
// Icon +
const Icon(
Icons.add,
size: 30,
),
// Texte
Text(
"Ajouter une tâche",
style: GoogleFonts.exo2(
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
)
],
),
),
const Padding(
padding: EdgeInsets.all(25.0),
child: Divider(),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.collection("Tasks")
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Shimmer.fromColors(
baseColor: const Color.fromRGBO(224, 224, 224, 1),
highlightColor: Colors.grey,
child: GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: null,
itemBuilder: (context, index) {
return MyTaskTile(
taskTitle: "Task Title",
taskIndex: index,
isTaskDone: false,
taskTheme: null,
);
},
),
);
}
if (snapshot.data!.docs.isEmpty ||
snapshot.data == null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Illustration
Image.asset("assets/no_task.png"),
// Texte
Text(
"Vous n'avez aucune tâche.",
style: GoogleFonts.exo2(
fontSize: 30,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
],
),
);
}
return GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: snapshot.data?.docs.length ?? 0,
itemBuilder: (context, index) {
return MyTaskTile(
taskTitle: snapshot.data!.docs[index]["title"],
taskIndex: index,
isTaskDone: snapshot.data!.docs[index]["state"],
taskTheme: snapshot.data!.docs[index]["theme"],
);
},
);
},
),
),
)
],
),
),
),
);
}
}
How can I replace my GridView.builder and change some of the placement of other widget to get the looking that I want ?
If someone knows how i can copy the same page or just help me doing it, it would be very nice !
I would like to share the code that helped me solve my problem:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:db_todoapp/components/my_task_tile.dart';
import 'package:db_todoapp/pages/account_page.dart';
import 'package:db_todoapp/utilities/task_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shimmer/shimmer.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final String userID = FirebaseAuth.instance.currentUser!.uid;
final TaskService _ts = TaskService();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 25.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Message de bienvenue
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Shimmer.fromColors(
baseColor: const Color.fromRGBO(224, 224, 224, 1),
highlightColor: Colors.grey,
child: Text.rich(
TextSpan(
text: "Bonjour,\n",
style: GoogleFonts.exo2(
fontSize: 40,
height: 1.25,
),
children: const [
TextSpan(
text: "Nom",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
if (snapshot.hasError) {
return const Text("Erreur");
}
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountPage()));
},
child: Text.rich(
TextSpan(
text: "Bonjour,\n",
style: GoogleFonts.exo2(
fontSize: 40,
height: 1.25,
),
children: [
TextSpan(
text: snapshot.data!.data()!["name"],
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
},
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.collection("Tasks")
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const SizedBox();
}
if (snapshot.data == null ||
snapshot.data!.docs.isEmpty) {
return GestureDetector(
onTap: () {
_ts.addTask(context);
},
child: Container(
padding: const EdgeInsets.all(25.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
bottom: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
left: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
),
borderRadius: BorderRadius.horizontal(
left: Radius.circular(18.0),
),
),
child: Column(
children: [
// Icon +
const Icon(
Icons.add,
size: 30,
),
// Texte
Text(
"Ajouter une tâche",
style: GoogleFonts.exo2(
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
return const SizedBox();
},
),
],
),
),
const Padding(
padding: EdgeInsets.all(25.0),
child: Divider(),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(userID)
.collection("Tasks")
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {}
if (snapshot.data == null ||
snapshot.data!.docs.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Illustration
Image.asset("assets/no_task.png"),
// Texte
Text(
"Vous n'avez aucune tâche.",
style: GoogleFonts.exo2(
fontSize: 30,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
],
),
);
}
return MasonryGridView.count(
itemCount: snapshot.data!.docs.length + 1,
crossAxisCount: 2,
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.only(right: 10.0),
child: MyTaskTile(
taskTitle: snapshot.data!.docs[index]["title"],
taskIndex: index,
isTaskDone: snapshot.data!.docs[index]["state"],
taskTheme: snapshot.data!.docs[index]["theme"],
),
);
}
if (index == 1) {
return Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: GestureDetector(
onTap: () {
_ts.addTask(context);
},
child: Container(
padding: const EdgeInsets.all(25.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
bottom: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
left: BorderSide(
width: 1.5,
color: Color.fromRGBO(189, 189, 189, 1),
),
),
borderRadius: BorderRadius.horizontal(
left: Radius.circular(18.0),
),
),
child: Column(
children: [
// Icon +
const Icon(
Icons.add,
size: 30,
),
// Texte
Text(
"Ajouter une tâche",
style: GoogleFonts.exo2(
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
);
}
return Padding(
padding: const EdgeInsets.only(right: 10.0),
child: MyTaskTile(
taskTitle: snapshot.data!.docs[index - 1]
["title"],
taskIndex: index - 1,
isTaskDone: snapshot.data!.docs[index - 1]
["state"],
taskTheme: snapshot.data!.docs[index - 1]
["theme"],
),
);
},
);
},
),
),
)
],
),
),
),
);
}
}
The Firebase code is not important; the layout is what matters most.
Thanks to everyone who helped me with comments/answers, and thank you Ultranmus for taking the time to assist me!