MotorSignal-App/lib/services/social/rider_search_service.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;
}
}
}