Login Screen
Clean email + password login with form validation and loading state
View component →A complete e-learning home screen built in Flutter — featuring a personalized greeting header, search bar, gradient promotional banner, horizontally scrollable mentor avatars, category filter chips, and course cards with pricing, ratings and student counts. Drops straight into any online learning, course-marketplace or skill-building app.
WHAT'S INCLUDED
USE CASES
PRO TIP
Wrap the entire scrollable area in a CustomScrollView with SliverList sections so the mentors row, categories and course list stay performant once you load real data. Also extract each course card into its own widget — it will be reused on the search results, "see all" and bookmarks screens, so building it once saves you from three near-duplicate widgets.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
class CourseDetailScreen extends StatefulWidget {
const CourseDetailScreen({super.key});
@override
State<CourseDetailScreen> createState() => _CourseDetailScreenState();
}
class _CourseDetailScreenState extends State<CourseDetailScreen> {
static const Color _primaryBlue = Color(0xFF335EF7);
static const Color _bg = Color(0xFFF5F7FB);
static const Color _textDark = Color(0xFF111827);
static const Color _textMuted = Color(0xFF6B7280);
final TextEditingController _searchController = TextEditingController();
String _selectedCategory = 'All';
int _selectedNavIndex = 0;
final List<_Mentor> _mentors = const [
_Mentor(name: 'Jacob', color: Color(0xFFFFD6C0)),
_Mentor(name: 'Claire', color: Color(0xFFD0F0FF)),
_Mentor(name: 'Priscilla', color: Color(0xFFFFE5F0)),
_Mentor(name: 'Wade', color: Color(0xFFE0E7FF)),
_Mentor(name: 'Kathry', color: Color(0xFFFEF3C7)),
];
final List<_CategoryChip> _categories = const [
_CategoryChip(label: 'All', icon: Icons.local_fire_department),
_CategoryChip(label: '3D Design', icon: Icons.view_in_ar),
_CategoryChip(label: 'Business', icon: Icons.business_center),
_CategoryChip(label: 'Entrepreneurship', icon: Icons.lightbulb_outline),
_CategoryChip(label: 'UI/UX Design', icon: Icons.design_services),
];
final List<_Course> _courses = const [
_Course(
tag: '3D Design',
title: '3D Design Illustration',
price: 48,
originalPrice: 80,
rating: 4.8,
students: '8,289',
coverColor: Color(0xFF1E3A8A),
),
_Course(
tag: 'Entrepreneurship',
title: 'Digital Entrepreneur...',
price: 39,
originalPrice: null,
rating: 4.9,
students: '6,182',
coverColor: Color(0xFF92400E),
),
_Course(
tag: 'UI/UX Design',
title: 'Learn UX User Persona',
price: 42,
originalPrice: 75,
rating: 4.7,
students: '7,938',
coverColor: Color(0xFFFBBF24),
),
];
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
// TODO: Replace with your actual API/Firestore call
void _onCoursePressed(_Course course) {}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bg,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: 16),
_buildSearchBar(),
const SizedBox(height: 20),
_buildPromoBanner(),
const SizedBox(height: 22),
_buildSectionHeader('Top Mentors', 'See All'),
const SizedBox(height: 14),
_buildMentorsRow(),
const SizedBox(height: 22),
_buildSectionHeader('Most Popular Courses', 'See All'),
const SizedBox(height: 14),
_buildCategoryChips(),
const SizedBox(height: 16),
..._courses.map(_buildCourseCard).toList(),
const SizedBox(height: 24),
],
),
),
),
bottomNavigationBar: _buildBottomNav(),
);
}
Widget _buildHeader() {
return Row(
children: [
const CircleAvatar(
radius: 22,
backgroundColor: Color(0xFFE5E7EB),
child: Icon(Icons.person, color: _textMuted),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'Good Morning 👋',
style: TextStyle(fontSize: 13, color: _textMuted),
),
SizedBox(height: 2),
Text(
'Andrew Ainsley',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: _textDark,
),
),
],
),
),
const Icon(Icons.notifications_none, color: _textDark),
const SizedBox(width: 12),
const Icon(Icons.bookmark_border, color: _textDark),
],
);
}
Widget _buildSearchBar() {
return Container(
height: 52,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: const Color(0xFFEEF1F6),
borderRadius: BorderRadius.circular(14),
),
child: Row(
children: [
const Icon(Icons.search, color: _textMuted, size: 22),
const SizedBox(width: 10),
Expanded(
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: 'Search',
hintStyle: TextStyle(color: _textMuted, fontSize: 15),
border: InputBorder.none,
isCollapsed: true,
),
),
),
Container(width: 1, height: 22, color: const Color(0xFFD1D5DB)),
const SizedBox(width: 12),
const Icon(Icons.tune, color: _primaryBlue, size: 22),
],
),
);
}
Widget _buildPromoBanner() {
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 18, 20, 18),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF4A6CF7), Color(0xFF335EF7)],
),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.18),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'40% OFF',
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(height: 10),
const Text(
"Today's Special",
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
const SizedBox(
width: 200,
child: Text(
'Get a discount for every course order!\nOnly valid for today!',
style: TextStyle(
color: Colors.white,
fontSize: 12,
height: 1.4,
),
),
),
const SizedBox(height: 12),
Row(children: [
_buildDot(true),
const SizedBox(width: 6),
_buildDot(false),
const SizedBox(width: 6),
_buildDot(false),
]),
],
),
Positioned(
right: 0,
top: 0,
child: Text(
'40%',
style: TextStyle(
color: Colors.white.withOpacity(0.95),
fontSize: 44,
fontWeight: FontWeight.w800,
height: 1,
),
),
),
],
),
);
}
Widget _buildDot(bool active) {
return Container(
width: active ? 18 : 6,
height: 6,
decoration: BoxDecoration(
color: Colors.white.withOpacity(active ? 1 : 0.45),
borderRadius: BorderRadius.circular(4),
),
);
}
Widget _buildSectionHeader(String title, String action) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w700,
color: _textDark,
),
),
Text(
action,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: _primaryBlue,
),
),
],
);
}
Widget _buildMentorsRow() {
return SizedBox(
height: 84,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _mentors.length,
separatorBuilder: (_, __) => const SizedBox(width: 18),
itemBuilder: (_, index) {
final mentor = _mentors[index];
return Column(
children: [
CircleAvatar(
radius: 28,
backgroundColor: mentor.color,
child: Text(
mentor.name.substring(0, 1),
style: const TextStyle(
color: _textDark,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(height: 8),
Text(
mentor.name,
style: const TextStyle(fontSize: 12, color: _textDark),
),
],
);
},
),
);
}
Widget _buildCategoryChips() {
return SizedBox(
height: 38,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _categories.length,
separatorBuilder: (_, __) => const SizedBox(width: 10),
itemBuilder: (_, index) {
final cat = _categories[index];
final selected = cat.label == _selectedCategory;
return GestureDetector(
onTap: () => setState(() => _selectedCategory = cat.label),
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: selected ? _primaryBlue : Colors.white,
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: selected ? _primaryBlue : _primaryBlue,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
cat.icon,
size: 16,
color: selected ? Colors.white : _primaryBlue,
),
const SizedBox(width: 6),
Text(
cat.label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: selected ? Colors.white : _primaryBlue,
),
),
],
),
),
);
},
),
);
}
Widget _buildCourseCard(_Course course) {
return Container(
margin: const EdgeInsets.only(bottom: 14),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _onCoursePressed(course),
borderRadius: BorderRadius.circular(12),
child: Row(
children: [
Container(
width: 86,
height: 86,
decoration: BoxDecoration(
color: course.coverColor,
borderRadius: BorderRadius.circular(12),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
course.tag,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: _primaryBlue,
),
),
const Icon(
Icons.bookmark_border,
size: 18,
color: _primaryBlue,
),
],
),
const SizedBox(height: 4),
Text(
course.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: _textDark,
),
),
const SizedBox(height: 6),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'\$${course.price}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: _primaryBlue,
),
),
if (course.originalPrice != null) ...[
const SizedBox(width: 6),
Text(
'\$${course.originalPrice}',
style: const TextStyle(
fontSize: 12,
color: _textMuted,
decoration: TextDecoration.lineThrough,
),
),
],
],
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.star, size: 14, color: Color(0xFFF59E0B)),
const SizedBox(width: 3),
Text(
course.rating.toString(),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _textDark,
),
),
const SizedBox(width: 8),
Container(
width: 3,
height: 3,
decoration: const BoxDecoration(
color: _textMuted,
shape: BoxShape.circle,
)),
const SizedBox(width: 8),
Text(
'${course.students} students',
style: const TextStyle(
fontSize: 12,
color: _textMuted,
),
),
],
),
],
),
),
],
),
),
),
);
}
Widget _buildBottomNav() {
const items = [
_NavItem(label: 'Home', icon: Icons.home_outlined),
_NavItem(label: 'My Course', icon: Icons.menu_book_outlined),
_NavItem(label: 'Inbox', icon: Icons.chat_bubble_outline),
_NavItem(label: 'Transact.', icon: Icons.swap_horiz),
_NavItem(label: 'Profile', icon: Icons.person_outline),
];
return Container(
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color(0x14000000),
blurRadius: 16,
offset: Offset(0, -2),
),
],
),
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(items.length, (index) {
final item = items[index];
final selected = index == _selectedNavIndex;
return GestureDetector(
onTap: () => setState(() => _selectedNavIndex = index),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
item.icon,
size: 22,
color: selected ? _primaryBlue : _textMuted,
),
const SizedBox(height: 4),
Text(
item.label,
style: TextStyle(
fontSize: 11,
fontWeight:
selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? _primaryBlue : _textMuted,
),
),
],
),
);
}),
),
),
),
);
}
}
class _Mentor {
final String name;
final Color color;
const _Mentor({required this.name, required this.color});
}
class _CategoryChip {
final String label;
final IconData icon;
const _CategoryChip({required this.label, required this.icon});
}
class _Course {
final String tag;
final String title;
final int price;
final int? originalPrice;
final double rating;
final String students;
final Color coverColor;
const _Course({
required this.tag,
required this.title,
required this.price,
required this.originalPrice,
required this.rating,
required this.students,
required this.coverColor,
});
}
class _NavItem {
final String label;
final IconData icon;
const _NavItem({required this.label, required this.icon});
}
Clean email + password login with form validation and loading state
View component →Registration screen with role selection grid — Owner, Manager, or Employee
View component →Animated splash with logo fade-in and auto-navigate after delay
View component →