import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:latlong2/latlong.dart'; import '../models/place_models.dart'; final placeControllerProvider = NotifierProvider( PlaceController.new, ); class PlaceState { const PlaceState({ required this.intent, required this.places, required this.selectedPlaceId, required this.reviewDraft, }); final UserIntent intent; final List places; final String? selectedPlaceId; final VoiceReviewDraft reviewDraft; List get recommendations { final wanted = intent.traits; final ranked = [...places] ..sort((a, b) { final aScore = a.traits.intersection(wanted).length; final bScore = b.traits.intersection(wanted).length; return bScore.compareTo(aScore); }); return ranked.take(4).toList(); } PlaceRecommendation? get selectedPlace { for (final place in places) { if (place.id == selectedPlaceId) { return place; } } return recommendations.isEmpty ? null : recommendations.first; } PlaceState copyWith({ UserIntent? intent, List? places, String? selectedPlaceId, VoiceReviewDraft? reviewDraft, }) { return PlaceState( intent: intent ?? this.intent, places: places ?? this.places, selectedPlaceId: selectedPlaceId ?? this.selectedPlaceId, reviewDraft: reviewDraft ?? this.reviewDraft, ); } } class PlaceController extends Notifier { @override PlaceState build() { final places = _seedPlaces(); return PlaceState( intent: UserIntent.exhale, places: places, selectedPlaceId: places.first.id, reviewDraft: const VoiceReviewDraft( placeName: '', duration: Duration.zero, extractedTraits: {}, suggestedIntents: {}, evidence: [], ), ); } void selectIntent(UserIntent intent) { PlaceRecommendation? next; for (final place in state.places) { if (place.traits.intersection(intent.traits).isNotEmpty) { next = place; break; } } state = state.copyWith(intent: intent, selectedPlaceId: next?.id); } void selectPlace(String placeId) { state = state.copyWith(selectedPlaceId: placeId); } void setReviewPlace(String placeName) { state = state.copyWith( reviewDraft: state.reviewDraft.copyWith(placeName: placeName), ); } void setReviewDuration(Duration duration) { state = state.copyWith( reviewDraft: state.reviewDraft.copyWith(duration: duration), ); } void analyzeVoiceReview() { final placeName = state.reviewDraft.placeName.trim().isEmpty ? 'Новое место' : state.reviewDraft.placeName.trim(); state = state.copyWith( reviewDraft: state.reviewDraft.copyWith( placeName: placeName, duration: state.reviewDraft.duration.inSeconds < 30 ? const Duration(seconds: 36) : state.reviewDraft.duration, extractedTraits: { PlaceTrait.cozy, PlaceTrait.private, PlaceTrait.beautiful, PlaceTrait.calm, }, suggestedIntents: {UserIntent.exhale, UserIntent.date}, evidence: [ 'можно нормально поговорить', 'место мягкое, не давит', 'туда хочется привести человека вечером', ], ), ); } void publishReview({LatLng? coordinate}) { final draft = state.reviewDraft; final place = PlaceRecommendation( id: 'local-${DateTime.now().microsecondsSinceEpoch}', name: draft.placeName.trim().isEmpty ? 'Новое место' : draft.placeName, area: 'добавлено голосом', photoUrls: const [ 'https://images.unsplash.com/photo-1554118811-1e0d58224f24?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?auto=format&fit=crop&w=600&q=80', ], coordinate: coordinate ?? const LatLng(10.7729, 106.7004), traits: draft.extractedTraits.isEmpty ? {PlaceTrait.cozy, PlaceTrait.calm} : draft.extractedTraits, ); state = state.copyWith( places: [place, ...state.places], selectedPlaceId: place.id, reviewDraft: const VoiceReviewDraft( placeName: '', duration: Duration.zero, extractedTraits: {}, suggestedIntents: {}, evidence: [], ), ); } List _seedPlaces() { return const [ PlaceRecommendation( id: 'secret-garden', name: 'Secret Garden', area: 'District 1', photoUrls: [ 'https://images.unsplash.com/photo-1552566626-52f8b828add9?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1559339352-11d035aa65de?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1521017432531-fbd92d768814?auto=format&fit=crop&w=600&q=80', ], coordinate: LatLng(10.7752, 106.7009), traits: { PlaceTrait.calm, PlaceTrait.cozy, PlaceTrait.private, PlaceTrait.beautiful, PlaceTrait.social, }, ), PlaceRecommendation( id: 'workshop', name: 'The Workshop', area: 'District 1', photoUrls: [ 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1511920170033-f8396924c348?auto=format&fit=crop&w=600&q=80', ], coordinate: LatLng(10.7740, 106.7042), traits: { PlaceTrait.focused, PlaceTrait.calm, PlaceTrait.neutral, PlaceTrait.solo, }, ), PlaceRecommendation( id: 'oc-dao', name: 'Oc Dao', area: 'District 1', photoUrls: [ 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1544025162-d76694265947?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1551218808-94e220e084d2?auto=format&fit=crop&w=600&q=80', ], coordinate: LatLng(10.7607, 106.6898), traits: { PlaceTrait.alive, PlaceTrait.open, PlaceTrait.social, PlaceTrait.unusual, }, ), PlaceRecommendation( id: 'l-usine', name: 'L\'Usine', area: 'Dong Khoi', photoUrls: [ 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1550966871-3ed3cdb5ed0c?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1551632436-cbf8dd35adfa?auto=format&fit=crop&w=600&q=80', ], coordinate: LatLng(10.7755, 106.7038), traits: { PlaceTrait.status, PlaceTrait.beautiful, PlaceTrait.private, PlaceTrait.clear, }, ), ]; } }