720 lines
30 KiB
Dart
720 lines
30 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'dart:math';
|
|
|
|
import '../../models/event_model.dart';
|
|
import '../../services/auth_service.dart';
|
|
import '../../services/event_service.dart';
|
|
import '../../widgets/loading_indicator.dart';
|
|
|
|
class EventFormScreen extends StatefulWidget {
|
|
final Event? event; // null이면 새 이벤트 생성, 아니면 이벤트 수정
|
|
|
|
const EventFormScreen({
|
|
Key? key,
|
|
this.event,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<EventFormScreen> createState() => _EventFormScreenState();
|
|
}
|
|
|
|
class _EventFormScreenState extends State<EventFormScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
bool _isLoading = false;
|
|
bool _isInit = false;
|
|
final _currencyFormat = NumberFormat.currency(locale: 'ko_KR', symbol: '₩', decimalDigits: 0);
|
|
|
|
// 폼 필드 컨트롤러
|
|
final _titleController = TextEditingController();
|
|
final _descriptionController = TextEditingController();
|
|
final _locationController = TextEditingController();
|
|
final _maxParticipantsController = TextEditingController();
|
|
final _gameCountController = TextEditingController();
|
|
final _participantFeeController = TextEditingController();
|
|
final _publicHashController = TextEditingController();
|
|
final _accessPasswordController = TextEditingController();
|
|
|
|
// 이벤트 데이터
|
|
String? _type;
|
|
String? _status;
|
|
DateTime _startDate = DateTime.now();
|
|
DateTime? _endDate;
|
|
DateTime? _registrationDeadline;
|
|
|
|
// 이벤트 유형 및 상태 옵션
|
|
final List<String> _typeOptions = ['개인전', '팀전', '리그', '토너먼트', '연습', '기타'];
|
|
final List<String> _statusOptions = ['활성', '대기', '취소', '완료'];
|
|
|
|
// 이벤트 유형 및 상태별 색상
|
|
final Map<String, Color> _typeColors = {
|
|
'개인전': Colors.blue,
|
|
'팀전': Colors.green,
|
|
'리그': Colors.purple,
|
|
'토너먼트': Colors.orange,
|
|
'연습': Colors.teal,
|
|
'기타': Colors.grey,
|
|
};
|
|
|
|
final Map<String, Color> _statusColors = {
|
|
'활성': Colors.green,
|
|
'대기': Colors.amber,
|
|
'취소': Colors.red,
|
|
'완료': Colors.blue,
|
|
};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
// 수정 모드인 경우 기존 데이터 로드
|
|
if (widget.event != null) {
|
|
_titleController.text = widget.event!.title;
|
|
_descriptionController.text = widget.event!.description ?? '';
|
|
_locationController.text = widget.event!.location ?? '';
|
|
_maxParticipantsController.text = widget.event!.maxParticipants?.toString() ?? '';
|
|
_gameCountController.text = widget.event!.gameCount?.toString() ?? '';
|
|
_participantFeeController.text = widget.event!.participantFee?.toString() ?? '';
|
|
_publicHashController.text = widget.event!.publicHash ?? '';
|
|
_accessPasswordController.text = widget.event!.accessPassword ?? '';
|
|
|
|
_type = widget.event!.type;
|
|
_status = widget.event!.status;
|
|
_startDate = widget.event!.startDate;
|
|
_endDate = widget.event!.endDate;
|
|
_registrationDeadline = widget.event!.registrationDeadline;
|
|
} else {
|
|
// 새 이벤트 생성 시 기본값 설정
|
|
_type = _typeOptions[0];
|
|
_status = _statusOptions[0];
|
|
_generatePublicHash(); // 새 이벤트는 공개 해시 자동 생성
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_titleController.dispose();
|
|
_descriptionController.dispose();
|
|
_locationController.dispose();
|
|
_maxParticipantsController.dispose();
|
|
_gameCountController.dispose();
|
|
_participantFeeController.dispose();
|
|
_publicHashController.dispose();
|
|
_accessPasswordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// 공개 URL 해시 생성
|
|
void _generatePublicHash() {
|
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
final random = Random();
|
|
final hash = String.fromCharCodes(
|
|
Iterable.generate(
|
|
8,
|
|
(_) => chars.codeUnitAt(random.nextInt(chars.length)),
|
|
),
|
|
);
|
|
|
|
_publicHashController.text = hash;
|
|
}
|
|
|
|
// 이벤트 저장
|
|
Future<void> _saveEvent() async {
|
|
if (!_formKey.currentState!.validate()) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final eventService = Provider.of<EventService>(context, listen: false);
|
|
final authService = Provider.of<AuthService>(context, listen: false);
|
|
|
|
// 이벤트 데이터 준비
|
|
final eventData = {
|
|
'title': _titleController.text.trim(),
|
|
'description': _descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim(),
|
|
'location': _locationController.text.trim().isEmpty ? null : _locationController.text.trim(),
|
|
'type': _type,
|
|
'status': _status,
|
|
'startDate': _startDate.toIso8601String(),
|
|
'endDate': _endDate?.toIso8601String(),
|
|
'maxParticipants': _maxParticipantsController.text.isEmpty ? null : int.parse(_maxParticipantsController.text),
|
|
'gameCount': _gameCountController.text.isEmpty ? null : int.parse(_gameCountController.text),
|
|
'participantFee': _participantFeeController.text.isEmpty ? null : double.parse(_participantFeeController.text),
|
|
'registrationDeadline': _registrationDeadline?.toIso8601String(),
|
|
'publicHash': _publicHashController.text.trim().isEmpty ? null : _publicHashController.text.trim(),
|
|
'accessPassword': _accessPasswordController.text.trim().isEmpty ? null : _accessPasswordController.text.trim(),
|
|
};
|
|
|
|
if (widget.event == null) {
|
|
// 새 이벤트 생성
|
|
await eventService.createEvent(eventData);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('이벤트가 생성되었습니다')),
|
|
);
|
|
}
|
|
} else {
|
|
// 기존 이벤트 수정
|
|
await eventService.updateEvent(widget.event!.id, eventData);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('이벤트가 수정되었습니다')),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (mounted) {
|
|
Navigator.of(context).pop(true); // 성공 결과와 함께 이전 화면으로 돌아가기
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('이벤트 저장에 실패했습니다: $e')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.event == null ? '새 이벤트 생성' : '이벤트 수정'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.save),
|
|
onPressed: _saveEvent,
|
|
),
|
|
],
|
|
),
|
|
body: _isLoading
|
|
? const LoadingIndicator()
|
|
: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 기본 정보 섹션
|
|
_buildSectionTitle('기본 정보'),
|
|
|
|
// 제목
|
|
TextFormField(
|
|
controller: _titleController,
|
|
decoration: const InputDecoration(
|
|
labelText: '이벤트 제목 *',
|
|
hintText: '이벤트 제목을 입력하세요',
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return '이벤트 제목을 입력해주세요';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 설명
|
|
TextFormField(
|
|
controller: _descriptionController,
|
|
decoration: const InputDecoration(
|
|
labelText: '이벤트 설명',
|
|
hintText: '이벤트 설명을 입력하세요',
|
|
),
|
|
maxLines: 3,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 이벤트 유형
|
|
DropdownButtonFormField<String>(
|
|
value: _type,
|
|
decoration: const InputDecoration(
|
|
labelText: '이벤트 유형',
|
|
),
|
|
items: _typeOptions
|
|
.map((type) => DropdownMenuItem<String>(
|
|
value: type,
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: _typeColors[type]?.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: _typeColors[type] ?? Colors.grey),
|
|
),
|
|
child: Text(
|
|
type,
|
|
style: TextStyle(color: _typeColors[type]),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_type = value;
|
|
});
|
|
},
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return '이벤트 유형을 선택해주세요';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
if (_type != null)
|
|
Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(left: 12),
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: _typeColors[_type]?.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: _typeColors[_type] ?? Colors.grey),
|
|
),
|
|
child: Text(
|
|
_type!,
|
|
style: TextStyle(color: _typeColors[_type], fontSize: 12),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 상태
|
|
DropdownButtonFormField<String>(
|
|
value: _status,
|
|
decoration: const InputDecoration(
|
|
labelText: '상태',
|
|
),
|
|
items: _statusOptions
|
|
.map((status) => DropdownMenuItem<String>(
|
|
value: status,
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: _statusColors[status]?.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: _statusColors[status] ?? Colors.grey),
|
|
),
|
|
child: Text(
|
|
status,
|
|
style: TextStyle(color: _statusColors[status]),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_status = value;
|
|
});
|
|
},
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return '상태를 선택해주세요';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
if (_status != null)
|
|
Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(left: 12),
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: _statusColors[_status]?.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: _statusColors[_status] ?? Colors.grey),
|
|
),
|
|
child: Text(
|
|
_status!,
|
|
style: TextStyle(color: _statusColors[_status], fontSize: 12),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// 날짜 및 시간 섹션
|
|
_buildSectionTitle('날짜 및 시간'),
|
|
|
|
// 시작 날짜
|
|
ListTile(
|
|
title: const Text('시작 날짜 및 시간 *'),
|
|
subtitle: Text(
|
|
DateFormat('yyyy년 MM월 dd일 HH:mm').format(_startDate),
|
|
),
|
|
trailing: const Icon(Icons.calendar_today),
|
|
onTap: () async {
|
|
final date = await showDatePicker(
|
|
context: context,
|
|
initialDate: _startDate,
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
|
|
if (date != null) {
|
|
final time = await showTimePicker(
|
|
context: context,
|
|
initialTime: TimeOfDay.fromDateTime(_startDate),
|
|
);
|
|
|
|
if (time != null && mounted) {
|
|
setState(() {
|
|
_startDate = DateTime(
|
|
date.year,
|
|
date.month,
|
|
date.day,
|
|
time.hour,
|
|
time.minute,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// 종료 날짜
|
|
ListTile(
|
|
title: const Text('종료 날짜 및 시간 (선택)'),
|
|
subtitle: _endDate != null
|
|
? Text(
|
|
DateFormat('yyyy년 MM월 dd일 HH:mm').format(_endDate!),
|
|
)
|
|
: const Text('설정되지 않음'),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (_endDate != null)
|
|
IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
setState(() {
|
|
_endDate = null;
|
|
});
|
|
},
|
|
),
|
|
const Icon(Icons.calendar_today),
|
|
],
|
|
),
|
|
onTap: () async {
|
|
final date = await showDatePicker(
|
|
context: context,
|
|
initialDate: _endDate ?? _startDate.add(const Duration(hours: 2)),
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
|
|
if (date != null) {
|
|
final time = await showTimePicker(
|
|
context: context,
|
|
initialTime: _endDate != null
|
|
? TimeOfDay.fromDateTime(_endDate!)
|
|
: TimeOfDay.fromDateTime(_startDate.add(const Duration(hours: 2))),
|
|
);
|
|
|
|
if (time != null && mounted) {
|
|
setState(() {
|
|
_endDate = DateTime(
|
|
date.year,
|
|
date.month,
|
|
date.day,
|
|
time.hour,
|
|
time.minute,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// 등록 마감일
|
|
ListTile(
|
|
title: const Text('등록 마감일 (선택)'),
|
|
subtitle: _registrationDeadline != null
|
|
? Text(
|
|
DateFormat('yyyy년 MM월 dd일 HH:mm').format(_registrationDeadline!),
|
|
)
|
|
: const Text('설정되지 않음'),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (_registrationDeadline != null)
|
|
IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
setState(() {
|
|
_registrationDeadline = null;
|
|
});
|
|
},
|
|
),
|
|
const Icon(Icons.calendar_today),
|
|
],
|
|
),
|
|
onTap: () async {
|
|
final date = await showDatePicker(
|
|
context: context,
|
|
initialDate: _registrationDeadline ?? _startDate.subtract(const Duration(days: 1)),
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
|
|
if (date != null) {
|
|
final time = await showTimePicker(
|
|
context: context,
|
|
initialTime: _registrationDeadline != null
|
|
? TimeOfDay.fromDateTime(_registrationDeadline!)
|
|
: TimeOfDay.fromDateTime(_startDate.subtract(const Duration(days: 1))),
|
|
);
|
|
|
|
if (time != null && mounted) {
|
|
setState(() {
|
|
_registrationDeadline = DateTime(
|
|
date.year,
|
|
date.month,
|
|
date.day,
|
|
time.hour,
|
|
time.minute,
|
|
);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// 장소 및 참가자 섹션
|
|
_buildSectionTitle('장소 및 참가자'),
|
|
|
|
// 장소
|
|
TextFormField(
|
|
controller: _locationController,
|
|
decoration: const InputDecoration(
|
|
labelText: '장소',
|
|
hintText: '이벤트 장소를 입력하세요',
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 최대 참가자 수
|
|
TextFormField(
|
|
controller: _maxParticipantsController,
|
|
decoration: const InputDecoration(
|
|
labelText: '최대 참가자 수',
|
|
hintText: '최대 참가자 수를 입력하세요',
|
|
suffixText: '명',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
validator: (value) {
|
|
if (value != null && value.isNotEmpty) {
|
|
if (int.tryParse(value) == null) {
|
|
return '숫자를 입력해주세요';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 게임 수
|
|
TextFormField(
|
|
controller: _gameCountController,
|
|
decoration: const InputDecoration(
|
|
labelText: '게임 수',
|
|
hintText: '게임 수를 입력하세요',
|
|
suffixText: '게임',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
validator: (value) {
|
|
if (value != null && value.isNotEmpty) {
|
|
final numValue = int.tryParse(value);
|
|
if (numValue == null) {
|
|
return '숫자를 입력해주세요';
|
|
}
|
|
if (numValue <= 0) {
|
|
return '1 이상의 숫자를 입력해주세요';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 게임 수
|
|
TextFormField(
|
|
controller: _gameCountController,
|
|
decoration: const InputDecoration(
|
|
labelText: '게임 수',
|
|
hintText: '게임 수를 입력하세요',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
validator: (value) {
|
|
if (value != null && value.isNotEmpty) {
|
|
if (int.tryParse(value) == null) {
|
|
return '숫자를 입력해주세요';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 참가비
|
|
TextFormField(
|
|
controller: _participantFeeController,
|
|
decoration: const InputDecoration(
|
|
labelText: '참가비',
|
|
hintText: '참가비를 입력하세요',
|
|
prefixText: '₩',
|
|
),
|
|
keyboardType: TextInputType.number,
|
|
validator: (value) {
|
|
if (value != null && value.isNotEmpty) {
|
|
if (double.tryParse(value) == null) {
|
|
return '숫자를 입력해주세요';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
onChanged: (value) {
|
|
if (value.isNotEmpty) {
|
|
final numericValue = double.tryParse(value);
|
|
if (numericValue != null) {
|
|
// 입력 중에는 포맷을 적용하지 않음
|
|
}
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 4),
|
|
if (_participantFeeController.text.isNotEmpty && double.tryParse(_participantFeeController.text) != null)
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: Text(
|
|
'표시: ${_currencyFormat.format(double.parse(_participantFeeController.text))}',
|
|
style: TextStyle(color: Colors.grey[600], fontSize: 12),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// 공개 접근 섹션
|
|
_buildSectionTitle('공개 접근'),
|
|
|
|
// 공개 URL 해시
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
controller: _publicHashController,
|
|
decoration: const InputDecoration(
|
|
labelText: '공개 URL 해시',
|
|
hintText: '공개 URL 해시를 입력하세요',
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
onPressed: () {
|
|
setState(() {
|
|
_generatePublicHash();
|
|
});
|
|
},
|
|
tooltip: '새 해시 생성',
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.copy),
|
|
onPressed: () {
|
|
final publicUrl = 'https://bowling.example.com/events/${_publicHashController.text}';
|
|
Clipboard.setData(ClipboardData(text: publicUrl));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('공개 URL이 클립보드에 복사되었습니다')),
|
|
);
|
|
},
|
|
tooltip: 'URL 복사',
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 4),
|
|
if (_publicHashController.text.isNotEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 12),
|
|
child: Text(
|
|
'https://bowling.example.com/events/${_publicHashController.text}',
|
|
style: TextStyle(color: Colors.blue[700], fontSize: 12),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 접근 비밀번호
|
|
TextFormField(
|
|
controller: _accessPasswordController,
|
|
decoration: const InputDecoration(
|
|
labelText: '접근 비밀번호 (선택)',
|
|
hintText: '접근 비밀번호를 입력하세요',
|
|
),
|
|
obscureText: true,
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// 저장 버튼
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _saveEvent,
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
child: Text(
|
|
widget.event == null ? '이벤트 생성' : '이벤트 수정',
|
|
style: const TextStyle(fontSize: 16),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 섹션 제목 위젯
|
|
Widget _buildSectionTitle(String title) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const Divider(),
|
|
const SizedBox(height: 8),
|
|
],
|
|
);
|
|
}
|
|
}
|