Created
May 17, 2025 03:44
-
-
Save md-riaz/f801ce35b5a998c9fec831ae09e41a44 to your computer and use it in GitHub Desktop.
add contact flutter demo2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'dart:convert'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Contact Phone Input Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: const ContactInputScreen(), | |
debugShowCheckedModeBanner: false, | |
); | |
} | |
} | |
class ContactInputScreen extends StatefulWidget { | |
const ContactInputScreen({super.key}); | |
@override | |
State<ContactInputScreen> createState() => _ContactInputScreenState(); | |
} | |
class _ContactInputScreenState extends State<ContactInputScreen> { | |
final _formKey = GlobalKey<FormState>(); | |
late TextEditingController _contactNameController; | |
List<PhoneEntry> phoneNumbers = []; | |
final List<PhoneType> phoneTypes = [ | |
PhoneType.mobile, | |
PhoneType.home, | |
PhoneType.work, | |
PhoneType.other, | |
]; | |
@override | |
void initState() { | |
super.initState(); | |
_contactNameController = TextEditingController(); | |
addPhoneNumber(); // Start with one empty phone number | |
} | |
@override | |
void dispose() { | |
_contactNameController.dispose(); | |
for (var entry in phoneNumbers) { | |
entry.controller.dispose(); | |
} | |
super.dispose(); | |
} | |
void addPhoneNumber() { | |
setState(() { | |
if (phoneNumbers.isEmpty) { | |
phoneNumbers.add(PhoneEntry( | |
number: '', | |
isPrimary: true, | |
phoneType: phoneTypes.first, | |
controller: TextEditingController())); | |
} else { | |
phoneNumbers.add(PhoneEntry( | |
number: '', | |
isPrimary: false, | |
phoneType: phoneTypes.first, | |
controller: TextEditingController())); | |
} | |
}); | |
} | |
void updatePhoneNumber(int index, String number) { | |
setState(() { | |
phoneNumbers[index] = phoneNumbers[index].copyWith(number: number); | |
}); | |
} | |
void updatePhoneType(int index, PhoneType? phoneType) { | |
if (phoneType == null) { | |
return; | |
} | |
setState(() { | |
phoneNumbers[index] = phoneNumbers[index].copyWith(phoneType: phoneType); | |
}); | |
} | |
void setPrimaryNumber(int index) { | |
setState(() { | |
for (int i = 0; i < phoneNumbers.length; i++) { | |
phoneNumbers[i] = phoneNumbers[i].copyWith(isPrimary: i == index); | |
} | |
}); | |
} | |
void deletePhoneNumber(int index) { | |
setState(() { | |
phoneNumbers[index].controller.dispose(); | |
phoneNumbers.removeAt(index); | |
if (phoneNumbers.isEmpty) { | |
addPhoneNumber(); // Ensure at least one phone number exists | |
} else if (phoneNumbers.every((element) => !element.isPrimary)) { | |
setPrimaryNumber(0); | |
} | |
}); | |
} | |
String toJson() { | |
final numbersJson = phoneNumbers | |
.map((entry) => { | |
'number': entry.number, | |
'is_primary': entry.isPrimary ? 1 : 0, | |
'phone_type': entry.phoneType.index, | |
}) | |
.toList(); | |
final json = { | |
'name': _contactNameController.text, | |
'numbers': numbersJson, | |
}; | |
return jsonEncode(json); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Contact Phone Numbers'), | |
), | |
body: Form( | |
key: _formKey, | |
child: ListView( | |
children: [ | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
const Text( | |
'Contact Name', | |
style: TextStyle(fontWeight: FontWeight.bold), | |
), | |
const SizedBox(height: 4.0), | |
TextFormField( | |
controller: _contactNameController, | |
decoration: const InputDecoration( | |
border: OutlineInputBorder(), | |
isDense: true, | |
contentPadding: EdgeInsets.symmetric( | |
horizontal: 12, vertical: 8), // Add content padding | |
), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter a name'; | |
} | |
return null; | |
}, | |
), | |
], | |
), | |
), | |
...phoneNumbers.asMap().entries.map((entry) { | |
int index = entry.key; | |
PhoneEntry phoneEntry = entry.value; | |
return PhoneEntryWidget( | |
index: index, | |
phoneEntry: phoneEntry, | |
updatePhoneNumber: updatePhoneNumber, | |
updatePhoneType: updatePhoneType, | |
setPrimaryNumber: setPrimaryNumber, | |
deletePhoneNumber: deletePhoneNumber, | |
phoneTypes: phoneTypes, | |
key: Key(index.toString()), | |
); | |
}).toList(), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: ElevatedButton( | |
onPressed: () { | |
addPhoneNumber(); | |
}, | |
child: const Text('Add Phone Number'), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: ElevatedButton( | |
onPressed: () { | |
if (_formKey.currentState!.validate()) { | |
ScaffoldMessenger.of(context).showSnackBar( | |
SnackBar(content: Text('Saving Contact: ${toJson()}')), | |
); | |
} | |
}, | |
child: const Text('Save Contact'), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
enum PhoneType { | |
mobile, | |
home, | |
work, | |
other, | |
} | |
class PhoneEntry { | |
final String number; | |
final bool isPrimary; | |
final PhoneType phoneType; | |
final TextEditingController controller; | |
PhoneEntry({ | |
required this.number, | |
required this.isPrimary, | |
required this.phoneType, | |
required this.controller, | |
}); | |
PhoneEntry copyWith( | |
{String? number, bool? isPrimary, PhoneType? phoneType}) { | |
return PhoneEntry( | |
number: number ?? this.number, | |
isPrimary: isPrimary ?? this.isPrimary, | |
phoneType: phoneType ?? this.phoneType, | |
controller: controller, | |
); | |
} | |
} | |
class PhoneEntryWidget extends StatefulWidget { | |
final int index; | |
final PhoneEntry phoneEntry; | |
final Function(int, String) updatePhoneNumber; | |
final Function(int, PhoneType?) updatePhoneType; | |
final Function(int) setPrimaryNumber; | |
final Function(int) deletePhoneNumber; | |
final List<PhoneType> phoneTypes; | |
const PhoneEntryWidget({ | |
required Key key, | |
required this.index, | |
required this.phoneEntry, | |
required this.updatePhoneNumber, | |
required this.updatePhoneType, | |
required this.setPrimaryNumber, | |
required this.deletePhoneNumber, | |
required this.phoneTypes, | |
}) : super(key: key); | |
@override | |
State<PhoneEntryWidget> createState() => _PhoneEntryWidgetState(); | |
} | |
class _PhoneEntryWidgetState extends State<PhoneEntryWidget> { | |
@override | |
Widget build(BuildContext context) { | |
return Card( | |
margin: const EdgeInsets.all(8.0), | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
Row( | |
children: [ | |
Expanded( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
const Text( | |
'Phone Number', | |
style: TextStyle(fontWeight: FontWeight.bold), | |
), | |
const SizedBox(height: 4.0), | |
TextFormField( | |
controller: widget.phoneEntry.controller, | |
decoration: const InputDecoration( | |
border: OutlineInputBorder(), | |
isDense: true, | |
contentPadding: EdgeInsets.symmetric( | |
horizontal: 12, | |
vertical: 8), // Add content padding | |
), | |
keyboardType: TextInputType.phone, | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter a phone number'; | |
} | |
return null; | |
}, | |
onChanged: (text) { | |
widget.updatePhoneNumber(widget.index, text); | |
}, | |
), | |
], | |
), | |
), | |
const SizedBox(width: 8.0), | |
Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: [ | |
const Text( | |
'Type', | |
style: TextStyle(fontWeight: FontWeight.bold), | |
), | |
const SizedBox(height: 4.0), | |
DropdownButton<PhoneType>( | |
value: widget.phoneEntry.phoneType, | |
items: widget.phoneTypes.map((PhoneType type) { | |
return DropdownMenuItem<PhoneType>( | |
value: type, | |
child: Text(type.toString().split('.').last), | |
); | |
}).toList(), | |
onChanged: (PhoneType? newValue) { | |
widget.updatePhoneType(widget.index, newValue); | |
}, | |
), | |
], | |
), | |
], | |
), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
children: [ | |
Row( | |
children: [ | |
const Text('Primary: '), | |
Radio<bool>( | |
value: true, | |
groupValue: widget.phoneEntry.isPrimary, | |
onChanged: (bool? value) { | |
if (value != null && value) { | |
widget.setPrimaryNumber(widget.index); | |
} | |
}, | |
), | |
], | |
), | |
IconButton( | |
icon: const Icon(Icons.delete), | |
onPressed: () { | |
widget.deletePhoneNumber(widget.index); | |
}, | |
), | |
], | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment