234 lines
7.3 KiB
Dart
234 lines
7.3 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
import '../models/place_models.dart';
|
|
|
|
final placeControllerProvider = NotifierProvider<PlaceController, PlaceState>(
|
|
PlaceController.new,
|
|
);
|
|
|
|
class PlaceState {
|
|
const PlaceState({
|
|
required this.intent,
|
|
required this.places,
|
|
required this.selectedPlaceId,
|
|
required this.reviewDraft,
|
|
});
|
|
|
|
final UserIntent intent;
|
|
final List<PlaceRecommendation> places;
|
|
final String? selectedPlaceId;
|
|
final VoiceReviewDraft reviewDraft;
|
|
|
|
List<PlaceRecommendation> 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<PlaceRecommendation>? 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<PlaceState> {
|
|
@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<PlaceRecommendation> _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,
|
|
},
|
|
),
|
|
];
|
|
}
|
|
}
|