I have a mobile application, I show markers on Google Maps, then I open those markers and direct them to the search page with a search bar at the top. When the user searches for something and clicks on it, I want him to zoom in on the location of the neighborhood he clicked on and open the dialog, but I couldn't do it, I know it's a bit complicated but I need help.
class GoogleMapsScreen extends StatefulWidget {
const GoogleMapsScreen({super.key});
@override
State<GoogleMapsScreen> createState() => _GoogleMapsScreenState();
}
class _GoogleMapsScreenState extends State<GoogleMapsScreen> {
GoogleMapController? mapController;
Future<void> _goToCurrentLocation() async {
var currentLocation = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
mapController?.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: LatLng(currentLocation.latitude, currentLocation.longitude),
zoom: 17.0,
)));
}
final TextEditingController searchController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => MahallelerBloc()..add(LoadMahalleler()),
child: Scaffold(
body: BlocBuilder<MahallelerBloc, MahallelerState>(
builder: (context, state) {
if (state is MahallelerLoaded) {
return Stack(
children: [
GoogleMap(
myLocationButtonEnabled: false,
onMapCreated: (GoogleMapController controller) {
print('Harita oluşturuldu');
mapController = controller;
BlocProvider.of<MahallelerBloc>(context).mapController =
controller;
},
zoomControlsEnabled: false,
myLocationEnabled: true,
mapToolbarEnabled: false,
mapType: MapType.normal,
initialCameraPosition: const CameraPosition(
target: LatLng(37.3122872, 40.7339973),
zoom: 14,
),
markers: Set<Marker>.of(
state.markers.map((marker) {
return Marker(
markerId: marker.markerId,
position: marker.position,
icon: marker.icon,
onTap: () async {
final mahallelerState =
BlocProvider.of<MahallelerBloc>(context).state;
String seciliSecimID = "Varsayılan ID";
if (mahallelerState is MahallelerLoaded) {
seciliSecimID =
mahallelerState.selectedElection ??
"Varsayılan ID";
}
final markerIdValue = marker.markerId.value;
final bilgi = await FirestoreService()
.getMahalleVeIlceBilgisi(markerIdValue);
final secimSonuclari = await FirestoreService()
.getElectionResults(
markerIdValue, seciliSecimID);
final secimBilgileri = await FirestoreService()
.getElectionInfo(markerIdValue, seciliSecimID);
if (!mounted) {
return;
}
showDialog(
context: context,
builder: (context) {
List<String> siraliAdayIsimleri = [];
List<int> siraliOyOranlari = [];
for (var adayId in secimAdaySiralama) {
if (secimSonuclari.containsKey(adayId)) {
siraliAdayIsimleri.add(
secimAdayIsimleri[adayId] ??
"Bilinmeyen Aday");
siraliOyOranlari
.add(secimSonuclari[adayId]);
}
}
List<List<Color>> secilenGradientRenkler = [];
for (var adayIsim in siraliAdayIsimleri) {
var gradientRenk = adayRenkleri[adayIsim] ??
[Colors.grey, Colors.white];
secilenGradientRenkler.add(gradientRenk);
}
Map<String, String> duzenliSecimBilgileri = {};
for (var key in secimBilgileriSiralama) {
if (secimBilgileri.containsKey(key)) {
String yeniKey =
secimBilgilerikeys[key] ?? key;
String deger =
secimBilgileri[key].toString();
duzenliSecimBilgileri[yeniKey] = deger;
}
}
return PieChartDialog(
markerId: markerIdValue,
mahalleAdi: bilgi['mahalleAdi'],
ilceAdi: bilgi['ilceAdi'],
seciliSecim: secimAdlari[seciliSecimID] ??
"Bilinmeyen Seçim",
secimAdaylari: siraliAdayIsimleri,
secimSonuclari: siraliOyOranlari,
gradientColors: secilenGradientRenkler,
secimBilgileri: duzenliSecimBilgileri);
},
);
},
);
}),
),
),
Positioned(
right: 30,
bottom: 170,
child: FloatingActionButton(
heroTag: "konumum",
backgroundColor: Colors.white,
foregroundColor: const Color.fromRGBO(29, 66, 115, 1),
onPressed: _goToCurrentLocation,
child: const Icon(IconlyBold.location),
),
),
Positioned(
top: MediaQuery.of(context).padding.top + 20,
left: 20,
right: 20,
child: SearchTextField(
keyboardType: TextInputType.none,
showCursor: false,
controller: searchController,
prefixIconData: IconlyLight.search,
suffixIconData: IconlyLight.filter,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SearchScreen(
selectedElectionId:
state.selectedElection ?? 'BSB2019',
controller: searchController),
),
);
},
),
),
Positioned(
right: 30,
bottom: 110,
child: FloatingActionButton(
heroTag: "Seçimler",
backgroundColor: Colors.white,
foregroundColor: const Color.fromRGBO(29, 66, 115, 1),
child: const Icon(IconlyBroken.chart),
onPressed: () {
showElectionDialog(context);
},
),
),
],
);
} else if (state is MahallelerError) {
return const Center(child: Text('Bir hata oluştu.'));
}
return const Center(child: CircularProgressIndicator());
},
),
),
);
}
}
Search Screen
class SearchScreen extends StatefulWidget {
final TextEditingController controller;
final String selectedElectionId;
const SearchScreen({
super.key,
required this.controller,
required this.selectedElectionId,
});
@override
State<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final FocusNode _focusNode = FocusNode();
List<SearchResultCard> searchResults = [];
String currentSortingOption = "A'dan Z'ye";
@override
void initState() {
super.initState();
widget.controller.addListener(_onSearchChanged);
_fetchMahalleler(widget.selectedElectionId, currentSortingOption, '');
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_focusNode.canRequestFocus) {
_focusNode.requestFocus();
}
});
}
void _onSearchChanged() {
String searchQueryLowercase = widget.controller.text.toLowerCase();
_fetchMahalleler(
widget.selectedElectionId, currentSortingOption, searchQueryLowercase);
}
Future<void> _fetchMahalleler(
String selectedElection, String sortingOption, String searchQuery) async {
List<SearchResultCard> fetchedResults = [];
try {
Query query = FirebaseFirestore.instance.collection('mahalleler').where(
'secimler.$selectedElection.secimSonuclari',
isNotEqualTo: null);
if (searchQuery.isNotEmpty) {
query = query
.where('genelBilgiler.mahalleKoyKucukHarf',
isGreaterThanOrEqualTo: searchQuery)
.where('genelBilgiler.mahalleKoyKucukHarf',
isLessThanOrEqualTo: '$searchQuery\uf8ff');
}
var querySnapshot = await query.get();
List<DocumentSnapshot> docs = querySnapshot.docs;
List<Map<String, dynamic>> tempDocs =
docs.map((doc) => doc.data() as Map<String, dynamic>).toList();
if (sortingOption == "Z'den A'ya") {
tempDocs.sort((a, b) => _turkishStringCompare(
b['genelBilgiler']['mahalleKoyKucukHarf'],
a['genelBilgiler']['mahalleKoyKucukHarf']));
} else {
tempDocs.sort((a, b) => _turkishStringCompare(
a['genelBilgiler']['mahalleKoyKucukHarf'],
b['genelBilgiler']['mahalleKoyKucukHarf']));
}
for (var doc in docs) {
var data = doc.data() as Map<String, dynamic>;
var secimSonuclari = data['secimler'][selectedElection]['secimSonuclari']
as Map<String, dynamic>? ??
{};
List<Candidate> adaylar = secimAdaySiralama
.where((key) => secimSonuclari.containsKey(key))
.map((key) {
var adayAdi = secimAdayIsimleri[key]!;
var oySayisi = secimSonuclari[key];
var renkler = adayRenkleri[adayAdi] ?? [Colors.grey, Colors.grey];
return Candidate(
adayAdi: adayAdi, aldigiOy: oySayisi, gradient: LinearGradient(colors: renkler));
}).toList();
var enYuksekOyAlanPartiKey = secimSonuclari.entries
.reduce((a, b) => a.value > b.value ? a : b)
.key;
var markerIconPath = MarkerIconPaths.getIconPath(
selectedElection, enYuksekOyAlanPartiKey);
var konum = data['genelBilgiler']['konum'] as GeoPoint;
fetchedResults.add(SearchResultCard(
adaylar: adaylar,
mahalleId: doc.id, // Belge kimliği burada kullanılıyor
latitude: konum.latitude,
longitude: konum.longitude,
ilce: data['genelBilgiler']['ilceAdi'],
mahalle: data['genelBilgiler']['mahalleKoy'],
kazananinResmi: markerIconPath));
print('Adaylar: $adaylar, MahalleID: ${doc.id}, Latitude: ${konum.latitude}, Longitude: ${konum.longitude}, İlçe: ${data['genelBilgiler']['ilceAdi']}, Mahalle: ${data['genelBilgiler']['mahalleKoy']}, Kazananın Resmi: $markerIconPath');
}
setState(() {
searchResults = fetchedResults;
});
} catch (e) {
if (kDebugMode) {
print('Error fetching mahalleler: $e');
}
}
}
int _turkishStringCompare(String a, String b) {
const order = 'aAbBcCçÇdDeEfFgGğĞhHıIiİjJkKlLmMnNoOöÖpPrRsSşŞtTuUüÜvVyYzZ';
for (int i = 0; i < a.length && i < b.length; i++) {
final ai = order.indexOf(a[i]);
final bi = order.indexOf(b[i]);
if (ai != bi) return ai.compareTo(bi);
}
return a.length.compareTo(b.length);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 20,
left: 20,
right: 20,
),
child: SearchTextField(
controller: widget.controller,
focusNode: _focusNode,
clearTextOnSuffixIconTap: true,
returnOnSuffixIconTap: true,
prefixIconData: IconlyLight.search,
suffixIconData: CupertinoIcons.clear,
),
),
SearchFilterWidget(
onSelectedSorting: (selectedOption) {
setState(() {
currentSortingOption = selectedOption;
_fetchMahalleler(widget.selectedElectionId,
currentSortingOption, widget.controller.text);
});
},
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.only(top: 20),
itemCount: searchResults.length,
itemBuilder: (context, index) {
return searchResults[index];
},
),
),
],
),
);
}
}
Search Result Card
class SearchResultCard extends StatefulWidget {
final String kazananinResmi;
final String mahalle;
final String ilce;
final List<Candidate> adaylar;
// Yeni eklenen alanlar:
final String mahalleId;
final double latitude;
final double longitude;
const SearchResultCard({
super.key,
required this.kazananinResmi,
required this.mahalle,
required this.ilce,
required this.adaylar,
// Yeni eklenen alanların constructor içinde tanımlanması:
required this.mahalleId,
required this.latitude,
required this.longitude,
});
@override
SearchResultCardState createState() => SearchResultCardState();
}
class SearchResultCardState extends State<SearchResultCard> {
final ScrollController _scrollController = ScrollController();
Timer? _timer;
final double scrollAmount = 3.0;
final int scrollDelay = 100;
final Duration waitDuration = const Duration(seconds: 3);
void _startAutoScroll() {
_timer = Timer.periodic(Duration(milliseconds: scrollDelay), (timer) {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.offset;
if (currentScroll < maxScroll) {
_scrollController.animateTo(
currentScroll + scrollAmount,
duration: Duration(milliseconds: scrollDelay),
curve: Curves.linear,
);
} else {
_timer?.cancel();
_scrollController.jumpTo(0.0);
Future.delayed(waitDuration, () {
if (mounted) {
_startAutoScroll();
}
});
}
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _startAutoScroll());
}
@override
void dispose() {
_timer?.cancel();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print("Karta tıklandı: ${widget.mahalleId}");
// Burada, BLoC'a MahalleSecildi event'ini gönderin
BlocProvider.of<MahallelerBloc>(context).add(
MahalleSecildi(
mahalleId: widget.mahalleId,
latitude: widget.latitude,
longitude: widget.longitude,
),
);
Navigator.of(context).pop();
},
child: Card(
color: Colors.white,
surfaceTintColor: Colors.white,
shadowColor: const Color.fromRGBO(194, 217, 26, 1),
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
widget.kazananinResmi,
width: 45,
height: 45,
fit: BoxFit.cover,
),
const SizedBox(width: 8.0),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.mahalle,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 35,
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: _scrollController,
itemCount: widget.adaylar.length,
itemBuilder: (context, index) {
final candidate = widget.adaylar[index];
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 13,
height: 13,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: candidate.gradient,
),
),
const SizedBox(width: 5.0),
Text(
'${candidate.adayAdi}: ${candidate.aldigiOy}',
style: const TextStyle(fontSize: 14)),
],
),
);
},
),
),
Align(
alignment: Alignment.topRight,
child: Text(
widget.ilce,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black54,
),
),
),
],
),
),
],
),
),
),
);
}
}
class Candidate {
final String adayAdi;
final int aldigiOy;
final LinearGradient gradient;
Candidate({
required this.adayAdi,
required this.aldigiOy,
required this.gradient,
});
}
Bloc
class MahallelerBloc extends Bloc<MahallelerEvent, MahallelerState> {
String? selectedElection;
GoogleMapController? mapController; // Google Maps Controller'ı tanımla
MahallelerBloc() : super(MahallelerInitial()) {
on<LoadMahalleler>(_onLoadMahalleler);
on<SelectElection>(_onSelectElection);
on<MahalleSecildi>(_onMahalleSecildi); // Yeni event handler'ı ekle
_loadSelectedElection();
}
Future<void> _loadSelectedElection() async {
final prefs = await SharedPreferences.getInstance();
selectedElection = prefs.getString('selectedElection') ?? 'BSB2019';
add(LoadMahalleler());
}
Future<void> _onLoadMahalleler(
LoadMahalleler event, Emitter<MahallelerState> emit) async {
try {
final election = selectedElection ?? 'BSB2019';
final Set<Marker> markers = {};
var querySnapshot =
await FirebaseFirestore.instance.collection('mahalleler').get();
Map<String, String> elections = await _fetchElections();
for (var mahalleDoc in querySnapshot.docs) {
var secimSonuclari = mahalleDoc.data()['secimler'][election]
['secimSonuclari'] as Map<String, dynamic>?;
if (secimSonuclari != null) {
var enYuksekOyAlanParti = secimSonuclari.entries
.reduce((a, b) => a.value > b.value ? a : b)
.key;
String markerIconPath =
MarkerIconPaths.getIconPath(election, enYuksekOyAlanParti);
GeoPoint konum = mahalleDoc.data()['genelBilgiler']['konum'];
var markerId = MarkerId(mahalleDoc.id);
BitmapDescriptor icon = await getCustomIcon(markerIconPath);
var marker = Marker(
markerId: markerId,
icon: icon,
position: LatLng(konum.latitude, konum.longitude),
);
markers.add(marker);
}
}
emit(MahallelerLoaded(markers, elections, selectedElection: election));
} catch (e) {
emit(MahallelerError());
if (kDebugMode) {
print('Hata: $e');
}
}
}
Future<void> _onMahalleSecildi(MahalleSecildi event, Emitter<MahallelerState> emit) async {
try {
print('Zoom işlemi başlıyor...'); // İşlemin başladığını belirten log.
await mapController?.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(event.latitude, event.longitude),
zoom: 18.0,
),
),
);
print('Zoom işlemi tamamlandı.'); // İşlemin tamamlandığını belirten log.
} catch (e) {
print('Zoom işlemi sırasında bir hata oluştu: $e'); // Hata oluştuğunu belirten log.
}
// İsteğe bağlı: Seçilen mahalleyi işaretleyen bir marker ekleyin veya mevcut marker'ları güncelleyin
// Bu kısımda, marker listesini güncelleyebilir ve emit ile durumu güncelleyebilirsiniz.
}
Future<void> _onSelectElection(
SelectElection event, Emitter<MahallelerState> emit) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('selectedElection', event.electionId);
selectedElection = event.electionId;
final Set<Marker> markers = {};
var querySnapshot =
await FirebaseFirestore.instance.collection('mahalleler').get();
Map<String, String> elections = await _fetchElections();
for (var doc in querySnapshot.docs) {
var data = doc.data();
var secimSonuclari = data['secimler']?[event.electionId]
?['secimSonuclari'] as Map<String, dynamic>?;
if (secimSonuclari != null) {
var enYuksekOyAlanParti = secimSonuclari.entries
.reduce((a, b) => a.value > b.value ? a : b)
.key;
String markerIconPath = MarkerIconPaths.getIconPath(
selectedElection!, enYuksekOyAlanParti);
GeoPoint konum = data['genelBilgiler']['konum'];
var markerId = MarkerId(doc.id);
BitmapDescriptor icon = await getCustomIcon(markerIconPath);
var marker = Marker(
markerId: markerId,
icon: icon,
position: LatLng(konum.latitude, konum.longitude));
markers.add(marker);
}
}
emit(MahallelerLoaded(markers, elections,
selectedElection: event.electionId));
} catch (e) {
if (kDebugMode) {
print('Bir hata oluştu: $e');
}
emit(MahallelerError());
}
}
Future<Map<String, String>> _fetchElections() async {
DocumentSnapshot documentSnapshot = await FirebaseFirestore.instance
.collection('secimler')
.doc('secimler')
.get();
Map<String, dynamic>? data =
documentSnapshot.data() as Map<String, dynamic>?;
Map<String, String> elections = {};
if (data != null && data['secimListesi'] != null) {
Map<String, dynamic> secimListesi = data['secimListesi'];
secimListesi.forEach((key, value) {
elections[key] = value.toString();
});
}
return elections;
}
Future<BitmapDescriptor> getCustomIcon(String path) async {
final ByteData byteData = await rootBundle.load(path);
final Uint8List bytes = byteData.buffer.asUint8List();
return BitmapDescriptor.fromBytes(bytes);
}
}
abstract class MahallelerEvent {}
class LoadMahalleler extends MahallelerEvent {}
class SelectElection extends MahallelerEvent {
final String electionId;
SelectElection(this.electionId);
}
class MahalleSecildi extends MahallelerEvent {
final String mahalleId;
final double latitude;
final double longitude;
MahalleSecildi({required this.mahalleId, required this.latitude, required this.longitude});
}
abstract class MahallelerState {}
class MahallelerInitial extends MahallelerState {}
class MahallelerLoaded extends MahallelerState {
final Set<Marker> markers;
final Map<String, String> elections;
final String? selectedElection;
MahallelerLoaded(this.markers, this.elections, {this.selectedElection});
}
class MahallelerError extends MahallelerState {}
I applied all the solutions I know but I failed