INFRA: Set Up Project.
This commit is contained in:
189
lib/widget/geoflutterfire/src/collection/base.dart
Normal file
189
lib/widget/geoflutterfire/src/collection/base.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../models/distance_doc_snapshot.dart';
|
||||
import '../models/point.dart';
|
||||
import '../utils/arrays.dart';
|
||||
import '../utils/math.dart';
|
||||
|
||||
class BaseGeoFireCollectionRef<T> {
|
||||
final Query<T> _collectionReference;
|
||||
late final Stream<QuerySnapshot<T>>? _stream;
|
||||
|
||||
BaseGeoFireCollectionRef(this._collectionReference) {
|
||||
_stream = _createStream(_collectionReference).shareReplay(maxSize: 1);
|
||||
}
|
||||
|
||||
/// return QuerySnapshot stream
|
||||
Stream<QuerySnapshot<T>>? snapshot() {
|
||||
return _stream;
|
||||
}
|
||||
|
||||
/// return the Document mapped to the [id]
|
||||
Stream<List<DocumentSnapshot<T>>> data(String id) {
|
||||
return _stream!.map((querySnapshot) {
|
||||
querySnapshot.docs.where((documentSnapshot) {
|
||||
return documentSnapshot.id == id;
|
||||
});
|
||||
return querySnapshot.docs;
|
||||
});
|
||||
}
|
||||
|
||||
/// add a document to collection with [data]
|
||||
Future<DocumentReference<T>> add(
|
||||
T data,
|
||||
) {
|
||||
try {
|
||||
final colRef = _collectionReference as CollectionReference<T>;
|
||||
return colRef.add(data);
|
||||
} catch (e) {
|
||||
throw Exception('cannot call add on Query, use collection reference instead');
|
||||
}
|
||||
}
|
||||
|
||||
/// delete document with [id] from the collection
|
||||
Future<void> delete(id) {
|
||||
try {
|
||||
CollectionReference colRef = _collectionReference as CollectionReference;
|
||||
return colRef.doc(id).delete();
|
||||
} catch (e) {
|
||||
throw Exception('cannot call delete on Query, use collection reference instead');
|
||||
}
|
||||
}
|
||||
|
||||
/// create or update a document with [id], [merge] defines whether the document should overwrite
|
||||
Future<void> setDoc(String id, Object? data, {bool merge = false}) {
|
||||
try {
|
||||
CollectionReference colRef = _collectionReference as CollectionReference;
|
||||
return colRef.doc(id).set(data, SetOptions(merge: merge));
|
||||
} catch (e) {
|
||||
throw Exception('cannot call set on Query, use collection reference instead');
|
||||
}
|
||||
}
|
||||
|
||||
/// set a geo point with [latitude] and [longitude] using [field] as the object key to the document with [id]
|
||||
Future<void> setPoint(
|
||||
String id,
|
||||
String field,
|
||||
double latitude,
|
||||
double longitude,
|
||||
) {
|
||||
try {
|
||||
CollectionReference colRef = _collectionReference as CollectionReference;
|
||||
var point = GeoFirePoint(latitude, longitude).data;
|
||||
return colRef.doc(id).set({field: point}, SetOptions(merge: true));
|
||||
} catch (e) {
|
||||
throw Exception('cannot call set on Query, use collection reference instead');
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
Stream<List<DocumentSnapshot<T>>> protectedWithin({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
required GeoPoint? Function(T t) geopointFrom,
|
||||
required bool? strictMode,
|
||||
}) =>
|
||||
protectedWithinWithDistance(
|
||||
center: center,
|
||||
radius: radius,
|
||||
field: field,
|
||||
geopointFrom: geopointFrom,
|
||||
strictMode: strictMode,
|
||||
).map((snapshots) => snapshots.map((snapshot) => snapshot.documentSnapshot).toList());
|
||||
|
||||
/// query firestore documents based on geographic [radius] from geoFirePoint [center]
|
||||
/// [field] specifies the name of the key in the document
|
||||
@protected
|
||||
Stream<List<DistanceDocSnapshot<T>>> protectedWithinWithDistance({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
required GeoPoint? Function(T t) geopointFrom,
|
||||
required bool? strictMode,
|
||||
}) {
|
||||
final nonNullStrictMode = strictMode ?? false;
|
||||
|
||||
final precision = MathUtils.setPrecision(radius);
|
||||
final centerHash = center.hash.substring(0, precision);
|
||||
final area = GeoFirePoint.neighborsOf(hash: centerHash)..add(centerHash);
|
||||
|
||||
final queries = area.map((hash) {
|
||||
final tempQuery = _queryPoint(hash, field);
|
||||
return _createStream(tempQuery).map((querySnapshot) {
|
||||
return querySnapshot.docs;
|
||||
});
|
||||
});
|
||||
|
||||
final mergedObservable = mergeObservable(queries);
|
||||
|
||||
final filtered = mergedObservable.map((list) {
|
||||
final mappedList = list.map((documentSnapshot) {
|
||||
final snapData = documentSnapshot.exists ? documentSnapshot.data() : null;
|
||||
|
||||
assert(snapData != null, 'Data in one of the docs is empty');
|
||||
if (snapData == null) return null;
|
||||
// We will handle it to fail gracefully
|
||||
|
||||
final geoPoint = geopointFrom(snapData);
|
||||
assert(geoPoint != null, 'Couldnt find geopoint from stored data');
|
||||
if (geoPoint == null) return null;
|
||||
// We will handle it to fail gracefully
|
||||
|
||||
final kmDistance = center.kmDistance(
|
||||
lat: geoPoint.latitude,
|
||||
lng: geoPoint.longitude,
|
||||
);
|
||||
return DistanceDocSnapshot(
|
||||
documentSnapshot: documentSnapshot,
|
||||
kmDistance: kmDistance,
|
||||
);
|
||||
});
|
||||
|
||||
final nullableFilteredList = nonNullStrictMode
|
||||
? mappedList
|
||||
.where((doc) => doc != null && doc.kmDistance <= radius * 1.02 // buffer for edge distances;
|
||||
)
|
||||
.toList()
|
||||
: mappedList.toList();
|
||||
final filteredList = nullableFilteredList.whereNotNull().toList();
|
||||
|
||||
filteredList.sort(
|
||||
(a, b) => (a.kmDistance * 1000).toInt() - (b.kmDistance * 1000).toInt(),
|
||||
);
|
||||
return filteredList;
|
||||
});
|
||||
return filtered.asBroadcastStream();
|
||||
}
|
||||
|
||||
Stream<List<QueryDocumentSnapshot<T>>> mergeObservable(
|
||||
Iterable<Stream<List<QueryDocumentSnapshot<T>>>> queries,
|
||||
) {
|
||||
final mergedObservable = Rx.combineLatest<List<QueryDocumentSnapshot<T>>, List<QueryDocumentSnapshot<T>>>(queries, (originalList) {
|
||||
final reducedList = <QueryDocumentSnapshot<T>>[];
|
||||
for (final t in originalList) {
|
||||
reducedList.addAll(t);
|
||||
}
|
||||
return reducedList;
|
||||
});
|
||||
return mergedObservable;
|
||||
}
|
||||
|
||||
/// INTERNAL FUNCTIONS
|
||||
|
||||
/// construct a query for the [geoHash] and [field]
|
||||
Query<T> _queryPoint(String geoHash, String field) {
|
||||
final end = '$geoHash~';
|
||||
final temp = _collectionReference;
|
||||
return temp.orderBy('$field.geohash').startAt([geoHash]).endAt([end]);
|
||||
}
|
||||
|
||||
/// create an observable for [ref], [ref] can be [Query] or [CollectionReference]
|
||||
Stream<QuerySnapshot<T>> _createStream(Query<T> ref) {
|
||||
return ref.snapshots();
|
||||
}
|
||||
}
|
||||
64
lib/widget/geoflutterfire/src/collection/default.dart
Normal file
64
lib/widget/geoflutterfire/src/collection/default.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:customer/widget/geoflutterfire/src/models/distance_doc_snapshot.dart';
|
||||
import 'package:customer/widget/geoflutterfire/src/models/point.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'base.dart';
|
||||
|
||||
class GeoFireCollectionRef extends BaseGeoFireCollectionRef<Map<String, dynamic>> {
|
||||
GeoFireCollectionRef(super.collectionReference);
|
||||
|
||||
Stream<List<DocumentSnapshot<Map<String, dynamic>>>> within({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
bool? strictMode,
|
||||
}) {
|
||||
return protectedWithin(
|
||||
center: center,
|
||||
radius: radius,
|
||||
field: field,
|
||||
geopointFrom: (snapData) => geopointFromMap(
|
||||
field: field,
|
||||
snapData: snapData,
|
||||
),
|
||||
strictMode: strictMode,
|
||||
);
|
||||
}
|
||||
|
||||
Stream<List<DistanceDocSnapshot<Map<String, dynamic>>>> withinWithDistance({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
bool? strictMode,
|
||||
}) {
|
||||
return protectedWithinWithDistance(
|
||||
center: center,
|
||||
radius: radius,
|
||||
field: field,
|
||||
geopointFrom: (snapData) => geopointFromMap(
|
||||
field: field,
|
||||
snapData: snapData,
|
||||
),
|
||||
strictMode: strictMode,
|
||||
);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static GeoPoint? geopointFromMap({
|
||||
required String field,
|
||||
required Map<String, dynamic> snapData,
|
||||
}) {
|
||||
// split and fetch geoPoint from the nested Map
|
||||
final fieldList = field.split('.');
|
||||
Map<dynamic, dynamic>? geoPointField = snapData[fieldList[0]];
|
||||
if (fieldList.length > 1) {
|
||||
for (int i = 1; i < fieldList.length; i++) {
|
||||
geoPointField = geoPointField?[fieldList[i]];
|
||||
}
|
||||
}
|
||||
return geoPointField?['geopoint'] as GeoPoint?;
|
||||
}
|
||||
}
|
||||
43
lib/widget/geoflutterfire/src/collection/with_converter.dart
Normal file
43
lib/widget/geoflutterfire/src/collection/with_converter.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:customer/widget/geoflutterfire/src/models/distance_doc_snapshot.dart';
|
||||
import 'package:customer/widget/geoflutterfire/src/models/point.dart';
|
||||
|
||||
import 'base.dart';
|
||||
|
||||
class GeoFireCollectionWithConverterRef<T> extends BaseGeoFireCollectionRef<T> {
|
||||
GeoFireCollectionWithConverterRef(super.collectionReference);
|
||||
|
||||
Stream<List<DocumentSnapshot<T>>> within({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
required GeoPoint Function(T) geopointFrom,
|
||||
bool? strictMode,
|
||||
}) {
|
||||
return protectedWithin(
|
||||
center: center,
|
||||
radius: radius,
|
||||
field: field,
|
||||
geopointFrom: geopointFrom,
|
||||
strictMode: strictMode,
|
||||
);
|
||||
}
|
||||
|
||||
Stream<List<DistanceDocSnapshot<T>>> withinWithDistance({
|
||||
required GeoFirePoint center,
|
||||
required double radius,
|
||||
required String field,
|
||||
required GeoPoint Function(T) geopointFrom,
|
||||
bool? strictMode,
|
||||
}) {
|
||||
return protectedWithinWithDistance(
|
||||
center: center,
|
||||
radius: radius,
|
||||
field: field,
|
||||
geopointFrom: geopointFrom,
|
||||
strictMode: strictMode,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user