Skip to content

Commit

Permalink
Bugfix/ 매치스크린 에러 해결 및 리펙토링 (#84)
Browse files Browse the repository at this point in the history
Co-authored-by: Jungwoo <[email protected]>
  • Loading branch information
ottuck and jwson-automation authored Aug 31, 2024
1 parent 605fd3e commit b8cc023
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 122 deletions.
5 changes: 1 addition & 4 deletions lib/feature/match/MatchScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import 'widget/MatchProfileListWidget.dart';
import 'widget/MatchFilterWidget.dart';

/// MatchScreen - 프로필 스와이프 매칭 화면
///
/// 주요 구성 요소:
/// - SwipeCardWidget: 프로필 카드를 스와이프할 수 있는 위젯
/// - SwipeButtonWidget: 수동으로 좌/우 스와이프를 할 수 있는 버튼
/// 완성 8월 18일 상현
class MatchScreen extends StatelessWidget {
static const String name = 'MatchScreen';
Expand Down
3 changes: 3 additions & 0 deletions lib/feature/match/ProfileScreen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import 'package:flutter/material.dart';
import 'package:blueberry_flutter_template/model/PetProfileModel.dart';
import '../../utils/AppStrings.dart';

/// ProfileScreen - ProfileDetail 스크린으로 대체할 임시 화면
/// 완성 8월 18일 상현
class ProfileScreen extends StatelessWidget {
final PetProfileModel petProfile;

Expand Down
247 changes: 156 additions & 91 deletions lib/feature/match/provider/MatchProvider.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../model/PetProfileModel.dart';
Expand All @@ -12,144 +12,209 @@ class MatchScreenNotifier extends StateNotifier<List<PetProfileModel>> {
}

bool isLoading = false;
String? errorMessage;
static const userId = "eztqDqrvEXDc8nqnnrB8"; // 로그인 상황을 가정한 userId
static const userId = "eztqDqrvEXDc8nqnnrB8"; // 로그인 상황을 가정

// 펫 데이터를 로드하고 필터링하는 함수
Future<void> loadPets({String? location, String? gender}) async {
// 상태 초기화
state = [];
isLoading = true;

final firestore = FirebaseFirestore.instance;
final userDoc = await firestore.collection('users_test').doc(userId).get();

List<dynamic> ignoredPets = [];
if (userDoc.exists) {
ignoredPets = userDoc.data()?['ignoredPets'] ?? [];
}
_setLoading(true);

try {
// 모든 펫 정보를 가져오기
final snapshot = await firestore.collection('pet').get();
final pets = snapshot.docs
.map((doc) => PetProfileModel.fromJson(doc.data()))
.toList();
final ignoredPets = await _getIgnoredPets();
final pets = await _getPetsFromFirestore();

// 매칭 조건 필터 적용
List<PetProfileModel> filteredPets = pets.where((pet) {
final matchesLocation = location == null || pet.location == location;
final matchesGender = gender == null || pet.gender == gender;
final notIgnored = !ignoredPets.contains(pet.petID);
return matchesLocation && matchesGender && notIgnored;
final filteredPets = pets.where((pet) {
return _matchesFilter(pet, ignoredPets, location, gender);
}).toList();

// 필터링된 결과가 있을 경우 상태를 업데이트
if (filteredPets.isNotEmpty) {
state = filteredPets;
} else {
// 상태를 업데이트하여 필터된 펫 데이터를 설정
state = filteredPets.isNotEmpty ? filteredPets : [];

if (filteredPets.isEmpty) {
talker.info(AppStrings.noFilteredResult);
}
} catch (e) {
talker.error('${AppStrings.dbLoadError}$e');
} finally {
isLoading = false;
_setLoading(false);
}
}

void _setLoading(bool value) {
isLoading = value;
}

// ignore한 펫 데이터 가져오기
Future<List<dynamic>> _getIgnoredPets() async {
final userDoc = await FirebaseFirestore.instance
.collection('users_test')
.doc(userId)
.get();
final data = userDoc.data();

if (data != null && data.containsKey('ignoredPets')) {
return data['ignoredPets'] ?? [];
} else {
return [];
}
}

// Firebase DB field 명을 받아서 해당 필드에 petId를 추가하는 함수
Future<void> _updatePetList(
String userId, String petId, String fieldName) async {
// 모든 pet 데이터 가져오기
Future<List<PetProfileModel>> _getPetsFromFirestore() async {
final snapshot = await FirebaseFirestore.instance.collection('pet').get();
return snapshot.docs
.map((doc) => PetProfileModel.fromJson(doc.data()))
.toList();
}

// 조건에 따라 펫 데이터 필터링
bool _matchesFilter(PetProfileModel pet, List<dynamic> ignoredPets,
String? location, String? gender) {
final matchesLocation = location == null || pet.location == location;
final matchesGender = gender == null || pet.gender == gender;
final notIgnored = !ignoredPets.contains(pet.petID);
final notMyPet = pet.ownerUserID != userId;
return matchesLocation && matchesGender && notIgnored && notMyPet;
}

// 펫 좋아요 기능
Future<void> addPetToLikes(
BuildContext context, String userId, String petId) async {
await _updatePetList(context, userId, petId, 'likedPets');
_checkForMatchAndAddFriend(context, userId, petId, 'like');
}

// 펫 즐겨찾기 기능
Future<void> addPetToSuperLikes(
BuildContext context, String userId, String petId) async {
await _updatePetList(context, userId, petId, 'superLikedPets');
_checkForMatchAndAddFriend(context, userId, petId, 'superlike');
}

// 펫 추천 안함 기능
Future<void> addPetToIgnored(
BuildContext context, String userId, String petId) async {
await _updatePetList(context, userId, petId, 'ignoredPets');
loadPets();
_showSnackbar(context, AppStrings.ignoreSuccessMessage);
}

// 특정 필드에 펫 ID 추가 기능
Future<void> _updatePetList(BuildContext context, String userId, String petId,
String fieldName) async {
final firestore = FirebaseFirestore.instance;
final userDoc = firestore.collection('users_test').doc(userId);
final snapshot = await userDoc.get();
List<dynamic> petList = snapshot.data()![fieldName];

try {
final snapshot = await userDoc.get();
List<dynamic> petList = snapshot.data()![fieldName];

if (!petList.contains(petId)) {
petList.add(petId);
await userDoc.update({
fieldName: petList,
});
talker.info("${AppStrings.dbUpdateSuccess}: $petList");
} else {
talker.info(AppStrings.dbUpdateFail);
}
} catch (e) {
talker.error('${AppStrings.dbUpdateError}$e');
if (!petList.contains(petId)) {
petList.add(petId);
await userDoc.update({fieldName: petList});
}
}

Future<void> addPetToLikes(String userId, String petId) async {
talker.info("match provider petId: $petId");
await _updatePetList(userId, petId, 'likedPets');
await _checkForMatch(userId, petId);
// 펫 좋아요 후 매칭 여부 확인
Future<bool> _isMatchFound(String userId, String petId) async {
final petOwnerId = await _getPetOwnerId(petId);
final likedPetsByOwner = await _getLikedPets(petOwnerId);
final myPets = await _getMyPets(userId);

// 매칭된 경우 반환
return likedPetsByOwner.contains(myPets[0]);
}

Future<void> addPetToSuperLikes(String userId, String petId) async {
await _updatePetList(userId, petId, 'superLikedPets');
await _checkForMatch(userId, petId);
// 펫 소유자의 ID 가져오기
Future<String> _getPetOwnerId(String petId) async {
final firestore = FirebaseFirestore.instance;
final petDoc = await firestore.collection('pet').doc(petId).get();
return petDoc.data()!['ownerUserID'];
}

Future<void> addPetToIgnored(String userId, String petId) async {
await _updatePetList(userId, petId, 'ignoredPets');
loadPets(); // 무시한 펫이 카드에 안나오도록 데이터를 리로드
// 특정 사용자의 좋아요 목록 가져오기
Future<List<dynamic>> _getLikedPets(String userId) async {
final firestore = FirebaseFirestore.instance;
final userDoc = await firestore.collection('users_test').doc(userId).get();
return userDoc.data()!['likedPets'] ?? [];
}

Future<void> _checkForMatch(String userId, String petId) async {
// 하트를 누른 펫의 petOwnerId 가져오기
// 특정 사용자의 펫 목록 가져오기
Future<List<dynamic>> _getMyPets(String userId) async {
final firestore = FirebaseFirestore.instance;
final petDoc = await firestore.collection('pet').doc(petId).get();
final petOwnerId = petDoc.data()!['ownerUserID'];

// 상대방의 likedPets 목록 가져오기
final userDoc =
await firestore.collection('users_test').doc(petOwnerId).get();
List<dynamic> likedPetsByOwner = userDoc.data()!['likedPets'] ?? [];

// 내 Pets 목록 가져오기 (로그인 가능해진 후에는 내 펫 정보를 가져오는 방법을 변경해야 함)
final myUserDoc =
await firestore.collection('users_test').doc(userId).get();
List<dynamic> myPets = myUserDoc.data()!['pets'] ?? [];

// likedPetsByOwner 에 내 petID 가 있을 경우 서로 좋아요한 것으로 간주하여 friends 서브 컬렉션 서로의 정보를 등록
if (likedPetsByOwner.contains(myPets[0])) {
talker.info("Match found between $likedPetsByOwner and $myPets");
await _addFriend(userId, petOwnerId); // 내 친구 목록에 상대방을 친구로 추가
await _addFriend(petOwnerId, userId); // 상대방 친구 목록에 나를 친구로 추가
final userDoc = await firestore.collection('users_test').doc(userId).get();
return userDoc.data()!['pets'] ?? [];
}

// 매칭 여부 확인 후 친구 추가 및 메세지 출력
Future<void> _checkForMatchAndAddFriend(BuildContext context, String userId,
String petId, String matchType) async {
if (await _isMatchFound(userId, petId)) {
await _handleSuccessfulMatch(context, userId, petId, matchType);
} else {
talker.info("No match found between $likedPetsByOwner and $myPets");
_handleFailedMatch(context, matchType);
}
}

// 매칭 성공 시 처리
Future<void> _handleSuccessfulMatch(BuildContext context, String userId,
String petId, String matchType) async {
final petOwnerId = await _getPetOwnerId(petId);

await _addFriend(userId, petOwnerId); // 내 친구 목록에 상대방을 추가
await _addFriend(petOwnerId, userId); // 상대방 친구 목록에 나를 추가

// 매칭 성공 메세지 전달
if (context.mounted) {
final message = (matchType == 'like')
? AppStrings.matchSuccessMessageLike
: AppStrings.matchSuccessMessageSuperLike;
_showSnackbar(context, message);
}
}

// 매칭 실패 시 처리
void _handleFailedMatch(BuildContext context, String matchType) {
if (context.mounted) {
final message = (matchType == 'like')
? AppStrings.matchFailMessageLike
: AppStrings.matchFailMessageSuperLike;
_showSnackbar(context, message);
}
}

// 친구 추가 기능
Future<void> _addFriend(String userId, String friendId) async {
final firestore = FirebaseFirestore.instance;
final userDoc = firestore.collection('users_test').doc(userId);
await userDoc.collection('friends').doc(friendId).set({
'userId': friendId,
'addedDate': Timestamp.now(),
});
}

try {
await userDoc.collection('friends').doc(friendId).set({
'userId': friendId,
'addedDate': Timestamp.now(),
});
talker.info("Friend added between $userId and $friendId");
} catch (e) {
talker.error("Error adding friend: $e");
}
// 안내 메세지 출력
void _showSnackbar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
),
);
}

// 해당 유저를 추천 안함 기능 (ProfileScreen 에서 호출)
// ProfileScreen 에서 호출하는 함수
Future<void> handleIgnoreProfile({
required BuildContext context,
required PetProfileModel petProfile,
}) async {
await addPetToIgnored(userId, petProfile.petID);
await addPetToIgnored(context, userId, petProfile.petID);
if (context.mounted) {
Navigator.of(context).pop(); // 프로필 화면 닫기
}
}
}

final matchScreenProvider =
StateNotifierProvider<MatchScreenNotifier, List<PetProfileModel>>(
final matchScreenProvider = StateNotifierProvider<MatchScreenNotifier, List<PetProfileModel>>(
(ref) => MatchScreenNotifier(),
);


9 changes: 9 additions & 0 deletions lib/feature/match/provider/PetImageProvider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Pet 이미지 URL을 제공하는 Provider
final petImageProvider = FutureProvider.family<String, String>((ref, imageName) async {
final storageRef = FirebaseStorage.instance.ref('pet/$imageName');
return await storageRef.getDownloadURL();
});

11 changes: 8 additions & 3 deletions lib/feature/match/widget/MatchFilterOptionWidget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ class MatchFilterOptionWidget extends ConsumerWidget {
value: 'ignore',
child: Row(
children: [
Icon(Icons.block, color: Colors.red[400], size: 14),
Icon(Icons.block, color: Colors.red[400], size: 16),
const SizedBox(width: 8),
Text(AppStrings.ignoreThisPet,
style: TextStyle(color: Colors.red[400])),
const Text(
AppStrings.ignoreThisPet,
style: TextStyle(
color: Colors.black87,
fontSize: 14, // 글자 크기 줄이기
),
),
],
),
),
Expand Down
Loading

0 comments on commit b8cc023

Please sign in to comment.