130 lines
4.5 KiB
Dart
130 lines
4.5 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_core/firebase_core.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'friendship_service.dart';
|
|
import 'handle_validator.dart';
|
|
import 'rider_profile_models.dart';
|
|
import 'rider_profile_service.dart';
|
|
|
|
/// Search service for discovering riders by unique handle while respecting privacy controls.
|
|
class RiderSearchService {
|
|
final FirebaseFirestore? _firestore;
|
|
final RiderProfileService _profileService;
|
|
final FriendshipService _friendshipService;
|
|
|
|
RiderSearchService({
|
|
FirebaseFirestore? firestore,
|
|
RiderProfileService? profileService,
|
|
FriendshipService? friendshipService,
|
|
}) : _firestore = firestore ?? _resolveFirestore(),
|
|
_profileService = profileService ?? RiderProfileService(firestore: firestore),
|
|
_friendshipService = friendshipService ?? FriendshipService(firestore: firestore);
|
|
|
|
static FirebaseFirestore? _resolveFirestore() {
|
|
try {
|
|
if (Firebase.apps.isNotEmpty) return FirebaseFirestore.instance;
|
|
} catch (_) {}
|
|
return null;
|
|
}
|
|
|
|
/// Searches for a rider by exact or normalized handle.
|
|
/// Returns null if not found, blocked, or hidden by privacy settings.
|
|
Future<RiderSearchResult?> searchByHandle({
|
|
required String rawHandle,
|
|
required String callerUid,
|
|
}) async {
|
|
final validation = HandleValidator.validate(rawHandle);
|
|
if (!validation.isValid) return null;
|
|
|
|
final normalized = validation.normalizedHandle!;
|
|
final firestore = _firestore;
|
|
if (firestore == null) return null;
|
|
|
|
try {
|
|
// 1. Resolve handle to UID
|
|
final handleDoc =
|
|
await firestore.collection('handles').doc(normalized).get();
|
|
if (!handleDoc.exists || handleDoc.data() == null) return null;
|
|
|
|
final targetUid = handleDoc.data()!['uid'] as String?;
|
|
if (targetUid == null || targetUid.isEmpty) return null;
|
|
|
|
// 2. Self check
|
|
if (targetUid == callerUid) {
|
|
final profile = await _profileService.getProfile(callerUid);
|
|
if (profile == null) return null;
|
|
return RiderSearchResult(
|
|
uid: profile.uid,
|
|
handle: profile.handle,
|
|
displayName: profile.displayName,
|
|
bio: profile.bio,
|
|
region: profile.region,
|
|
avatarPreset: profile.avatarPreset,
|
|
avatarUrl: profile.avatarUrl,
|
|
isProfilePublic: profile.privacySettings.isProfilePublic,
|
|
allowFriendRequests: profile.privacySettings.allowFriendRequests,
|
|
relationship: FriendshipStatus.self,
|
|
);
|
|
}
|
|
|
|
// 3. Check blocks between caller and target
|
|
final callerBlockDoc = await firestore
|
|
.collection('users')
|
|
.doc(callerUid)
|
|
.collection('blocks')
|
|
.doc(targetUid)
|
|
.get();
|
|
if (callerBlockDoc.exists) return null;
|
|
|
|
final targetBlockDoc = await firestore
|
|
.collection('users')
|
|
.doc(targetUid)
|
|
.collection('blocks')
|
|
.doc(callerUid)
|
|
.get();
|
|
if (targetBlockDoc.exists) return null;
|
|
|
|
// 4. Fetch target profile
|
|
final targetProfile = await _profileService.getProfile(targetUid);
|
|
if (targetProfile == null) return null;
|
|
|
|
// 5. Determine relationship
|
|
final relationship = await _friendshipService.getRelationshipStatus(
|
|
callerUid,
|
|
targetUid,
|
|
);
|
|
|
|
// 6. Respect handle searchability privacy setting
|
|
final isFriend = relationship == FriendshipStatus.friend;
|
|
final hasPending = relationship == FriendshipStatus.requestIncoming ||
|
|
relationship == FriendshipStatus.requestOutgoing;
|
|
|
|
if (!targetProfile.privacySettings.isSearchableByHandle &&
|
|
!isFriend &&
|
|
!hasPending) {
|
|
return null;
|
|
}
|
|
|
|
// 7. Respect public/private profile setting
|
|
final isPublic = targetProfile.privacySettings.isProfilePublic;
|
|
final showFullProfile = isPublic || isFriend;
|
|
|
|
return RiderSearchResult(
|
|
uid: targetProfile.uid,
|
|
handle: targetProfile.handle,
|
|
displayName: targetProfile.displayName,
|
|
bio: showFullProfile ? targetProfile.bio : '',
|
|
region: showFullProfile ? targetProfile.region : '',
|
|
avatarPreset: targetProfile.avatarPreset,
|
|
avatarUrl: targetProfile.avatarUrl,
|
|
isProfilePublic: isPublic,
|
|
allowFriendRequests: targetProfile.privacySettings.allowFriendRequests,
|
|
relationship: relationship,
|
|
);
|
|
} catch (e) {
|
|
debugPrint('[RiderSearchService] Search error for $normalized: $e');
|
|
return null;
|
|
}
|
|
}
|
|
}
|