Skip to content

Commit 4bb501d

Browse files
authored
Afsan workpace (#20)
2 parents c5caf44 + 196d4dd commit 4bb501d

18 files changed

Lines changed: 2358 additions & 2604 deletions

.cursor/settings.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
{
2-
"plugins": {
3-
"supabase": {
4-
"enabled": true
5-
}
6-
}
7-
}
1+
{}

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,5 @@ builder.exe
4949
dist/petsphere-217fd880.ipa
5050
dist/petsphere-e6d8abd0.ipa
5151
.env.local
52-
android/gradle.properties
53-
android/gradle.properties
52+
/android/gradle.properties
5453
/scratch

android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
22
android.useAndroidX=true
33
# Android Gradle Plugin / Gradle do not run on JDK 26; without a JDK 17–21 toolchain the build fails with "26.0.2" as the only message.
4-
org.gradle.java.home=C:\\Program Files\\Android\\Android Studio\\jbr
4+
# org.gradle.java.home=C:\\Program Files\\Android\\Android Studio\\jbr
55
# Enable parallel build to speed up the build process
66
org.gradle.parallel=true
77
# Enable the Gradle build cache to reuse outputs from previous builds

lib/controllers/match_controller.dart

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ import 'auth_controller.dart';
88
import 'chat_controller.dart';
99
import 'pet_controller.dart';
1010

11+
// ---------------------------------------------------------------------------
12+
// Discovery tab: which of the user's pets is browsing (null = active pet)
13+
// ---------------------------------------------------------------------------
14+
15+
class DiscoveryPetIdNotifier extends Notifier<String?> {
16+
@override
17+
String? build() => null;
18+
19+
void select(String? petId) => state = petId;
20+
}
21+
22+
final discoveryActivePetIdProvider =
23+
NotifierProvider<DiscoveryPetIdNotifier, String?>(
24+
DiscoveryPetIdNotifier.new,
25+
);
26+
1127
// ---------------------------------------------------------------------------
1228
// State
1329
// ---------------------------------------------------------------------------
@@ -74,23 +90,53 @@ class MatchController extends Notifier<MatchState> {
7490
@override
7591
MatchState build() {
7692
final activePet = ref.watch(activePetProvider);
77-
if (activePet == null) {
93+
final myPets = ref.watch(petProvider.select((s) => s.myPets));
94+
95+
if (myPets.isEmpty) {
7896
_lastLoadedPetId = null;
97+
Future.microtask(
98+
() => ref.read(discoveryActivePetIdProvider.notifier).select(null),
99+
);
79100
return MatchState();
80101
}
81102

82-
// Do not return a fresh [MatchState(isLoading: true)] on every rebuild —
83-
// that wipes discovery data whenever [activePetProvider] notifies with the
84-
// same pet id (new model instance). Only reset loading when the pet id changes.
85-
if (_lastLoadedPetId != activePet.id) {
86-
_lastLoadedPetId = activePet.id;
87-
Future.microtask(() => _load(activePet.id));
88-
return MatchState(isLoading: true);
103+
final browsingId = ref.watch(discoveryActivePetIdProvider);
104+
String? targetId = browsingId ?? activePet?.id;
105+
if (targetId != null && !myPets.any((p) => p.id == targetId)) {
106+
targetId = activePet?.id ?? myPets.first.id;
107+
Future.microtask(
108+
() => ref.read(discoveryActivePetIdProvider.notifier).select(targetId),
109+
);
110+
}
111+
targetId ??= activePet?.id ?? myPets.first.id;
112+
113+
if (_lastLoadedPetId != targetId) {
114+
final hadPriorLoad = _lastLoadedPetId != null;
115+
_lastLoadedPetId = targetId;
116+
Future.microtask(() => _load(targetId!));
117+
if (!hadPriorLoad) {
118+
return MatchState(isLoading: true);
119+
}
120+
return MatchState(
121+
isLoading: true,
122+
filterAnimal: state.filterAnimal,
123+
filterBreed: state.filterBreed,
124+
searchQuery: state.searchQuery,
125+
discoveryPets: const [],
126+
allDiscoveryPets: const [],
127+
myRequests: state.myRequests,
128+
sentRequests: state.sentRequests,
129+
);
89130
}
90131

91132
return state;
92133
}
93134

135+
String? _resolveDiscoveryTargetPetId() {
136+
return ref.read(discoveryActivePetIdProvider) ??
137+
ref.read(activePetProvider)?.id;
138+
}
139+
94140
// When [silent] is true the loading flag is NOT set, so the UI avoids a
95141
// full-screen spinner. Use this for background refreshes (e.g. after a like).
96142
Future<void> _load(String myPetId, {bool silent = false}) async {
@@ -105,13 +151,27 @@ class MatchController extends Notifier<MatchState> {
105151

106152
_currentDiscoveryPetId = myPetId;
107153

154+
final myPets = ref.read(petProvider).myPets;
155+
PetModel? viewerPet;
156+
for (final p in myPets) {
157+
if (p.id == myPetId) {
158+
viewerPet = p;
159+
break;
160+
}
161+
}
162+
final viewerAnimalType = viewerPet?.animalType.trim();
163+
108164
try {
109165
final futures = await Future.wait([
110166
matchRepository.fetchDiscoveryPets(
111167
myPetId: myPetId,
112168
userId: userId,
113-
filterAnimal: state.filterAnimal,
169+
allMyPetIds: myPets.map((p) => p.id).toList(),
114170
filterBreed: state.filterBreed,
171+
viewerAnimalType:
172+
(viewerAnimalType != null && viewerAnimalType.isNotEmpty)
173+
? viewerAnimalType
174+
: null,
115175
),
116176
matchRepository.fetchMyRequests(myPetId),
117177
matchRepository.fetchSentRequests(myPetId),
@@ -136,9 +196,9 @@ class MatchController extends Notifier<MatchState> {
136196
}
137197

138198
Future<void> refresh() async {
139-
final activePet = ref.read(activePetProvider);
140-
if (activePet != null) {
141-
await _load(activePet.id);
199+
final target = _resolveDiscoveryTargetPetId();
200+
if (target != null) {
201+
await _load(target);
142202
}
143203
}
144204

@@ -154,12 +214,12 @@ class MatchController extends Notifier<MatchState> {
154214
String? _currentDiscoveryPetId;
155215

156216
void setFilterBreed(String? breed) {
157-
final activePet = ref.read(activePetProvider);
217+
final target = _resolveDiscoveryTargetPetId();
158218
debugPrint(
159-
'[MatchController] setFilterBreed($breed) — activePet=${activePet?.name}');
160-
if (activePet == null) {
219+
'[MatchController] setFilterBreed($breed) — targetPetId=$target');
220+
if (target == null) {
161221
debugPrint(
162-
'[MatchController] ⚠️ activePet is NULL — filter change ignored!');
222+
'[MatchController] ⚠️ No discovery target pet — filter change ignored!');
163223
return;
164224
}
165225
if (breed == null || breed.isEmpty) {
@@ -169,16 +229,16 @@ class MatchController extends Notifier<MatchState> {
169229
}
170230
debugPrint(
171231
'[MatchController] State updated: animal=${state.filterAnimal}, breed=${state.filterBreed}');
172-
_load(activePet.id);
232+
_load(target);
173233
}
174234

175235
void setFilterAnimal(String? animal) {
176-
final activePet = ref.read(activePetProvider);
236+
final target = _resolveDiscoveryTargetPetId();
177237
debugPrint(
178-
'[MatchController] setFilterAnimal($animal) — activePet=${activePet?.name}');
179-
if (activePet == null) {
238+
'[MatchController] setFilterAnimal($animal) — targetPetId=$target');
239+
if (target == null) {
180240
debugPrint(
181-
'[MatchController] ⚠️ activePet is NULL — filter change ignored!');
241+
'[MatchController] ⚠️ No discovery target pet — filter change ignored!');
182242
return;
183243
}
184244
if (animal == null || animal.isEmpty) {
@@ -188,7 +248,7 @@ class MatchController extends Notifier<MatchState> {
188248
}
189249
debugPrint(
190250
'[MatchController] State updated: animal=${state.filterAnimal}, breed=${state.filterBreed}');
191-
_load(activePet.id);
251+
_load(target);
192252
}
193253

194254
void setSearchQuery(String query) {
@@ -250,7 +310,7 @@ class MatchController extends Notifier<MatchState> {
250310
}
251311

252312
try {
253-
await matchRepository.sendLikeRequest(
313+
final matchRequestId = await matchRepository.sendLikeRequest(
254314
senderPetId: senderPet.id,
255315
receiverPetId: receiverPetId,
256316
);
@@ -274,7 +334,7 @@ class MatchController extends Notifier<MatchState> {
274334
'${senderPet.name} is interested in breeding with ${receiverPet.name}.',
275335
type: 'match_request',
276336
entityType: 'match_request',
277-
entityId: senderPet.id,
337+
entityId: matchRequestId,
278338
actorPetId: senderPet.id,
279339
);
280340
}
@@ -283,6 +343,15 @@ class MatchController extends Notifier<MatchState> {
283343
// does NOT show a loading spinner, so the UI transition is seamless.
284344
_load(senderPet.id, silent: true);
285345
return true;
346+
} on StateError catch (e) {
347+
if (e.message == 'duplicate_match_request') {
348+
state = state.copyWith(
349+
error: 'You have already sent a request for this pet.',
350+
);
351+
} else {
352+
state = state.copyWith(error: e.toString());
353+
}
354+
return false;
286355
} catch (e) {
287356
state = state.copyWith(error: 'Could not send request: $e');
288357
return false;

lib/models/pet_health_extended_models.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class MedicationDose {
145145

146146
Color get statusColor {
147147
if (isGiven) return AppTheme.secondaryAccent;
148-
if (isOverdue) return AppTheme.primaryAccent;
148+
if (isOverdue) return AppTheme.alertAccent;
149149
return AppTheme.textSecondary;
150150
}
151151

0 commit comments

Comments
 (0)