Skip to content

Commit 1d5a506

Browse files
Feature/for you (#45)
* for you * fix errors
1 parent d49d37f commit 1d5a506

11 files changed

Lines changed: 481 additions & 6 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
enum CuisineType {
2+
italian,
3+
chinese,
4+
mexican,
5+
indian,
6+
japanese,
7+
thai,
8+
french,
9+
mediterranean,
10+
vegan,
11+
local,
12+
}
13+
14+
extension CuisineTypeExtension on CuisineType {
15+
String get displayName {
16+
switch (this) {
17+
case CuisineType.italian:
18+
return 'Italian';
19+
case CuisineType.chinese:
20+
return 'Chinese';
21+
case CuisineType.mexican:
22+
return 'Mexican';
23+
case CuisineType.indian:
24+
return 'Indian';
25+
case CuisineType.japanese:
26+
return 'Japanese';
27+
case CuisineType.thai:
28+
return 'Thai';
29+
case CuisineType.french:
30+
return 'French';
31+
case CuisineType.mediterranean:
32+
return 'Mediterranean';
33+
case CuisineType.vegan:
34+
return 'Vegan';
35+
case CuisineType.local:
36+
return 'Local';
37+
}
38+
}
39+
40+
static CuisineType fromString(String cuisine) {
41+
return CuisineType.values.firstWhere(
42+
(CuisineType e) =>
43+
e.toString().split('.').last.toLowerCase() == cuisine.toLowerCase(),
44+
orElse: () => CuisineType.local,
45+
);
46+
}
47+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// ignore_for_file: sort_unnamed_constructors_first
2+
3+
import 'package:eco_bites/features/food/domain/models/cuisine_type.dart';
4+
import 'package:eco_bites/features/food/domain/models/offer.dart';
5+
6+
class FoodBusiness {
7+
factory FoodBusiness.fromMap(Map<String, dynamic> map, String id) {
8+
return FoodBusiness(
9+
id: id,
10+
name: map['name'] as String,
11+
cuisineType: CuisineType.values.firstWhere(
12+
(CuisineType e) => e.displayName == map['cuisineType'],
13+
orElse: () => CuisineType.local,
14+
),
15+
latitude: (map['latitude'] as num).toDouble(),
16+
longitude: (map['longitude'] as num).toDouble(),
17+
offers: (map['offers'] as List<dynamic>)
18+
.map(
19+
// ignore: always_specify_types
20+
(offerMap) => Offer.fromMap(
21+
offerMap as Map<String, dynamic>,
22+
offerMap['id'] as String,
23+
id,
24+
),
25+
)
26+
.toList(),
27+
imageUrl: map['imageUrl'] as String?,
28+
);
29+
}
30+
FoodBusiness({
31+
required this.id,
32+
required this.name,
33+
required this.cuisineType,
34+
required this.latitude,
35+
required this.longitude,
36+
required this.offers,
37+
this.imageUrl,
38+
});
39+
final String id;
40+
final String name;
41+
final CuisineType cuisineType;
42+
final double latitude;
43+
final double longitude;
44+
final List<Offer> offers;
45+
final String? imageUrl;
46+
47+
Map<String, dynamic> toMap() {
48+
return <String, dynamic>{
49+
'name': name,
50+
'cuisineType': cuisineType.displayName,
51+
'latitude': latitude,
52+
'longitude': longitude,
53+
'offers': offers.map((Offer offer) => offer.toMap()).toList(),
54+
'imageUrl': imageUrl,
55+
};
56+
}
57+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'package:cloud_firestore/cloud_firestore.dart';
2+
import 'package:eco_bites/features/food/domain/models/cuisine_type.dart';
3+
import 'package:eco_bites/features/food/domain/models/offer_type.dart';
4+
5+
class Offer {
6+
Offer({
7+
required this.id,
8+
required this.type,
9+
required this.cuisineType,
10+
required this.description,
11+
required this.availableQuantity,
12+
required this.normalPrice,
13+
required this.offerPrice,
14+
required this.validUntil,
15+
required this.businessId,
16+
this.imageUrl,
17+
});
18+
final String id;
19+
final OfferType type;
20+
final CuisineType cuisineType;
21+
final String businessId;
22+
final String description;
23+
final int availableQuantity;
24+
final double normalPrice;
25+
final double offerPrice;
26+
final DateTime validUntil;
27+
final String? imageUrl;
28+
29+
Map<String, dynamic> toMap() {
30+
return <String, dynamic>{
31+
'type': type.index,
32+
'description': description,
33+
'availableQuantity': availableQuantity,
34+
'normalPrice': normalPrice,
35+
'offerPrice': offerPrice,
36+
'validUntil': validUntil.toIso8601String(),
37+
'cuisineType': cuisineType.index,
38+
'businessId': businessId,
39+
'imageUrl': imageUrl,
40+
};
41+
}
42+
43+
static Offer fromMap(Map<String, dynamic> map, String id, String businessId) {
44+
return Offer(
45+
id: id,
46+
type: OfferType.values.firstWhere(
47+
(OfferType e) => e.toString().split('.').last == map['type'],
48+
orElse: () => OfferType.values.first,
49+
),
50+
cuisineType: CuisineType.values.firstWhere(
51+
(CuisineType e) => e.toString().split('.').last == map['cuisineType'],
52+
orElse: () => CuisineType.values.first,
53+
),
54+
description: map['description'] as String,
55+
availableQuantity: map['availableQuantity'] as int,
56+
normalPrice: (map['normalPrice'] as num).toDouble(),
57+
offerPrice: (map['offerPrice'] as num).toDouble(),
58+
validUntil: (map['validUntil'] as Timestamp).toDate(),
59+
businessId: businessId,
60+
imageUrl: map['imageUrl'] as String?,
61+
);
62+
}
63+
64+
// Add a method to convert the offer to a cart item
65+
Map<String, dynamic> toCartItem() {
66+
return <String, dynamic>{
67+
'id': id,
68+
'type': type.index,
69+
'description': description,
70+
'price': offerPrice,
71+
'businessId': businessId,
72+
'imageUrl': imageUrl,
73+
};
74+
}
75+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
enum OfferType {
2+
restaurant,
3+
ingredient,
4+
store,
5+
diary,
6+
drink,
7+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'package:eco_bites/features/address/domain/models/address.dart';
2+
import 'package:eco_bites/features/address/presentation/bloc/address_bloc.dart';
3+
import 'package:eco_bites/features/address/presentation/bloc/address_state.dart';
4+
import 'package:eco_bites/features/food/domain/models/food_business.dart';
5+
import 'package:eco_bites/features/food/presentation/bloc/food_business_event.dart';
6+
import 'package:eco_bites/features/food/presentation/bloc/food_business_state.dart';
7+
import 'package:eco_bites/features/food/repository/food_business_repository.dart';
8+
import 'package:flutter_bloc/flutter_bloc.dart';
9+
10+
class FoodBusinessBloc extends Bloc<FoodBusinessEvent, FoodBusinessState> {
11+
FoodBusinessBloc({
12+
required this.foodBusinessRepository,
13+
required this.addressBloc,
14+
}) : super(FoodBusinessInitial()) {
15+
on<FetchSurplusFoodBusinesses>(_onFetchSurplusFoodBusinesses);
16+
}
17+
final FoodBusinessRepository foodBusinessRepository;
18+
final AddressBloc addressBloc;
19+
20+
Future<void> _onFetchSurplusFoodBusinesses(
21+
FetchSurplusFoodBusinesses event,
22+
Emitter<FoodBusinessState> emit,
23+
) async {
24+
emit(FoodBusinessLoading());
25+
26+
try {
27+
final AddressState addressState = addressBloc.state;
28+
29+
if (addressState is AddressLoaded) {
30+
final Address userLocation = addressState.savedAddress;
31+
32+
final List<FoodBusiness> foodBusinesses =
33+
await foodBusinessRepository.fetchNearbySurplusFoodBusinesses(
34+
userLocation: userLocation,
35+
favoriteCuisine: event.favoriteCuisine,
36+
);
37+
38+
emit(FoodBusinessLoaded(foodBusinesses: foodBusinesses));
39+
} else {
40+
emit(
41+
const FoodBusinessError(
42+
message: 'User address not available. Please set your address.',
43+
),
44+
);
45+
}
46+
} catch (e) {
47+
emit(FoodBusinessError(message: e.toString()));
48+
}
49+
}
50+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:eco_bites/features/food/domain/models/cuisine_type.dart';
2+
import 'package:equatable/equatable.dart';
3+
4+
abstract class FoodBusinessEvent extends Equatable {
5+
const FoodBusinessEvent();
6+
7+
@override
8+
List<Object?> get props => <Object?>[];
9+
}
10+
11+
class FetchSurplusFoodBusinesses extends FoodBusinessEvent {
12+
const FetchSurplusFoodBusinesses({required this.favoriteCuisine});
13+
final CuisineType favoriteCuisine;
14+
15+
@override
16+
List<Object?> get props => <Object?>[favoriteCuisine];
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'package:eco_bites/features/food/domain/models/food_business.dart';
2+
import 'package:equatable/equatable.dart';
3+
4+
abstract class FoodBusinessState extends Equatable {
5+
const FoodBusinessState();
6+
7+
@override
8+
List<Object?> get props => <Object?>[];
9+
}
10+
11+
class FoodBusinessInitial extends FoodBusinessState {}
12+
13+
class FoodBusinessLoading extends FoodBusinessState {}
14+
15+
class FoodBusinessLoaded extends FoodBusinessState {
16+
const FoodBusinessLoaded({required this.foodBusinesses});
17+
final List<FoodBusiness> foodBusinesses;
18+
19+
@override
20+
List<Object?> get props => <Object?>[foodBusinesses];
21+
}
22+
23+
class FoodBusinessError extends FoodBusinessState {
24+
const FoodBusinessError({required this.message});
25+
final String message;
26+
27+
@override
28+
List<Object?> get props => <Object?>[message];
29+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// ignore_for_file: avoid_print
2+
3+
import 'package:cloud_firestore/cloud_firestore.dart';
4+
import 'package:eco_bites/core/utils/distance.dart';
5+
import 'package:eco_bites/features/address/domain/models/address.dart';
6+
import 'package:eco_bites/features/food/domain/models/cuisine_type.dart';
7+
import 'package:eco_bites/features/food/domain/models/food_business.dart';
8+
import 'package:eco_bites/features/food/domain/models/offer.dart';
9+
10+
class FoodBusinessRepository {
11+
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
12+
13+
Future<List<FoodBusiness>> fetchNearbySurplusFoodBusinesses({
14+
required Address userLocation,
15+
required CuisineType favoriteCuisine,
16+
double distanceInKm = 5.0, // Default search radius
17+
}) async {
18+
try {
19+
// Fetch restaurants with the favorite cuisine
20+
print('==> favoriteCuisine: $favoriteCuisine');
21+
// favorite cuisine name
22+
print('==> favoriteCuisine: ${favoriteCuisine.name}');
23+
print('==> userLocation: $userLocation');
24+
print('==> distanceInKm: $distanceInKm');
25+
print('==> fetching nearby surplus food businesses');
26+
final QuerySnapshot<Map<String, dynamic>> foodBusinessSnapshot =
27+
await _firestore
28+
.collection('foddBusiness')
29+
.where('cuisineType', isEqualTo: favoriteCuisine.name)
30+
.get();
31+
32+
print('==> foodBusinessSnapshot: ${foodBusinessSnapshot.docs}');
33+
34+
final List<FoodBusiness> nearbyFoodBusinesses = <FoodBusiness>[];
35+
36+
for (final QueryDocumentSnapshot<Map<String, dynamic>> doc
37+
in foodBusinessSnapshot.docs) {
38+
final QuerySnapshot<Map<String, dynamic>> offersSnapshot =
39+
await _firestore
40+
.collection('foddBusiness')
41+
.doc(doc.id)
42+
.collection('offers')
43+
.where('availableQuantity', isGreaterThan: 0)
44+
.where('validUntil', isGreaterThanOrEqualTo: Timestamp.now())
45+
.get();
46+
47+
print('==> query snapshot: $offersSnapshot');
48+
print('==> offersSnapshot.docs: ${offersSnapshot.docs}');
49+
50+
final List<Offer> surplusOffers = offersSnapshot.docs
51+
.map((QueryDocumentSnapshot<Map<String, dynamic>> offerDoc) {
52+
return Offer.fromMap(offerDoc.data(), offerDoc.id, doc.id);
53+
}).toList();
54+
55+
if (surplusOffers.isNotEmpty) {
56+
final double distance = calculateDistance(
57+
userLocation.latitude,
58+
userLocation.longitude,
59+
(doc.data()['latitude'] as num?)?.toDouble() ?? 0.0,
60+
(doc.data()['longitude'] as num?)?.toDouble() ?? 0.0,
61+
);
62+
63+
if (distance <= distanceInKm) {
64+
final FoodBusiness foodBusiness = FoodBusiness(
65+
id: doc.id,
66+
name: doc.data()['name'] ?? '',
67+
cuisineType: CuisineTypeExtension.fromString(
68+
doc.data()['cuisineType'] ?? 'local',
69+
),
70+
latitude: (doc.data()['latitude'] as num?)?.toDouble() ?? 0.0,
71+
longitude: (doc.data()['longitude'] as num?)?.toDouble() ?? 0.0,
72+
offers: surplusOffers,
73+
);
74+
75+
nearbyFoodBusinesses.add(foodBusiness);
76+
}
77+
}
78+
}
79+
80+
return nearbyFoodBusinesses;
81+
} catch (e) {
82+
print('Error fetching nearby surplus food businesses: $e');
83+
return <FoodBusiness>[];
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)