Lab 6
by:
Muhammad Hassan Raza
(SP23-BCS-056)
Submitted to: Ma’am Qanetah Ahmed
Course: Mobile Application Development (CSC303)
Dated: October 8, 2025
DEPARTMENT OF COMPUTER SCIENCE
COMSATS UNIVERSITY ISLAMABAD,
ISLAMABAD CAMPUS
Project Structure
main.dart
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import
'package:test_app/screens/event_registration_screen.da
rt';
final lightTheme = ThemeData.light(useMaterial3:
true).copyWith(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 95, 227),
brightness: Brightness.light,
),
textTheme: GoogleFonts.montserratTextTheme(),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 0,
95, 227),
foregroundColor: Colors.white,
textStyle: GoogleFonts.montserrat(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
appBarTheme: const AppBarTheme().copyWith(
backgroundColor: const Color.fromARGB(255, 0, 95,
227),
foregroundColor: Colors.white,
elevation: 4,
),
);
final darkTheme = ThemeData.light(useMaterial3:
true).copyWith(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 58, 138),
brightness: Brightness.dark,
),
textTheme: GoogleFonts.montserratTextTheme(),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 0,
95, 227),
foregroundColor: Colors.white,
textStyle: GoogleFonts.montserrat(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
);
void main() {
runApp(const LabSix());
}
class LabSix extends StatelessWidget {
const LabSix({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Event Registration App',
theme: lightTheme,
darkTheme: darkTheme,
home: const EventRegistrationScreen(),
debugShowCheckedModeBanner: false,
);
}
}
/utils/validators/input_validator.dart
import 'package:audioplayers/audioplayers.dart';
class AudioPlayerUtil {
static final AudioPlayer _audioPlayer =
AudioPlayer();
static Future<void> playSound(String path) async {
await _audioPlayer.play(AssetSource(path));
}
// Since it's a singleton, use the dispose method
carefully
static Future<void> dispose() async {
await _audioPlayer.dispose();
}
}import 'package:test_app/data/local/form_data.dart';
class InputValidator {
static String? validate(String? value, FieldType
fieldType) {
switch (fieldType) {
case FieldType.organizerName:
return _validateOrganizerName(value);
case FieldType.email:
return _validateEmail(value);
case FieldType.attendeesCount:
return _validateAttendeesCount(value);
case FieldType.teamMemId:
return _validateTeamMemberId(value);
}
}
static String? _validateOrganizerName(String? value)
{
if (value == null || value.trim().isEmpty) {
return 'Organizer Name is required';
}
value = value.trim();
if (value.length < 2) {
return 'Organizer Name must be at least 2
characters long';
}
if (!RegExp(r'^[A-Za-z_]+$').hasMatch(value)) {
return 'Organizer Name must contain only English
letters and underscores.';
}
return null;
}
static String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (value.contains(' ')) {
return 'Email cannot contain spaces';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w]{2,4}$').hasMatch(value)) {
return 'Enter a valid email address';
}
return null;
}
static String? _validateAttendeesCount(String?
value) {
if (value == null || value.isEmpty) {
return 'Number of attendees is required to
continue registration process';
}
final int? count = int.tryParse(value);
if (count == null || count <= 0) {
return 'Please enter a valid number';
}
if (count > 300) {
return 'Number of attendees cannot exceed 300';
}
return null;
}
static String? _validateTeamMemberId(String? value)
{
if
(FormData.teamMemberIds.contains(value?.trim())) {
return 'This ID is already added';
} else {
return null;
}
}
}
/data/local/form_data.dart
import 'package:test_app/data/models/venue.dart';
enum FieldType { email, organizerName, attendeesCount,
teamMemId }
class FormData {
static List<String> teamMemberIds = [];
static final List<String> eventTypes = const [
'Conference',
'Workshop',
'Seminar',
'Meetup',
];
static final List<Venue> venues = const [
Venue(name: 'Auditorium', value: 'auditorium',
capacity: 150),
Venue(name: 'CS Hall', value: 'cs_hall', capacity:
200),
Venue(name: 'Outdoor Stage', value:
'outdoor_stage', capacity: 300),
Venue(name: 'Conference Room', value:
'conference_room', capacity: 80),
];
}
/data/models/venue.dart
class Venue {
final String name;
final String value;
final int capacity;
const Venue({required this.name, required
this.value, required this.capacity});
}
/widgets/color_change_widget.dart
import 'dart:math';
import 'package:flutter/material.dart';
class ColorChangeWidget extends StatefulWidget {
const ColorChangeWidget({super.key});
@override
State<ColorChangeWidget> createState() =>
_ColorChangeWidgetState();
}
class _ColorChangeWidgetState extends
State<ColorChangeWidget> {
Color _backgroundColor = Colors.blue;
final Random _random = Random();
void _changeColor() {
final newColor = _getRandomColor();
setState(() {
_backgroundColor = newColor;
});
}
Color _getRandomColor() {
return Color.fromRGBO(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
1.0,
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _changeColor(),
child: Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
margin: const EdgeInsets.symmetric(horizontal:
24, vertical: 32),
color: _backgroundColor,
child: const Center(
child: Text(
'Tap on the card to change its color.',
style: TextStyle(
fontSize: 18,
fontFamily: 'Courier New',
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
),
);
}
}
/widgets/xylophone_tile.dart
import 'package:flutter/material.dart';
import '../utils/audio_player.dart';
@immutable
class XylophoneTile extends StatelessWidget {
final Color color;
final String audioFilePath;
final double width;
final String noteName;
const XylophoneTile({
super.key,
required this.color,
required this.audioFilePath,
required this.width,
required this.noteName,
});
void _playSound() async {
await AudioPlayerUtil.playSound(audioFilePath);
}
@override
Widget build(BuildContext context) {
return InkWell(
splashColor: color.withValues(alpha: 0.5),
highlightColor: color.withValues(alpha: 0.5),
onTap: _playSound,
child: Container(
width: width,
height: 56,
decoration: BoxDecoration(
color: color,
border: Border.all(width: 1.5, color:
Colors.black),
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.symmetric(horizontal:
2.0, vertical: 2.0),
child: Center(
child: Text(
noteName,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
}
}
/screens/event_registration_screen.dart
import 'package:flutter/material.dart';
import 'package:test_app/data/local/form_data.dart';
import 'package:test_app/data/models/venue.dart';
import
'package:test_app/widgets/custom_outline_button.dart';
import
'package:test_app/widgets/custom_text_field.dart';
import
'package:test_app/widgets/drop_down_button.dart';
import
'package:test_app/widgets/form_radio_button.dart';
import 'package:test_app/widgets/info_text.dart';
import
'package:test_app/widgets/team_member_tile.dart';
class EventRegistrationScreen extends StatefulWidget {
const EventRegistrationScreen({super.key});
@override
State<EventRegistrationScreen> createState() =>
_EventRegistrationScreenState();
}
class _EventRegistrationScreenState extends
State<EventRegistrationScreen> {
final _formKey = GlobalKey<FormState>();
final _orgNameController = TextEditingController();
final _orgEmailController = TextEditingController();
final _attendeesCountController =
TextEditingController();
final _teamMemIdController =
TextEditingController();
String? _selectedEventType;
Venue? _selectedVenue;
int? get _attendeesCount =>
int.tryParse(_attendeesCountController.text);
@override
void initState() {
super.initState();
_attendeesCountController.addListener(() {
setState(() {});
});
}
@override
void dispose() {
_orgEmailController.dispose();
_orgNameController.dispose();
_attendeesCountController.dispose();
_teamMemIdController.dispose();
super.dispose();
}
void _showSnackBar(String message, Color color) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: Duration(seconds: 3),
backgroundColor: color,
behavior: SnackBarBehavior.floating,
),
);
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
_showSnackBar('Signup Successful!',
Colors.green);
} else {
_showSnackBar(
'Please fill the form correctly.',
Theme.of(context).colorScheme.error,
);
}
}
@override
Widget build(BuildContext context) {
debugPrint('Building EventRegistrationScreen');
double screenHeight =
MediaQuery.of(context).size.height;
return Scaffold(
body: SafeArea(
child: Padding(
padding: const
EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Column(
children: [
SizedBox(height: 24),
Text(
'Event Registration',
style:
Theme.of(context).textTheme.headlineMedium!.copyWith(
fontWeight: FontWeight.bold,
color:
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 16),
Expanded(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
'Oraganizer Details:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color:
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 12),
CustomTextField(
controller:
_orgNameController,
labelText: 'Organizer Name',
fieldType:
FieldType.organizerName,
keyboardType:
TextInputType.text,
icon: Icons.person,
),
const SizedBox(height: 12),
CustomTextField(
controller:
_orgEmailController,
labelText: 'Organizer
Email',
fieldType: FieldType.email,
keyboardType:
TextInputType.emailAddress,
icon: Icons.email_rounded,
),
const SizedBox(height: 8),
Text(
'Select Event Type:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color:
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
LayoutBuilder(
builder: (context,
constraints) {
debugPrint(constraints.max
Width.toString());
debugPrint(constraints.max
Height.toString());
final spacing = 8.0;
final itemWidth =
(constraints.maxWidth
- spacing) / 2;
return Wrap(
clipBehavior:
Clip.hardEdge,
spacing: spacing,
runSpacing: spacing,
children:
FormData.eventTypes
.map(
(eventType) =>
FormRadioButton(
width:
itemWidth,
buttonValue:
eventType,
onChanged:
(value) => setState(
() =>
_selectedEventType = value,
),
groupValue:
_selectedEventType,
title:
eventType,
),
)
.toList(),
);
},
),
const SizedBox(height: 6),
Text(
'Attendees Count:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color:
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 12),
CustomTextField(
controller:
_attendeesCountController,
labelText: 'Enter Number of
Attendees',
fieldType:
FieldType.attendeesCount,
keyboardType:
TextInputType.number,
icon: Icons.people,
),
const SizedBox(height: 12),
if (_attendeesCount == null)
InfoText(
color:
Theme.of(context).colorScheme.primary,
text:
'Enter a valid number
of attendees to proceed with venue selection.',
),
// Show venue dropdown only if
attendees > 20 and <= 300
if
(_attendeesCountController.text.isNotEmpty &&
_attendeesCount != null &&
_attendeesCount! > 20 &&
_attendeesCount! <= 300)
...[
Text(
'Select Venue:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color:
Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
FormDropDown(
labelText: 'Choose a
Venue',
selectedValue:
_selectedVenue,
onChanged: (venue) =>
setState(() =>
_selectedVenue = venue),
),
const SizedBox(height: 12),
if (_selectedVenue == null)
InfoText(
text:
'Select a venue to
proceed with venue selection.',
color:
Theme.of(context).colorScheme.primary,
),
// Show the following only
if a venue is selected and it can accommodate the
attendees
if (_selectedVenue != null
&&
(_attendeesCount! <=
_selectedVenue!.capa
city)) ...[
Text(
'Add Team Members:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color: Theme.of(
context,
).colorScheme.prim
ary,
),
),
const SizedBox(height:
12),
// Team Member Email Input
and Add Button
SizedBox(
height: screenHeight /
6.3,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CustomTextField(
controller:
_teamMemIdController,
labelText: 'Enter
Team Member ID',
fieldType:
FieldType.teamMemId,
keyboardType:
TextInputType.text,
icon:
Icons.person_add_alt_1,
),
const
SizedBox(height: 6),
CustomOutlineButton(
onPressed: () {
setState(() {
if
(_formKey.currentState!.validate()) {
final String
teamMemId =
_teamMem
IdController.text.trim();
FormData.tea
mMemberIds.add(teamMemId);
_teamMemIdCo
ntroller.clear();
}
});
},
title: 'Add
Member',
icon: Icons.add,
),
],
),
),
// team members list
if
(FormData.teamMemberIds.isNotEmpty) ...[
Text(
'Team Members:',
style:
Theme.of(context).textTheme.titleLarge!
.copyWith(
fontWeight:
FontWeight.w600,
color: Theme.of(
context,
).colorScheme.pr
imary,
),
),
const SizedBox(height:
8),
Container(
height: screenHeight /
4.5,
decoration:
BoxDecoration(
border: Border.all(
color: Theme.of(
context,
).colorScheme.prim
ary,
),
borderRadius:
BorderRadius.circular(8),
),
child:
ListView.builder(
padding:
EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
itemCount:
FormData.teamMemberIds.length,
itemBuilder:
(context, index) {
return
TeamMemberTile(
memberIndex:
index,
email:
FormData.teamMemberIds[index],
onRemove: () {
setState(() {
FormData.tea
mMemberIds.removeAt(
index,
);
});
},
);
},
),
),
],
const SizedBox(height:
12),
Align(
alignment:
Alignment.center,
child: ElevatedButton(
onPressed:
_submitForm,
child: const
Text('Register'),
),
),
],
if (_selectedVenue != null
&&
(_attendeesCount! >
_selectedVenue!.capa
city)) ...[
const SizedBox(height: 8),
InfoText(
text:
'${_selectedVenue!.n
ame} cannot accommodate $_attendeesCount attendees.
Please choose a different venue or reduce the number
of attendees.',
color:
Theme.of(context).colorScheme.error,
),
],
],
if (_attendeesCount != null &&
_attendeesCount! <= 20)
InfoText(
text:
'Event cannot be
organized for 20 or fewer attendees. Please enter a
larger number.',
color:
Theme.of(context).colorScheme.error,
),
],
),
),
),
),
],
),
),
),
);
}
}
/widgets/custom_outline_button.dart
import 'package:flutter/material.dart';
class CustomOutlineButton extends StatelessWidget {
final VoidCallback? onPressed;
final String title;
final IconData icon;
const CustomOutlineButton({
super.key,
required this.onPressed,
required this.title,
required this.icon,
});
@override
Widget build(BuildContext context) {
return OutlinedButton.icon(
style: IconButton.styleFrom(
backgroundColor: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.1),
padding: EdgeInsets.symmetric(vertical: 12,
horizontal: 12),
alignment: Alignment.center,
visualDensity: VisualDensity.compact,
highlightColor: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.25),
),
icon: Icon(icon, size: 18, color:
Theme.of(context).colorScheme.primary),
label: Text(
title,
style: TextStyle(
color:
Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
onPressed: onPressed,
);
}
}
/widgets/custom_text_field.dart
import 'package:flutter/material.dart';
import 'package:test_app/data/local/form_data.dart';
import
'package:test_app/utils/validators/input_validator.dar
t';
class CustomTextField extends StatefulWidget {
final TextEditingController controller;
final String labelText;
final FieldType fieldType;
final TextInputType keyboardType;
final IconData icon;
const CustomTextField({
super.key,
required this.controller,
required this.labelText,
required this.fieldType,
required this.keyboardType,
required this.icon,
});
@override
State<CustomTextField> createState() =>
_CustomTextFieldState();
}
class _CustomTextFieldState extends
State<CustomTextField> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return TextFormField(
controller: widget.controller,
keyboardType: widget.keyboardType,
decoration: InputDecoration(
errorMaxLines: 2,
isDense: true,
prefixIcon: Icon(widget.icon, size: 22),
labelText: widget.labelText,
border: OutlineInputBorder(borderRadius:
BorderRadius.circular(12)),
contentPadding: const
EdgeInsets.symmetric(vertical: 4, horizontal: 8),
),
autovalidateMode: AutovalidateMode.onUnfocus,
validator: (value) {
return InputValidator.validate(value,
widget.fieldType);
},
);
}
}
/widgets/drop_down_button.dart
import 'package:flutter/material.dart';
import 'package:test_app/data/local/form_data.dart';
import 'package:test_app/data/models/venue.dart';
class FormDropDown extends StatelessWidget {
final String labelText;
final Venue? selectedValue;
final ValueChanged<Venue?> onChanged;
const FormDropDown({
super.key,
required this.labelText,
required this.selectedValue,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return DropdownButtonFormField<Venue>(
decoration: InputDecoration(
isDense: true,
labelText: labelText,
border: const OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(12)),
),
),
value: selectedValue,
items: FormData.venues.map((Venue venue) {
return DropdownMenuItem<Venue>(
value: venue,
child: Text('${venue.name} (Capacity:
${venue.capacity})'),
);
}).toList(),
onChanged: onChanged,
autovalidateMode: AutovalidateMode.onUnfocus,
validator: (value) {
if (value == null) {
return 'Please select a venue';
}
return null;
},
);
}
}
/widgets/form_radio_button.dart
import 'package:flutter/material.dart';
class FormRadioButton extends StatelessWidget {
final String buttonValue;
final String? groupValue;
final String title;
final double width;
final ValueChanged<String?> onChanged;
const FormRadioButton({
super.key,
required this.buttonValue,
required this.onChanged,
required this.groupValue,
required this.title,
required this.width,
});
@override
Widget build(BuildContext context) {
final isSelected = buttonValue == groupValue;
final primaryColor =
Theme.of(context).colorScheme.primary;
return SizedBox(
width: width,
child: RadioListTile<String>(
title: Text(
title,
style:
Theme.of(context).textTheme.titleSmall!.copyWith(
fontWeight: FontWeight.w600,
color: isSelected
? primaryColor
:
Theme.of(context).colorScheme.onSurface,
),
),
// Color of the radio button itself when
selected
activeColor: primaryColor,
// Background color when selected - set to
transparent to avoid scrolling issues
selectedTileColor: Colors.transparent,
// Background color when not selected
tileColor: Colors.transparent,
// Color when hovering (desktop/web)
hoverColor: primaryColor.withValues(alpha:
.08),
// splashRadius: 16,
value: buttonValue,
groupValue: groupValue,
onChanged: onChanged,
dense: true,
shape: RoundedRectangleBorder(borderRadius:
BorderRadius.circular(16)),
visualDensity: VisualDensity.compact,
selected: isSelected,
contentPadding:
EdgeInsets.symmetric(horizontal: 4, vertical: 0),
),
);
}
}
/widgets/team_member_tile.dart
import 'package:flutter/material.dart';
class TeamMemberTile extends StatelessWidget {
final int memberIndex;
final String email;
final VoidCallback onRemove;
const TeamMemberTile({
super.key,
required this.memberIndex,
required this.email,
required this.onRemove,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.primary.withValues(alpha
: .1),
borderRadius: BorderRadius.circular(6),
),
child: ListTile(
visualDensity: VisualDensity.compact,
contentPadding: const
EdgeInsets.symmetric(horizontal: 8, vertical: 0),
leading: CircleAvatar(
radius: 16,
backgroundColor:
Theme.of(context).colorScheme.primary,
child: Text(
email[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
title: Text(email, style:
Theme.of(context).textTheme.bodyLarge),
trailing: IconButton(
visualDensity: VisualDensity.compact,
style: IconButton.styleFrom(
backgroundColor: Theme.of(
context,
).colorScheme.error.withValues(alpha:
0.1),
),
icon: Icon(
Icons.close,
color:
Theme.of(context).colorScheme.error,
size: 20,
),
onPressed: onRemove,
),
),
);}}
/widgets/info_text.dart
import 'package:flutter/material.dart';
class InfoText extends StatelessWidget {
final String text;
final Color color;
const InfoText({super.key, required this.color,
required this.text});
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
),
);
}
}
Output
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )