Files
CheckTheethKids/lib/consultorios/address_edit_sheet.dart
Carlos Correia 89b27e5e86 CTK 1.1.0
2026-07-22 01:34:26 +01:00

213 lines
6.8 KiB
Dart

import 'package:flutter/material.dart';
import '../colors/app_colors.dart';
import '../colors/app_gradients.dart';
import '../main.dart' show supabase;
import '../strings/address_strings.dart';
import '../strings/common_strings.dart';
import '../widgets/tap_bounce.dart';
import 'geocoding_service.dart';
/// Mostra o formulário de morada num modal bottom sheet, no mesmo estilo
/// já usado para o formulário de adicionar criança. Devolve `true` se a
/// morada foi guardada com sucesso.
Future<bool?> showAddressEditSheet(
BuildContext context, {
String? initialAddress,
}) {
return showModalBottomSheet<bool>(
context: context,
isScrollControlled: true,
showDragHandle: true,
backgroundColor: AppColors.pinkBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) => AddressEditSheet(initialAddress: initialAddress),
);
}
class AddressEditSheet extends StatefulWidget {
const AddressEditSheet({super.key, this.initialAddress});
final String? initialAddress;
@override
State<AddressEditSheet> createState() => _AddressEditSheetState();
}
class _AddressEditSheetState extends State<AddressEditSheet> {
late final _addressController = TextEditingController(
text: widget.initialAddress ?? '',
);
bool _saving = false;
String? _errorText;
@override
void dispose() {
_addressController.dispose();
super.dispose();
}
Future<void> _save() async {
final address = _addressController.text.trim();
if (address.isEmpty) {
setState(() => _errorText = AddressStrings.addressRequired);
return;
}
setState(() {
_saving = true;
_errorText = null;
});
final coords = await GeocodingService.geocodeAddress(address);
if (!mounted) return;
if (coords == null) {
setState(() {
_saving = false;
_errorText = AddressStrings.geocodeFailed;
});
return;
}
final uid = supabase.auth.currentUser?.id;
if (uid == null) {
setState(() => _saving = false);
return;
}
try {
await supabase.from('profiles').upsert({
'id': uid,
'address': address,
'address_lat': coords.lat,
'address_lon': coords.lon,
});
if (!mounted) return;
Navigator.of(context).pop(true);
} catch (e) {
if (!mounted) return;
setState(() {
_saving = false;
_errorText = AddressStrings.saveError(e);
});
}
}
@override
Widget build(BuildContext context) {
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
return SafeArea(
child: Padding(
padding: EdgeInsets.fromLTRB(18, 6, 18, 18 + bottomInset),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
AddressStrings.title,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: AppColors.pink,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _addressController,
enabled: !_saving,
textInputAction: TextInputAction.done,
textCapitalization: TextCapitalization.sentences,
onSubmitted: (_) => _save(),
decoration: InputDecoration(
labelText: AddressStrings.label,
hintText: AddressStrings.hint,
errorText: _errorText,
),
),
const SizedBox(height: 10),
Text(
AddressStrings.privacyDisclaimer,
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w600,
color: Colors.black.withValues(alpha: 0.5),
height: 1.35,
),
),
],
),
),
const SizedBox(height: 14),
Row(
children: [
Expanded(
child: SizedBox(
height: 44,
child: TextButton(
onPressed: _saving
? null
: () => Navigator.of(context).pop(false),
child: const Text(CommonStrings.cancel),
),
),
),
const SizedBox(width: 10),
Expanded(
child: TapBounce(
child: ClipRRect(
borderRadius: BorderRadius.circular(999),
child: DecoratedBox(
decoration: const BoxDecoration(
gradient: kGreenButtonGradient,
),
child: SizedBox(
height: 44,
child: FilledButton(
style: FilledButton.styleFrom(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
),
),
onPressed: _saving ? null : _save,
child: _saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(AddressStrings.save),
),
),
),
),
),
),
],
),
],
),
),
);
}
}