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;
|
bool _isInPasswordRecoveryFlow = false;
|
||||||
Session? _session;
|
Session? _session;
|
||||||
String? _lastEnsuredUserId;
|
String? _lastEnsuredUserId;
|
||||||
|
StreamSubscription<AuthState>? _authStateSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -217,7 +218,7 @@ class _AuthGateState extends State<AuthGate> {
|
||||||
_session = supabase.auth.currentSession;
|
_session = supabase.auth.currentSession;
|
||||||
_ensureDefaultCollectionIfNeeded();
|
_ensureDefaultCollectionIfNeeded();
|
||||||
|
|
||||||
supabase.auth.onAuthStateChange.listen(
|
_authStateSubscription = supabase.auth.onAuthStateChange.listen(
|
||||||
(AuthState authState) {
|
(AuthState authState) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
|
|
@ -247,6 +248,12 @@ class _AuthGateState extends State<AuthGate> {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_authStateSubscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _ensureDefaultCollectionIfNeeded() async {
|
Future<void> _ensureDefaultCollectionIfNeeded() async {
|
||||||
final userId = _session?.user.id;
|
final userId = _session?.user.id;
|
||||||
if (userId == null || userId == _lastEnsuredUserId) return;
|
if (userId == null || userId == _lastEnsuredUserId) return;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//import 'package:supabase_flutter/supabase_flutter.dart';
|
//import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
|
|
||||||
/// Data model for a collection.
|
/// Data model for a collection.
|
||||||
|
|
@ -120,7 +121,8 @@ class CollectionService {
|
||||||
'p_collection_id': collectionId,
|
'p_collection_id': collectionId,
|
||||||
});
|
});
|
||||||
memberCounts[collectionId] = (rows as List).length;
|
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;
|
if (collectionId == null) continue;
|
||||||
itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0;
|
itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0;
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
debugPrint('CollectionService.getCollectionItemCounts RPC error: $e');
|
||||||
|
}
|
||||||
|
|
||||||
final unresolvedIds = collectionIds
|
final unresolvedIds = collectionIds
|
||||||
.where((collectionId) => !itemCounts.containsKey(collectionId))
|
.where((collectionId) => !itemCounts.containsKey(collectionId))
|
||||||
|
|
@ -211,7 +215,9 @@ class CollectionService {
|
||||||
recent: (first['recent_count'] as num?)?.toInt() ?? 0,
|
recent: (first['recent_count'] as num?)?.toInt() ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
debugPrint('CollectionService.getCollectionStats RPC error: $e');
|
||||||
|
}
|
||||||
|
|
||||||
final weekAgoIso = DateTime.now()
|
final weekAgoIso = DateTime.now()
|
||||||
.subtract(const Duration(days: 7))
|
.subtract(const Duration(days: 7))
|
||||||
|
|
@ -269,11 +275,21 @@ class CollectionService {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
// Add owner as a member.
|
// Add owner as a member.
|
||||||
await supabase.from('collection_members').insert({
|
try {
|
||||||
'collection_id': row['id'],
|
await supabase.from('collection_members').insert({
|
||||||
'user_id': userId,
|
'collection_id': row['id'],
|
||||||
'role': 'owner',
|
'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(
|
return Collection(
|
||||||
id: row['id'] as String,
|
id: row['id'] as String,
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@ class StorageService {
|
||||||
required int entryId,
|
required int entryId,
|
||||||
String? oldPath,
|
String? oldPath,
|
||||||
}) async {
|
}) async {
|
||||||
if (oldPath != null && oldPath.isNotEmpty) {
|
|
||||||
await deleteCarImage(oldPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
final user = supabase.auth.currentUser;
|
final user = supabase.auth.currentUser;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw Exception('You must be signed in to upload images.');
|
throw Exception('You must be signed in to upload images.');
|
||||||
|
|
@ -50,6 +46,14 @@ class StorageService {
|
||||||
|
|
||||||
_signedUrlCache.remove(path);
|
_signedUrlCache.remove(path);
|
||||||
|
|
||||||
|
if (oldPath != null && oldPath.isNotEmpty && oldPath != path) {
|
||||||
|
try {
|
||||||
|
await deleteCarImage(oldPath);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('StorageService.uploadCarImage cleanup error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return path;
|
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:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hwhub/widgets/car_card.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('placeholder test', () {
|
testWidgets('CarCard renders model id and metadata', (tester) async {
|
||||||
expect(1 + 1, 2);
|
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