Main coves client
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: format and cleanup existing changes

- Apply dart format to existing modified files
- Include auth, API service, and widget improvements
- Ensure consistency across codebase

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+16 -40
+1 -2
lib/config/oauth_config.dart
··· 30 30 // not double! 31 31 // Correct: dev.workers.example:/oauth/callback 32 32 // Incorrect: dev.workers.example://oauth/callback 33 - static const String customSchemeCallback = 34 - '$customScheme:/oauth/callback'; 33 + static const String customSchemeCallback = '$customScheme:/oauth/callback'; 35 34 36 35 // HTTPS callback (fallback for PDS that don't support custom 37 36 // URI schemes)
-10
lib/models/post.dart
··· 5 5 // /xrpc/social.coves.feed.getDiscover 6 6 7 7 class TimelineResponse { 8 - 9 8 TimelineResponse({required this.feed, this.cursor}); 10 9 11 10 factory TimelineResponse.fromJson(Map<String, dynamic> json) { ··· 33 32 } 34 33 35 34 class FeedViewPost { 36 - 37 35 FeedViewPost({required this.post, this.reason}); 38 36 39 37 factory FeedViewPost.fromJson(Map<String, dynamic> json) { ··· 50 48 } 51 49 52 50 class PostView { 53 - 54 51 PostView({ 55 52 required this.uri, 56 53 required this.cid, ··· 107 104 } 108 105 109 106 class AuthorView { 110 - 111 107 AuthorView({ 112 108 required this.did, 113 109 required this.handle, ··· 130 126 } 131 127 132 128 class CommunityRef { 133 - 134 129 CommunityRef({required this.did, required this.name, this.avatar}); 135 130 136 131 factory CommunityRef.fromJson(Map<String, dynamic> json) { ··· 146 141 } 147 142 148 143 class PostStats { 149 - 150 144 PostStats({ 151 145 required this.upvotes, 152 146 required this.downvotes, ··· 169 163 } 170 164 171 165 class PostEmbed { 172 - 173 166 PostEmbed({required this.type, this.external, required this.data}); 174 167 175 168 factory PostEmbed.fromJson(Map<String, dynamic> json) { ··· 191 184 } 192 185 193 186 class ExternalEmbed { 194 - 195 187 ExternalEmbed({ 196 188 required this.uri, 197 189 this.title, ··· 217 209 } 218 210 219 211 class PostFacet { 220 - 221 212 PostFacet({required this.data}); 222 213 223 214 factory PostFacet.fromJson(Map<String, dynamic> json) { ··· 227 218 } 228 219 229 220 class FeedReason { 230 - 231 221 FeedReason({required this.type, required this.data}); 232 222 233 223 factory FeedReason.fromJson(Map<String, dynamic> json) {
+1 -2
lib/providers/auth_provider.dart
··· 15 15 /// ✅ Tokens are stored securely by the package (iOS Keychain / Android EncryptedSharedPreferences) 16 16 /// ✅ Automatic token refresh handled by the package 17 17 class AuthProvider with ChangeNotifier { 18 - 19 18 /// Constructor with optional OAuthService for dependency injection (testing) 20 19 AuthProvider({OAuthService? oauthService}) 21 - : _oauthService = oauthService ?? OAuthService(); 20 + : _oauthService = oauthService ?? OAuthService(); 22 21 final OAuthService _oauthService; 23 22 24 23 // SharedPreferences keys for storing session info
+2 -1
lib/screens/auth/login_screen.dart
··· 181 181 onPressed: () { 182 182 showDialog( 183 183 context: context, 184 - builder: (context) => AlertDialog( 184 + builder: 185 + (context) => AlertDialog( 185 186 backgroundColor: const Color(0xFF1A2028), 186 187 title: const Text( 187 188 'What is a handle?',
+4 -5
lib/services/api_exceptions.dart
··· 6 6 7 7 /// Base class for all API exceptions 8 8 class ApiException implements Exception { 9 - 10 9 ApiException(this.message, {this.statusCode, this.originalError}); 11 10 final String message; 12 11 final int? statusCode; ··· 20 19 /// Token expired, invalid, or missing 21 20 class AuthenticationException extends ApiException { 22 21 AuthenticationException(super.message, {super.originalError}) 23 - : super(statusCode: 401); 22 + : super(statusCode: 401); 24 23 } 25 24 26 25 /// Resource not found (404) 27 26 /// PDS, community, post, or user not found 28 27 class NotFoundException extends ApiException { 29 28 NotFoundException(super.message, {super.originalError}) 30 - : super(statusCode: 404); 29 + : super(statusCode: 404); 31 30 } 32 31 33 32 /// Server error (500+) ··· 40 39 /// No internet, connection refused, timeout 41 40 class NetworkException extends ApiException { 42 41 NetworkException(super.message, {super.originalError}) 43 - : super(statusCode: null); 42 + : super(statusCode: null); 44 43 } 45 44 46 45 /// Federation error 47 46 /// atProto PDS unreachable or DID resolution failure 48 47 class FederationException extends ApiException { 49 48 FederationException(super.message, {super.originalError}) 50 - : super(statusCode: null); 49 + : super(statusCode: null); 51 50 }
+1 -5
lib/services/coves_api_service.dart
··· 15 15 /// rotates tokens automatically (~1 hour expiry), and caching tokens would 16 16 /// cause 401 errors after the first token expires. 17 17 class CovesApiService { 18 - 19 18 CovesApiService({Future<String?> Function()? tokenGetter}) 20 19 : _tokenGetter = tokenGetter { 21 20 _dio = Dio( ··· 264 263 case DioExceptionType.cancel: 265 264 throw ApiException('Request cancelled', originalError: e); 266 265 default: 267 - throw ApiException( 268 - 'Unknown error: ${e.message}', 269 - originalError: e, 270 - ); 266 + throw ApiException('Unknown error: ${e.message}', originalError: e); 271 267 } 272 268 } 273 269
+1 -2
lib/services/pds_discovery_service.dart
··· 68 68 /// Fetch a DID document from the PLC directory 69 69 Future<Map<String, dynamic>> _fetchDIDDocument(String did) async { 70 70 try { 71 - final response = 72 - await _dio.get('https://plc.directory/$did'); 71 + final response = await _dio.get('https://plc.directory/$did'); 73 72 74 73 if (response.statusCode != 200) { 75 74 throw Exception('Failed to fetch DID document: ${response.statusCode}');
-1
lib/widgets/logo.dart
··· 2 2 import 'package:flutter_svg/flutter_svg.dart'; 3 3 4 4 class CovesLogo extends StatelessWidget { 5 - 6 5 const CovesLogo({super.key, this.size = 150, this.useColorVersion = false}); 7 6 final double size; 8 7 final bool useColorVersion;
-1
lib/widgets/primary_button.dart
··· 3 3 enum ButtonVariant { solid, outline, tertiary } 4 4 5 5 class PrimaryButton extends StatelessWidget { 6 - 7 6 const PrimaryButton({ 8 7 super.key, 9 8 required this.title,
+6 -11
test/providers/auth_provider_test.dart
··· 10 10 11 11 // Generate mocks for OAuthService and OAuthSession only 12 12 @GenerateMocks([OAuthService, OAuthSession]) 13 - 14 13 void main() { 15 14 TestWidgetsFlutterBinding.ensureInitialized(); 16 15 ··· 206 205 // that are not exported from atproto_oauth_flutter package. 207 206 // These tests would need integration testing or a different approach. 208 207 209 - test( 210 - 'should return null when not authenticated ' 211 - '(skipped - needs integration test)', 212 - () async { 208 + test('should return null when not authenticated ' 209 + '(skipped - needs integration test)', () async { 213 210 // This test is skipped as it requires mocking internal OAuth classes 214 211 // that cannot be mocked with mockito 215 - }, skip: true,); 212 + }, skip: true); 216 213 217 - test( 218 - 'should sign out user if token refresh fails ' 219 - '(skipped - needs integration test)', 220 - () async { 214 + test('should sign out user if token refresh fails ' 215 + '(skipped - needs integration test)', () async { 221 216 // This test demonstrates the critical fix for issue #7 222 217 // Token refresh failure should trigger sign out 223 218 // Skipped as it requires mocking internal OAuth classes 224 - }, skip: true,); 219 + }, skip: true); 225 220 }); 226 221 227 222 group('State Management', () {