Files
MobilApp/lib/pages/contacts/presentation/views/contact_edit_view.dart
T

240 lines
8.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:terepi_seged/pages/contacts/presentation/controllers/contacts_controller.dart';
import 'package:terepi_seged/services/project_service.dart';
import '../../../../models/contact.dart';
import '../../../../services/contact_service.dart';
/// Kapcsolat szerkesztő — TELJES OLDAL (Scaffold), nem bottom sheet.
///
/// Miért oldal: a bottom sheet a szoftveres billentyűzettel rosszul
/// viselkedik (a mezőket eltakarja / a lapot feltolja). A Scaffold body
/// automatikusan a billentyűzet fölé görget, a mentés gomb pedig fixen
/// az appbarban van — kesztyűs, terepi használatra is kényelmes.
///
/// Új rekord: `Get.to(() => const ContactEditView())`.
/// Szerkesztés: `Get.to(() => const ContactEditView(), arguments: contact)`.
class ContactEditView extends StatefulWidget {
const ContactEditView({super.key});
@override
State<ContactEditView> createState() => _ContactEditViewState();
}
class _ContactEditViewState extends State<ContactEditView> {
final _formKey = GlobalKey<FormState>();
Contact? _original;
String? _originalLocalUuid;
late final TextEditingController _name;
late final TextEditingController _address;
late final TextEditingController _phone;
late final TextEditingController _email;
late final TextEditingController _note;
bool _saving = false;
late double? _lat;
late double? _lon;
late double? _eovY;
late double? _eovX;
@override
void initState() {
super.initState();
final args = Get.arguments;
if (args is ContactWithState) {
_original = args.contact;
_originalLocalUuid = args.isPending ? args.localUuid : null;
} else if (args is Contact) {
_original = args;
}
_name = TextEditingController(text: _original?.name ?? '');
_address = TextEditingController(text: _original?.address ?? '');
_phone = TextEditingController(text: _original?.phone ?? '');
_email = TextEditingController(text: _original?.email ?? '');
_note = TextEditingController(text: _original?.note ?? '');
_lat = _original?.lat;
_lon = _original?.lon;
_eovY = _original?.eovY;
_eovX = _original?.eovX;
}
@override
void dispose() {
_name.dispose();
_address.dispose();
_phone.dispose();
_email.dispose();
_note.dispose();
super.dispose();
}
Future<void> _save() async {
if (!(_formKey.currentState?.validate() ?? false)) return;
setState(() => _saving = true);
try {
final contact = (_original ??
Contact(
name: '',
projectId: ProjectService.to.activeProject.value!.uuid))
.copyWith(
name: _name.text,
address: _address.text,
phone: _phone.text,
email: _email.text,
note: _note.text,
lat: _lat,
lon: _lon,
eovY: _eovY,
eovX: _eovX,
clearLocation: _lat == null,
);
final queued = await ContactService.to
.save(contact, existingLocalUuid: _originalLocalUuid);
Get.back(result: queued); // a lista frissítéshez visszakapja
if (Get.isRegistered<ContactsController>()) {
Get.find<ContactsController>().load(silent: true);
}
Get.snackbar(queued ? 'Elmentve (offline)' : 'Mentve',
queued ? '${contact.name} - feltöltés, amint van net' : contact.name,
snackPosition: SnackPosition.BOTTOM);
} catch (e) {
Get.snackbar('Hiba', _friendly(e),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: const Color(0xFFB71C1C),
colorText: const Color(0xFFFFFFFF));
} finally {
if (mounted) setState(() => _saving = false);
}
}
String _friendly(Object e) {
final s = e.toString();
if (s.contains('permission') ||
s.contains('policy') ||
s.contains('row-level')) {
return 'Nincs jogosultságod a művelethez.';
}
return s;
}
@override
Widget build(BuildContext context) {
final isNew = _original?.id == null;
return Scaffold(
appBar: AppBar(
title: Text(isNew ? 'Új kapcsolat' : 'Kapcsolat szerkesztése'),
actions: [
TextButton.icon(
onPressed: _saving ? null : _save,
icon: _saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.check),
label: const Text('Mentés'),
),
],
),
body: Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
TextFormField(
controller: _name,
autofocus: isNew,
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Név *',
prefixIcon: Icon(Icons.person_outline),
),
textInputAction: TextInputAction.next,
validator: (v) => (v == null || v.trim().isEmpty)
? 'A név megadása kötelező.'
: null,
),
const SizedBox(height: 12),
TextFormField(
controller: _address,
textCapitalization: TextCapitalization.sentences,
decoration: const InputDecoration(
labelText: 'Cím',
prefixIcon: Icon(Icons.location_on_outlined),
),
textInputAction: TextInputAction.next,
),
const SizedBox(height: 12),
TextFormField(
controller: _phone,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
labelText: 'Telefonszám',
prefixIcon: Icon(Icons.phone_outlined),
),
textInputAction: TextInputAction.next,
),
const SizedBox(height: 12),
TextFormField(
controller: _email,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'E-mail',
prefixIcon: Icon(Icons.email_outlined),
),
textInputAction: TextInputAction.next,
validator: (v) {
if (v == null || v.trim().isEmpty) return null;
final ok =
RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$').hasMatch(v.trim());
return ok ? null : 'Érvénytelen e-mail cím.';
},
),
const SizedBox(height: 12),
TextFormField(
controller: _note,
maxLines: 4,
textCapitalization: TextCapitalization.sentences,
decoration: const InputDecoration(
labelText: 'Megjegyzés',
prefixIcon: Icon(Icons.notes_outlined),
alignLabelWithHint: true,
),
),
const SizedBox(height: 12),
if (_lat != null && _lon != null)
Card(
color: Colors.indigo.withOpacity(0.06),
child: ListTile(
leading: const Icon(Icons.location_on, color: Colors.indigo),
title: Text(
'${_lat!.toStringAsFixed(6)}, ${_lon!.toStringAsFixed(6)}'),
subtitle: const Text('Helyszín rögzítve'),
trailing: IconButton(
icon: const Icon(Icons.close),
tooltip: 'Helyszín törlése',
onPressed: () => setState(() {
_lat = null;
_lon = null;
_eovY = null;
_eovX = null;
}),
),
),
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _saving ? null : _save,
icon: const Icon(Icons.save_outlined),
label: const Text('Mentés'),
),
],
),
),
);
}
}