fix(review): address auth subscription, safe image replace, logging and widget tests
This commit is contained in:
parent
e8501f7d72
commit
38de6d8b86
4 changed files with 89 additions and 17 deletions
|
|
@ -209,6 +209,7 @@ class _AuthGateState extends State<AuthGate> {
|
|||
bool _isInPasswordRecoveryFlow = false;
|
||||
Session? _session;
|
||||
String? _lastEnsuredUserId;
|
||||
StreamSubscription<AuthState>? _authStateSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -217,7 +218,7 @@ class _AuthGateState extends State<AuthGate> {
|
|||
_session = supabase.auth.currentSession;
|
||||
_ensureDefaultCollectionIfNeeded();
|
||||
|
||||
supabase.auth.onAuthStateChange.listen(
|
||||
_authStateSubscription = supabase.auth.onAuthStateChange.listen(
|
||||
(AuthState authState) {
|
||||
if (!mounted) return;
|
||||
|
||||
|
|
@ -247,6 +248,12 @@ class _AuthGateState extends State<AuthGate> {
|
|||
setState(() => _isLoading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_authStateSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _ensureDefaultCollectionIfNeeded() async {
|
||||
final userId = _session?.user.id;
|
||||
if (userId == null || userId == _lastEnsuredUserId) return;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../main.dart';
|
||||
|
||||
/// Data model for a collection.
|
||||
|
|
@ -120,7 +121,8 @@ class CollectionService {
|
|||
'p_collection_id': collectionId,
|
||||
});
|
||||
memberCounts[collectionId] = (rows as List).length;
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
debugPrint('CollectionService.getMyCollections member RPC error: $e');
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
@ -182,7 +184,9 @@ class CollectionService {
|
|||
if (collectionId == null) continue;
|
||||
itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0;
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
debugPrint('CollectionService.getCollectionItemCounts RPC error: $e');
|
||||
}
|
||||
|
||||
final unresolvedIds = collectionIds
|
||||
.where((collectionId) => !itemCounts.containsKey(collectionId))
|
||||
|
|
@ -211,7 +215,9 @@ class CollectionService {
|
|||
recent: (first['recent_count'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
debugPrint('CollectionService.getCollectionStats RPC error: $e');
|
||||
}
|
||||
|
||||
final weekAgoIso = DateTime.now()
|
||||
.subtract(const Duration(days: 7))
|
||||
|
|
@ -269,11 +275,21 @@ class CollectionService {
|
|||
.single();
|
||||
|
||||
// Add owner as a member.
|
||||
try {
|
||||
await supabase.from('collection_members').insert({
|
||||
'collection_id': row['id'],
|
||||
'user_id': userId,
|
||||
'role': 'owner',
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('CollectionService.create member insert failed: $e');
|
||||
try {
|
||||
await supabase.from('collections').delete().eq('id', row['id']);
|
||||
} catch (cleanupError) {
|
||||
debugPrint('CollectionService.create rollback failed: $cleanupError');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return Collection(
|
||||
id: row['id'] as String,
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ class StorageService {
|
|||
required int entryId,
|
||||
String? oldPath,
|
||||
}) async {
|
||||
if (oldPath != null && oldPath.isNotEmpty) {
|
||||
await deleteCarImage(oldPath);
|
||||
}
|
||||
|
||||
final user = supabase.auth.currentUser;
|
||||
if (user == null) {
|
||||
throw Exception('You must be signed in to upload images.');
|
||||
|
|
@ -50,6 +46,14 @@ class StorageService {
|
|||
|
||||
_signedUrlCache.remove(path);
|
||||
|
||||
if (oldPath != null && oldPath.isNotEmpty && oldPath != path) {
|
||||
try {
|
||||
await deleteCarImage(oldPath);
|
||||
} catch (e) {
|
||||
debugPrint('StorageService.uploadCarImage cleanup error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,54 @@
|
|||
// Basic smoke test placeholder — will be updated when UI is finalized.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hwhub/widgets/car_card.dart';
|
||||
|
||||
void main() {
|
||||
test('placeholder test', () {
|
||||
expect(1 + 1, 2);
|
||||
testWidgets('CarCard renders model id and metadata', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 220,
|
||||
height: 360,
|
||||
child: CarCard(
|
||||
hwId: 'ABC12',
|
||||
name: 'Die-Cast Model',
|
||||
series: 'Collector Series',
|
||||
year: 2024,
|
||||
isVerified: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('ABC12'), findsOneWidget);
|
||||
expect(find.text('Die-Cast Model'), findsOneWidget);
|
||||
expect(find.text('Collector Series'), findsOneWidget);
|
||||
expect(find.text('2024'), findsOneWidget);
|
||||
expect(find.text('Verified'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('CarCard shows selected state label', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 220,
|
||||
height: 360,
|
||||
child: CarCard(
|
||||
hwId: 'XYZ99',
|
||||
isSelected: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Selected'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue