213 lines
6.8 KiB
Dart
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|