Use ontology traits in map UI
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 3m17s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 3m17s
This commit is contained in:
@@ -120,7 +120,7 @@ class MapflowApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final tag in tags) {
|
for (final tag in tags) {
|
||||||
final trait = _traitByName(tag.toString());
|
final trait = _traitByTag(tag.toString());
|
||||||
if (trait != null) {
|
if (trait != null) {
|
||||||
traits.add(trait);
|
traits.add(trait);
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,8 @@ class MapflowApi {
|
|||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaceTrait? _traitByName(String name) {
|
PlaceTrait? _traitByTag(String tag) {
|
||||||
|
final name = tag.contains(':') ? tag.split(':').last : tag;
|
||||||
for (final trait in PlaceTrait.values) {
|
for (final trait in PlaceTrait.values) {
|
||||||
if (trait.name == name) {
|
if (trait.name == name) {
|
||||||
return trait;
|
return trait;
|
||||||
|
|||||||
@@ -3,118 +3,48 @@ import 'package:latlong2/latlong.dart';
|
|||||||
|
|
||||||
enum PlaceTrait {
|
enum PlaceTrait {
|
||||||
calm,
|
calm,
|
||||||
alive,
|
dynamic,
|
||||||
cozy,
|
intimate,
|
||||||
cold,
|
|
||||||
status,
|
|
||||||
simple,
|
|
||||||
free,
|
|
||||||
formal,
|
|
||||||
private,
|
|
||||||
open,
|
open,
|
||||||
social,
|
|
||||||
solo,
|
solo,
|
||||||
beautiful,
|
group,
|
||||||
neutral,
|
reset,
|
||||||
unusual,
|
impress,
|
||||||
clear,
|
|
||||||
focused,
|
|
||||||
transit,
|
transit,
|
||||||
|
clean,
|
||||||
|
expressive,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaceTraitText on PlaceTrait {
|
extension PlaceTraitText on PlaceTrait {
|
||||||
String get label {
|
String get label {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
PlaceTrait.calm => 'спокойное',
|
PlaceTrait.calm => 'спокойное',
|
||||||
PlaceTrait.alive => 'живое',
|
PlaceTrait.dynamic => 'живое',
|
||||||
PlaceTrait.cozy => 'уютное',
|
PlaceTrait.intimate => 'камерное',
|
||||||
PlaceTrait.cold => 'холодное',
|
|
||||||
PlaceTrait.status => 'статусное',
|
|
||||||
PlaceTrait.simple => 'простое',
|
|
||||||
PlaceTrait.free => 'свободное',
|
|
||||||
PlaceTrait.formal => 'формальное',
|
|
||||||
PlaceTrait.private => 'приватное',
|
|
||||||
PlaceTrait.open => 'открытое',
|
PlaceTrait.open => 'открытое',
|
||||||
PlaceTrait.social => 'для общения',
|
|
||||||
PlaceTrait.solo => 'для себя',
|
PlaceTrait.solo => 'для себя',
|
||||||
PlaceTrait.beautiful => 'красивое',
|
PlaceTrait.group => 'для компании',
|
||||||
PlaceTrait.neutral => 'нейтральное',
|
PlaceTrait.reset => 'выдохнуть',
|
||||||
PlaceTrait.unusual => 'необычное',
|
PlaceTrait.impress => 'впечатлить',
|
||||||
PlaceTrait.clear => 'понятное',
|
|
||||||
PlaceTrait.focused => 'помогает собраться',
|
|
||||||
PlaceTrait.transit => 'транзитное',
|
PlaceTrait.transit => 'транзитное',
|
||||||
};
|
PlaceTrait.clean => 'чистое',
|
||||||
}
|
PlaceTrait.expressive => 'выразительное',
|
||||||
}
|
|
||||||
|
|
||||||
enum UserIntent { exhale, date, meet, focus, move, surprise, alone, impress }
|
|
||||||
|
|
||||||
extension UserIntentText on UserIntent {
|
|
||||||
String get title {
|
|
||||||
return switch (this) {
|
|
||||||
UserIntent.exhale => 'выдохнуть',
|
|
||||||
UserIntent.date => 'свидание',
|
|
||||||
UserIntent.meet => 'встретиться',
|
|
||||||
UserIntent.focus => 'поработать',
|
|
||||||
UserIntent.move => 'движ',
|
|
||||||
UserIntent.surprise => 'удивиться',
|
|
||||||
UserIntent.alone => 'побыть одному',
|
|
||||||
UserIntent.impress => 'привести кого-то',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String get subtitle {
|
|
||||||
return switch (this) {
|
|
||||||
UserIntent.exhale => 'тихо, мягко, без давления',
|
|
||||||
UserIntent.date => 'красиво и лично',
|
|
||||||
UserIntent.meet => 'общение без лишней формальности',
|
|
||||||
UserIntent.focus => 'собраться и не выпадать',
|
|
||||||
UserIntent.move => 'живо, шумно, с энергией',
|
|
||||||
UserIntent.surprise => 'не как обычно',
|
|
||||||
UserIntent.alone => 'быть в своем ритме',
|
|
||||||
UserIntent.impress => 'место должно держать момент',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
IconData get icon {
|
IconData get icon {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
UserIntent.exhale => Icons.air_outlined,
|
PlaceTrait.calm => Icons.air_outlined,
|
||||||
UserIntent.date => Icons.favorite_border,
|
PlaceTrait.dynamic => Icons.bolt_outlined,
|
||||||
UserIntent.meet => Icons.forum_outlined,
|
PlaceTrait.intimate => Icons.lock_outline,
|
||||||
UserIntent.focus => Icons.center_focus_strong_outlined,
|
PlaceTrait.open => Icons.public_outlined,
|
||||||
UserIntent.move => Icons.bolt_outlined,
|
PlaceTrait.solo => Icons.person_outline,
|
||||||
UserIntent.surprise => Icons.auto_awesome_outlined,
|
PlaceTrait.group => Icons.forum_outlined,
|
||||||
UserIntent.alone => Icons.person_outline,
|
PlaceTrait.reset => Icons.spa_outlined,
|
||||||
UserIntent.impress => Icons.diamond_outlined,
|
PlaceTrait.impress => Icons.diamond_outlined,
|
||||||
};
|
PlaceTrait.transit => Icons.near_me_outlined,
|
||||||
}
|
PlaceTrait.clean => Icons.wb_sunny_outlined,
|
||||||
|
PlaceTrait.expressive => Icons.auto_awesome_outlined,
|
||||||
Set<PlaceTrait> get traits {
|
|
||||||
return switch (this) {
|
|
||||||
UserIntent.exhale => {PlaceTrait.calm, PlaceTrait.cozy, PlaceTrait.solo},
|
|
||||||
UserIntent.date => {
|
|
||||||
PlaceTrait.private,
|
|
||||||
PlaceTrait.beautiful,
|
|
||||||
PlaceTrait.social,
|
|
||||||
},
|
|
||||||
UserIntent.meet => {PlaceTrait.social, PlaceTrait.free, PlaceTrait.alive},
|
|
||||||
UserIntent.focus => {
|
|
||||||
PlaceTrait.focused,
|
|
||||||
PlaceTrait.calm,
|
|
||||||
PlaceTrait.neutral,
|
|
||||||
},
|
|
||||||
UserIntent.move => {PlaceTrait.alive, PlaceTrait.open, PlaceTrait.social},
|
|
||||||
UserIntent.surprise => {
|
|
||||||
PlaceTrait.unusual,
|
|
||||||
PlaceTrait.alive,
|
|
||||||
PlaceTrait.open,
|
|
||||||
},
|
|
||||||
UserIntent.alone => {PlaceTrait.solo, PlaceTrait.free, PlaceTrait.calm},
|
|
||||||
UserIntent.impress => {
|
|
||||||
PlaceTrait.status,
|
|
||||||
PlaceTrait.beautiful,
|
|
||||||
PlaceTrait.private,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,14 +74,12 @@ class VoiceReviewDraft {
|
|||||||
required this.placeName,
|
required this.placeName,
|
||||||
required this.duration,
|
required this.duration,
|
||||||
required this.extractedTraits,
|
required this.extractedTraits,
|
||||||
required this.suggestedIntents,
|
|
||||||
required this.evidence,
|
required this.evidence,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String placeName;
|
final String placeName;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
final Set<PlaceTrait> extractedTraits;
|
final Set<PlaceTrait> extractedTraits;
|
||||||
final Set<UserIntent> suggestedIntents;
|
|
||||||
final List<String> evidence;
|
final List<String> evidence;
|
||||||
|
|
||||||
bool get isLongEnough => duration.inSeconds >= 30;
|
bool get isLongEnough => duration.inSeconds >= 30;
|
||||||
@@ -160,14 +88,12 @@ class VoiceReviewDraft {
|
|||||||
String? placeName,
|
String? placeName,
|
||||||
Duration? duration,
|
Duration? duration,
|
||||||
Set<PlaceTrait>? extractedTraits,
|
Set<PlaceTrait>? extractedTraits,
|
||||||
Set<UserIntent>? suggestedIntents,
|
|
||||||
List<String>? evidence,
|
List<String>? evidence,
|
||||||
}) {
|
}) {
|
||||||
return VoiceReviewDraft(
|
return VoiceReviewDraft(
|
||||||
placeName: placeName ?? this.placeName,
|
placeName: placeName ?? this.placeName,
|
||||||
duration: duration ?? this.duration,
|
duration: duration ?? this.duration,
|
||||||
extractedTraits: extractedTraits ?? this.extractedTraits,
|
extractedTraits: extractedTraits ?? this.extractedTraits,
|
||||||
suggestedIntents: suggestedIntents ?? this.suggestedIntents,
|
|
||||||
evidence: evidence ?? this.evidence,
|
evidence: evidence ?? this.evidence,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class _MapContent extends ConsumerWidget {
|
|||||||
SafeArea(
|
SafeArea(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: _IntentBar(intent: state.intent),
|
child: _TraitBar(selectedTrait: state.selectedTrait),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
@@ -187,10 +187,10 @@ class _MapError extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _IntentBar extends ConsumerWidget {
|
class _TraitBar extends ConsumerWidget {
|
||||||
const _IntentBar({required this.intent});
|
const _TraitBar({required this.selectedTrait});
|
||||||
|
|
||||||
final UserIntent intent;
|
final PlaceTrait selectedTrait;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -201,15 +201,15 @@ class _IntentBar extends ConsumerWidget {
|
|||||||
margin: const EdgeInsets.fromLTRB(10, 8, 10, 0),
|
margin: const EdgeInsets.fromLTRB(10, 8, 10, 0),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: UserIntent.values.length,
|
itemCount: PlaceTrait.values.length,
|
||||||
separatorBuilder: (_, _) => const SizedBox(width: 8),
|
separatorBuilder: (_, _) => const SizedBox(width: 8),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = UserIntent.values[index];
|
final item = PlaceTrait.values[index];
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
avatar: Icon(item.icon, size: 17),
|
avatar: Icon(item.icon, size: 17),
|
||||||
label: Text(item.title),
|
label: Text(item.label),
|
||||||
selected: item == intent,
|
selected: item == selectedTrait,
|
||||||
onSelected: (_) => controller.selectIntent(item),
|
onSelected: (_) => controller.selectTrait(item),
|
||||||
backgroundColor: const Color(0xFFFFFBF5),
|
backgroundColor: const Color(0xFFFFFBF5),
|
||||||
selectedColor: Theme.of(context).colorScheme.primaryContainer,
|
selectedColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||||
|
|||||||
@@ -9,23 +9,22 @@ final placeControllerProvider =
|
|||||||
|
|
||||||
class PlaceState {
|
class PlaceState {
|
||||||
const PlaceState({
|
const PlaceState({
|
||||||
required this.intent,
|
required this.selectedTrait,
|
||||||
required this.places,
|
required this.places,
|
||||||
required this.selectedPlaceId,
|
required this.selectedPlaceId,
|
||||||
required this.reviewDraft,
|
required this.reviewDraft,
|
||||||
});
|
});
|
||||||
|
|
||||||
final UserIntent intent;
|
final PlaceTrait selectedTrait;
|
||||||
final List<PlaceRecommendation> places;
|
final List<PlaceRecommendation> places;
|
||||||
final String? selectedPlaceId;
|
final String? selectedPlaceId;
|
||||||
final VoiceReviewDraft reviewDraft;
|
final VoiceReviewDraft reviewDraft;
|
||||||
|
|
||||||
List<PlaceRecommendation> get recommendations {
|
List<PlaceRecommendation> get recommendations {
|
||||||
final wanted = intent.traits;
|
|
||||||
final ranked = [...places]
|
final ranked = [...places]
|
||||||
..sort((a, b) {
|
..sort((a, b) {
|
||||||
final aScore = a.traits.intersection(wanted).length;
|
final aScore = a.traits.contains(selectedTrait) ? 1 : 0;
|
||||||
final bScore = b.traits.intersection(wanted).length;
|
final bScore = b.traits.contains(selectedTrait) ? 1 : 0;
|
||||||
return bScore.compareTo(aScore);
|
return bScore.compareTo(aScore);
|
||||||
});
|
});
|
||||||
return ranked.take(4).toList();
|
return ranked.take(4).toList();
|
||||||
@@ -41,13 +40,13 @@ class PlaceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlaceState copyWith({
|
PlaceState copyWith({
|
||||||
UserIntent? intent,
|
PlaceTrait? selectedTrait,
|
||||||
List<PlaceRecommendation>? places,
|
List<PlaceRecommendation>? places,
|
||||||
String? selectedPlaceId,
|
String? selectedPlaceId,
|
||||||
VoiceReviewDraft? reviewDraft,
|
VoiceReviewDraft? reviewDraft,
|
||||||
}) {
|
}) {
|
||||||
return PlaceState(
|
return PlaceState(
|
||||||
intent: intent ?? this.intent,
|
selectedTrait: selectedTrait ?? this.selectedTrait,
|
||||||
places: places ?? this.places,
|
places: places ?? this.places,
|
||||||
selectedPlaceId: selectedPlaceId ?? this.selectedPlaceId,
|
selectedPlaceId: selectedPlaceId ?? this.selectedPlaceId,
|
||||||
reviewDraft: reviewDraft ?? this.reviewDraft,
|
reviewDraft: reviewDraft ?? this.reviewDraft,
|
||||||
@@ -62,30 +61,29 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
|||||||
Future<PlaceState> build() async {
|
Future<PlaceState> build() async {
|
||||||
final places = await _api.fetchPlaces();
|
final places = await _api.fetchPlaces();
|
||||||
return PlaceState(
|
return PlaceState(
|
||||||
intent: UserIntent.exhale,
|
selectedTrait: PlaceTrait.calm,
|
||||||
places: places,
|
places: places,
|
||||||
selectedPlaceId: places.isEmpty ? null : places.first.id,
|
selectedPlaceId: places.isEmpty ? null : places.first.id,
|
||||||
reviewDraft: const VoiceReviewDraft(
|
reviewDraft: const VoiceReviewDraft(
|
||||||
placeName: '',
|
placeName: '',
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
extractedTraits: {},
|
extractedTraits: {},
|
||||||
suggestedIntents: {},
|
|
||||||
evidence: [],
|
evidence: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectIntent(UserIntent intent) {
|
void selectTrait(PlaceTrait trait) {
|
||||||
final value = state.requireValue;
|
final value = state.requireValue;
|
||||||
PlaceRecommendation? next;
|
PlaceRecommendation? next;
|
||||||
for (final place in value.places) {
|
for (final place in value.places) {
|
||||||
if (place.traits.intersection(intent.traits).isNotEmpty) {
|
if (place.traits.contains(trait)) {
|
||||||
next = place;
|
next = place;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state = AsyncData(
|
state = AsyncData(
|
||||||
value.copyWith(intent: intent, selectedPlaceId: next?.id),
|
value.copyWith(selectedTrait: trait, selectedPlaceId: next?.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +136,6 @@ class PlaceController extends AsyncNotifier<PlaceState> {
|
|||||||
placeName: '',
|
placeName: '',
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
extractedTraits: {},
|
extractedTraits: {},
|
||||||
suggestedIntents: {},
|
|
||||||
evidence: [],
|
evidence: [],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user