import 'package:aves/model/entry.dart'; import 'package:aves/model/filters/location.dart'; import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/map/geo_map.dart'; import 'package:aves/widgets/common/map/theme.dart'; import 'package:aves/widgets/map/map_page.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:flutter/material.dart'; class LocationSection extends StatefulWidget { final CollectionLens? collection; final AvesEntry entry; final bool showTitle; final ValueNotifier isScrollingNotifier; final FilterCallback onFilter; const LocationSection({ Key? key, required this.collection, required this.entry, required this.showTitle, required this.isScrollingNotifier, required this.onFilter, }) : super(key: key); @override _LocationSectionState createState() => _LocationSectionState(); } class _LocationSectionState extends State { CollectionLens? get collection => widget.collection; AvesEntry get entry => widget.entry; @override void initState() { super.initState(); _registerWidget(widget); } @override void didUpdateWidget(covariant LocationSection oldWidget) { super.didUpdateWidget(oldWidget); _unregisterWidget(oldWidget); _registerWidget(widget); } @override void dispose() { _unregisterWidget(widget); super.dispose(); } void _registerWidget(LocationSection widget) { widget.entry.metadataChangeNotifier.addListener(_handleChange); widget.entry.addressChangeNotifier.addListener(_handleChange); } void _unregisterWidget(LocationSection widget) { widget.entry.metadataChangeNotifier.removeListener(_handleChange); widget.entry.addressChangeNotifier.removeListener(_handleChange); } @override Widget build(BuildContext context) { if (!entry.hasGps) return const SizedBox(); final filters = []; if (entry.hasAddress) { final address = entry.addressDetails!; final country = address.countryName; if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}')); final place = address.place; if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place)); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.showTitle) const SectionRow(icon: AIcons.location), MapTheme( interactive: false, showCoordinateFilter: false, navigationButton: MapNavigationButton.map, visualDensity: VisualDensity.compact, mapHeight: 200, child: GeoMap( entries: [entry], isAnimatingNotifier: widget.isScrollingNotifier, onUserZoomChange: (zoom) => settings.infoMapZoom = zoom, onMarkerTap: collection != null ? (_, __) => _openMapPage(context) : null, openMapPage: collection != null ? _openMapPage : null, ), ), _AddressInfoGroup(entry: entry), if (filters.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8), child: Wrap( spacing: 8, runSpacing: 8, children: filters .map((filter) => AvesFilterChip( filter: filter, onTap: widget.onFilter, )) .toList(), ), ), ], ); } void _openMapPage(BuildContext context) { final baseCollection = collection; if (baseCollection == null) return; Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: MapPage.routeName), builder: (context) => MapPage( collection: CollectionLens( source: baseCollection.source, fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(), ), initialEntry: entry, ), ), ); } void _handleChange() => setState(() {}); } class _AddressInfoGroup extends StatefulWidget { final AvesEntry entry; const _AddressInfoGroup({required this.entry}); @override _AddressInfoGroupState createState() => _AddressInfoGroupState(); } class _AddressInfoGroupState extends State<_AddressInfoGroup> { late Future _addressLineLoader; AvesEntry get entry => widget.entry; @override void initState() { super.initState(); _addressLineLoader = availability.canLocatePlaces.then((connected) { if (connected) { return entry.findAddressLine(); } return null; }); } @override Widget build(BuildContext context) { return FutureBuilder( future: _addressLineLoader, builder: (context, snapshot) { final fullAddress = !snapshot.hasError && snapshot.connectionState == ConnectionState.done ? snapshot.data : null; final address = fullAddress ?? entry.shortAddress; final l10n = context.l10n; return InfoRowGroup( info: { l10n.viewerInfoLabelCoordinates: settings.coordinateFormat.format(entry.latLng!), if (address.isNotEmpty) l10n.viewerInfoLabelAddress: address, }, ); }, ); } }