enhanced search by gemini

This commit is contained in:
2025-07-30 14:02:52 +02:00
parent 98381800fc
commit 911f4e82d9
2 changed files with 165 additions and 116 deletions

View File

@@ -1,20 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_business_profile_preview.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart';
class BuildFavouriteBusinessesList extends StatefulWidget { class BuildFavouriteBusinessesList extends StatefulWidget {
final List<BookmarkedBusiness> favouriteBusinesses; final List<Business?> favouriteBusinesses;
final String? myLocation; final String? myLocation;
final String? searchQuery;
const BuildFavouriteBusinessesList({ const BuildFavouriteBusinessesList({
super.key, super.key,
required this.favouriteBusinesses, required this.favouriteBusinesses,
required this.myLocation, required this.myLocation,
required this.searchQuery,
}); });
@override @override
@@ -24,28 +20,6 @@ class BuildFavouriteBusinessesList extends StatefulWidget {
class _BuildFavouriteBusinessesListState class _BuildFavouriteBusinessesListState
extends State<BuildFavouriteBusinessesList> { extends State<BuildFavouriteBusinessesList> {
List<Business?> businesses = [];
List<Business?> getListOfBusinesses() {
List<Business?> businesses = [];
for (var item in widget.favouriteBusinesses) {
MihBusinessDetailsServices()
.getBusinessDetailsByBusinessId(item.business_id)
.then((business) {
if (business != null) {
businesses.add(business);
}
});
}
return businesses;
}
@override
void initState() {
super.initState();
businesses = getListOfBusinesses();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.separated( return ListView.separated(
@@ -58,63 +32,37 @@ class _BuildFavouriteBusinessesListState
); );
}, },
itemBuilder: (context, index) { itemBuilder: (context, index) {
Future<Business?> businessDetails = final Business? business = widget.favouriteBusinesses[index];
MihBusinessDetailsServices().getBusinessDetailsByBusinessId(
widget.favouriteBusinesses[index].business_id, if (business == null) {
); return const SizedBox(); // Or a placeholder if a business couldn't be loaded
return FutureBuilder<Business?>( }
future: businessDetails,
builder: (context, snapshot) { return Material(
if (snapshot.connectionState == ConnectionState.waiting) { color: MzansiInnovationHub.of(context)!.theme.primaryColor(),
return Row( child: InkWell(
children: [ onTap: () {
Padding( Navigator.of(context).pushNamed(
padding: const EdgeInsets.only(left: 25.0), '/business-profile/view',
child: CircularProgressIndicator(), arguments: BusinessViewArguments(
), business,
], business.Name,
);
} else if (snapshot.hasError) {
return Center(
child: Text(
'Error: ${snapshot.error}',
style: TextStyle(color: Colors.red),
), ),
); );
} else if (snapshot.hasData && snapshot.data != null) { },
Business business = snapshot.data!; splashColor: MzansiInnovationHub.of(context)!
return Material( .theme
color: MzansiInnovationHub.of(context)!.theme.primaryColor(), .secondaryColor()
child: InkWell( .withOpacity(0.2),
onTap: () { borderRadius: BorderRadius.circular(15),
Navigator.of(context).pushNamed( child: Padding(
'/business-profile/view', padding: EdgeInsets.symmetric(
arguments: BusinessViewArguments( horizontal: 25,
business, ),
business.Name, child: MihBusinessProfilePreview(
), business: business, myLocation: widget.myLocation),
); ),
}, ),
splashColor: MzansiInnovationHub.of(context)!
.theme
.secondaryColor()
.withOpacity(0.2),
borderRadius: BorderRadius.circular(15),
child: Padding(
padding: EdgeInsetsGeometry.symmetric(
// vertical: 5,
horizontal: 25,
),
child: MihBusinessProfilePreview(
business: business, myLocation: widget.myLocation),
),
),
);
} else {
print(snapshot.data);
return SizedBox();
}
},
); );
}, },
); );

View File

@@ -1,11 +1,15 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/bookmarked_business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_search_bar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_search_bar.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_directory/builders/build_favourite_businesses_list.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_mzansi_directory_services.dart';
import 'package:supertokens_flutter/supertokens.dart'; import 'package:supertokens_flutter/supertokens.dart';
@@ -26,33 +30,55 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
final FocusNode searchFocusNode = FocusNode(); final FocusNode searchFocusNode = FocusNode();
late Future<List<BookmarkedBusiness>> boookmarkedBusinessListFuture; late Future<List<BookmarkedBusiness>> boookmarkedBusinessListFuture;
List<BookmarkedBusiness> listBookmarkedBusinesses = []; List<BookmarkedBusiness> listBookmarkedBusinesses = [];
final ValueNotifier<List<BookmarkedBusiness>> searchBookmarkedBusinesses = final ValueNotifier<List<Business?>> searchBookmarkedBusinesses =
ValueNotifier([]); ValueNotifier([]);
Future<List<BookmarkedBusiness>> getAllBookmarkedBusinessesForUser() async { late Future<Map<String, Business?>> businessDetailsMapFuture;
Map<String, Business?> _businessDetailsMap = {};
Timer? _debounce;
Future<Map<String, Business?>>
getAndMapAllBusinessDetailsForBookmarkedBusinesses() async {
String user_id = await SuperTokens.getUserId(); String user_id = await SuperTokens.getUserId();
return MihMzansiDirectoryServices().getAllUserBookmarkedBusiness(user_id); List<BookmarkedBusiness> bookmarked = await MihMzansiDirectoryServices()
.getAllUserBookmarkedBusiness(user_id);
// Store the bookmarked list for search filtering
listBookmarkedBusinesses = bookmarked;
Map<String, Business?> businessMap = {};
List<Future<Business?>> detailFutures = [];
for (var item in bookmarked) {
detailFutures.add(MihBusinessDetailsServices()
.getBusinessDetailsByBusinessId(item.business_id));
}
List<Business?> details = await Future.wait(detailFutures);
for (int i = 0; i < bookmarked.length; i++) {
businessMap[bookmarked[i].business_id] = details[i];
}
_businessDetailsMap = businessMap;
_filterAndSetBusinesses();
return businessMap;
} }
void searchBookmarkedBusinessByName() { void _filterAndSetBusinesses() {
if (businessSearchController.text.isEmpty) { List<Business?> businessesToDisplay = [];
searchBookmarkedBusinesses.value = listBookmarkedBusinesses; String query = businessSearchController.text.toLowerCase();
} else { for (var bookmarked in listBookmarkedBusinesses) {
List<BookmarkedBusiness> temp = []; if (bookmarked.business_name.toLowerCase().contains(query)) {
for (var item in listBookmarkedBusinesses) { if (_businessDetailsMap.containsKey(bookmarked.business_id)) {
if (item.business_name businessesToDisplay.add(_businessDetailsMap[bookmarked.business_id]);
.toLowerCase()
.contains(businessSearchController.text.toLowerCase())) {
temp.add(item);
} }
} }
searchBookmarkedBusinesses.value = temp;
} }
searchBookmarkedBusinesses.value = businessesToDisplay;
} }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
businessSearchController.removeListener(searchBookmarkedBusinessByName);
businessSearchController.dispose(); businessSearchController.dispose();
searchFocusNode.dispose(); searchFocusNode.dispose();
searchBookmarkedBusinesses.dispose(); searchBookmarkedBusinesses.dispose();
@@ -61,8 +87,16 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
boookmarkedBusinessListFuture = getAllBookmarkedBusinessesForUser(); businessDetailsMapFuture =
businessSearchController.addListener(searchBookmarkedBusinessByName); getAndMapAllBusinessDetailsForBookmarkedBusinesses();
businessSearchController.addListener(() {
if (_debounce?.isActive ?? false) {
_debounce!.cancel();
}
_debounce = Timer(const Duration(milliseconds: 200), () {
_filterAndSetBusinesses();
});
});
} }
@override @override
@@ -93,8 +127,8 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder( FutureBuilder<Map<String, Business?>>(
future: boookmarkedBusinessListFuture, future: businessDetailsMapFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return Mihloadingcircle( return Mihloadingcircle(
@@ -102,18 +136,82 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
); );
} else if (snapshot.connectionState == ConnectionState.done) { } else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData && snapshot.data!.isNotEmpty) {
listBookmarkedBusinesses = snapshot.data!; // No need to re-filter here, _filterAndSetBusinesses is called in initState
searchBookmarkedBusinessByName(); // and by the text controller listener.
return ValueListenableBuilder( return ValueListenableBuilder<List<Business?>>(
valueListenable: searchBookmarkedBusinesses, valueListenable:
builder: (context, value, child) { searchBookmarkedBusinesses, // Listen to changes in this
return BuildFavouriteBusinessesList( builder: (context, businesses, child) {
favouriteBusinesses: value, // Display message if no results after search
myLocation: widget.myLocation, if (businesses.isEmpty &&
searchQuery: businessSearchController.text, businessSearchController.text.isNotEmpty) {
return Column(
children: [
const SizedBox(height: 50),
Icon(
Icons
.search_off_rounded, // A different icon for "no results"
size: 150,
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0),
child: SizedBox(
width: 500,
child: Text(
"No businesses found for '${businessSearchController.text}'", // Specific message for no search results
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
); );
}); } else if (businesses.isEmpty) {
// Initial empty state
return Column(
children: [
const SizedBox(height: 50),
Icon(
Icons.business_center_rounded,
size: 150,
color: MzansiInnovationHub.of(context)!
.theme
.secondaryColor(),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0),
child: SizedBox(
width: 500,
child: Text(
"No favourites yet, use Mzansi Search to find and bookmark businesses you like",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
return BuildFavouriteBusinessesList(
favouriteBusinesses:
businesses, // Pass the filtered list from ValueNotifier
myLocation: widget.myLocation,
);
},
);
} else { } else {
// This block handles the case where there are no bookmarked businesses initially
return Column( return Column(
children: [ children: [
const SizedBox(height: 50), const SizedBox(height: 50),
@@ -140,13 +238,16 @@ class _MihFavouriteBusinessesState extends State<MihFavouriteBusinesses> {
), ),
], ],
); );
// return Center(
// child: Text("No bookmarked businesses found"),
// );
} }
} else { } else if (snapshot.hasError) {
return Center( return Center(
child: Text("Error loading bookmarked businesses"), child: Text(
"Error loading bookmarked businesses: ${snapshot.error}"), // Show specific error
);
} else {
// Fallback for unexpected states
return Center(
child: Text("An unknown error occurred."),
); );
} }
}), }),