diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef..9657e6b 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -11,7 +11,25 @@ rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) + + afterEvaluate { + if (project.hasProperty("android")) { + project.extensions.configure { + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + } + } + + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "11" + } + } + } } + subprojects { project.evaluationDependsOn(":app") } diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a439442..11662c3 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false - id("org.jetbrains.kotlin.android") version "1.8.22" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false } include(":app") diff --git a/lib/config/oauth_config.dart b/lib/config/oauth_config.dart index e518596..58e8695 100644 --- a/lib/config/oauth_config.dart +++ b/lib/config/oauth_config.dart @@ -2,11 +2,13 @@ import 'package:atproto_oauth_flutter/atproto_oauth_flutter.dart'; /// OAuth Configuration for atProto /// -/// This configuration provides ClientMetadata for the new atproto_oauth_flutter package. -/// The new package handles proper decentralized OAuth discovery (works with ANY PDS). +/// This configuration provides ClientMetadata for the new +/// atproto_oauth_flutter package. The new package handles proper +/// decentralized OAuth discovery (works with ANY PDS). class OAuthConfig { // OAuth Server Configuration - // Cloudflare Worker that hosts client-metadata.json and handles OAuth callbacks + // Cloudflare Worker that hosts client-metadata.json and handles OAuth + // callbacks static const String oauthServerUrl = 'https://lingering-darkness-50a6.brettmay0212.workers.dev'; @@ -24,12 +26,15 @@ class OAuthConfig { // Derived OAuth URLs static const String clientId = '$oauthServerUrl/client-metadata.json'; - // IMPORTANT: Private-use URI schemes (RFC 8252) require SINGLE slash, not double! + // IMPORTANT: Private-use URI schemes (RFC 8252) require SINGLE slash, + // not double! // Correct: dev.workers.example:/oauth/callback // Incorrect: dev.workers.example://oauth/callback - static const String customSchemeCallback = '$customScheme:/oauth/callback'; + static const String customSchemeCallback = + '$customScheme:/oauth/callback'; - // HTTPS callback (fallback for PDS that don't support custom URI schemes) + // HTTPS callback (fallback for PDS that don't support custom + // URI schemes) static const String httpsCallback = '$oauthServerUrl/oauth/callback'; // OAuth Scopes - recommended scope for atProto @@ -49,8 +54,9 @@ class OAuthConfig { static ClientMetadata createClientMetadata() { return const ClientMetadata( clientId: clientId, - // Use HTTPS as PRIMARY - prevents browser re-navigation that invalidates auth codes - // Custom scheme as fallback (Worker page redirects to custom scheme anyway) + // Use HTTPS as PRIMARY - prevents browser re-navigation that + // invalidates auth codes. Custom scheme as fallback (Worker page + // redirects to custom scheme anyway) redirectUris: [httpsCallback, customSchemeCallback], scope: scope, clientName: clientName, diff --git a/lib/main.dart b/lib/main.dart index 9a7c529..69a46bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -98,7 +98,8 @@ GoRouter _createRouter(AuthProvider authProvider) { if (state.uri.scheme == OAuthConfig.customScheme) { if (kDebugMode) { print( - '⚠️ OAuth callback in errorBuilder - flutter_web_auth_2 should handle it', + '⚠️ OAuth callback in errorBuilder - ' + 'flutter_web_auth_2 should handle it', ); print(' URI: ${state.uri}'); } diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart index eb002c8..02c5f6d 100644 --- a/lib/providers/auth_provider.dart +++ b/lib/providers/auth_provider.dart @@ -15,11 +15,11 @@ import '../services/oauth_service.dart'; /// ✅ Tokens are stored securely by the package (iOS Keychain / Android EncryptedSharedPreferences) /// ✅ Automatic token refresh handled by the package class AuthProvider with ChangeNotifier { - final OAuthService _oauthService; /// Constructor with optional OAuthService for dependency injection (testing) AuthProvider({OAuthService? oauthService}) : _oauthService = oauthService ?? OAuthService(); + final OAuthService _oauthService; // SharedPreferences keys for storing session info // The DID and handle are public information, so SharedPreferences is fine @@ -51,13 +51,15 @@ class AuthProvider with ChangeNotifier { /// The token is automatically refreshed if expired. /// If token refresh fails (e.g., revoked server-side), signs out the user. Future getAccessToken() async { - if (_session == null) return null; + if (_session == null) { + return null; + } try { // Access the session getter to get the token set final session = await _session!.sessionGetter.get(_session!.sub); return session.tokenSet.accessToken; - } catch (e) { + } on Exception catch (e) { if (kDebugMode) { print('❌ Failed to get access token: $e'); print('🔄 Token refresh failed - signing out user'); @@ -124,7 +126,7 @@ class AuthProvider with ChangeNotifier { print('No stored DID found - user not logged in'); } } - } catch (e) { + } on Exception catch (e) { _error = e.toString(); if (kDebugMode) { print('❌ Failed to initialize auth: $e'); @@ -168,7 +170,8 @@ class AuthProvider with ChangeNotifier { _did = session.sub; _handle = trimmedHandle; - // Store the DID and handle in SharedPreferences so we can restore on next launch + // Store the DID and handle in SharedPreferences so we can restore + // on next launch final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefKeyDid, session.sub); await prefs.setString(_prefKeyHandle, trimmedHandle); @@ -232,7 +235,7 @@ class AuthProvider with ChangeNotifier { if (kDebugMode) { print('✅ Successfully signed out'); } - } catch (e) { + } on Exception catch (e) { _error = e.toString(); if (kDebugMode) { print('⚠️ Sign out failed: $e'); diff --git a/lib/providers/feed_provider.dart b/lib/providers/feed_provider.dart index d39e4e0..44792e6 100644 --- a/lib/providers/feed_provider.dart +++ b/lib/providers/feed_provider.dart @@ -8,8 +8,9 @@ import 'auth_provider.dart'; /// Manages feed state and fetching logic. /// Supports both authenticated timeline and public discover feed. /// -/// IMPORTANT: Accepts AuthProvider reference to fetch fresh access tokens -/// before each authenticated request (critical for atProto OAuth token rotation). +/// IMPORTANT: Accepts AuthProvider reference to fetch fresh access +/// tokens before each authenticated request (critical for atProto OAuth +/// token rotation). class FeedProvider with ChangeNotifier { FeedProvider(this._authProvider, {CovesApiService? apiService}) { @@ -19,21 +20,24 @@ class FeedProvider with ChangeNotifier { CovesApiService(tokenGetter: _authProvider.getAccessToken); // [P0 FIX] Listen to auth state changes and clear feed on sign-out - // This prevents privacy bug where logged-out users see their private timeline - // until they manually refresh. + // This prevents privacy bug where logged-out users see their private + // timeline until they manually refresh. _authProvider.addListener(_onAuthChanged); } /// Handle authentication state changes /// - /// When the user signs out (isAuthenticated becomes false), immediately - /// clear the feed to prevent showing personalized content to logged-out users. - /// This fixes a privacy bug where token refresh failures would sign out the user - /// but leave their private timeline visible until manual refresh. + /// When the user signs out (isAuthenticated becomes false), + /// immediately clear the feed to prevent showing personalized content + /// to logged-out users. This fixes a privacy bug where token refresh + /// failures would sign out the user but leave their private timeline + /// visible until manual refresh. void _onAuthChanged() { if (!_authProvider.isAuthenticated && _posts.isNotEmpty) { if (kDebugMode) { - debugPrint('🔒 Auth state changed to unauthenticated - clearing feed'); + debugPrint( + '🔒 Auth state changed to unauthenticated - clearing feed', + ); } reset(); // Automatically load the public discover feed @@ -64,10 +68,12 @@ class FeedProvider with ChangeNotifier { String get sort => _sort; String? get timeframe => _timeframe; - /// Load feed based on authentication state (business logic encapsulation) + /// Load feed based on authentication state (business logic + /// encapsulation) /// - /// This method encapsulates the business logic of deciding which feed to fetch. - /// Previously this logic was in the UI layer (FeedScreen), violating clean architecture. + /// This method encapsulates the business logic of deciding which feed + /// to fetch. Previously this logic was in the UI layer (FeedScreen), + /// violating clean architecture. Future loadFeed({bool refresh = false}) async { if (_authProvider.isAuthenticated) { await fetchTimeline(refresh: refresh); @@ -76,20 +82,24 @@ class FeedProvider with ChangeNotifier { } } - /// Common feed fetching logic (DRY principle - eliminates code duplication) + /// Common feed fetching logic (DRY principle - eliminates code + /// duplication) Future _fetchFeed({ required bool refresh, required Future Function() fetcher, required String feedName, }) async { - if (_isLoading || _isLoadingMore) return; + if (_isLoading || _isLoadingMore) { + return; + } try { if (refresh) { _isLoading = true; // DON'T clear _posts, _cursor, or _hasMore yet // Keep existing data visible until refresh succeeds - // This prevents transient failures from wiping the user's feed and pagination state + // This prevents transient failures from wiping the user's feed + // and pagination state _error = null; } else { _isLoadingMore = true; @@ -114,7 +124,7 @@ class FeedProvider with ChangeNotifier { if (kDebugMode) { debugPrint('✅ $feedName loaded: ${_posts.length} posts total'); } - } catch (e) { + } on Exception catch (e) { _error = e.toString(); if (kDebugMode) { debugPrint('❌ Failed to fetch $feedName: $e'); @@ -158,7 +168,9 @@ class FeedProvider with ChangeNotifier { /// Load more posts (pagination) Future loadMore() async { - if (!_hasMore || _isLoadingMore) return; + if (!_hasMore || _isLoadingMore) { + return; + } await loadFeed(); } diff --git a/lib/screens/auth/login_screen.dart b/lib/screens/auth/login_screen.dart index cfee184..cc8540d 100644 --- a/lib/screens/auth/login_screen.dart +++ b/lib/screens/auth/login_screen.dart @@ -38,7 +38,7 @@ class _LoginScreenState extends State { // Navigate to feed on successful login context.go('/feed'); } - } catch (e) { + } on Exception catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -147,7 +147,8 @@ class _LoginScreenState extends State { } // Basic handle validation if (!value.contains('.')) { - return 'Handle must contain a domain (e.g., user.bsky.social)'; + return 'Handle must contain a domain ' + '(e.g., user.bsky.social)'; } return null; }, @@ -166,7 +167,8 @@ class _LoginScreenState extends State { // Info text const Text( - 'You\'ll be redirected to authorize this app with your atProto provider.', + 'You\'ll be redirected to authorize this app with your ' + 'atProto provider.', style: TextStyle(fontSize: 14, color: Color(0xFF5A6B7F)), textAlign: TextAlign.center, ), @@ -179,17 +181,17 @@ class _LoginScreenState extends State { onPressed: () { showDialog( context: context, - builder: - (context) => AlertDialog( + builder: (context) => AlertDialog( backgroundColor: const Color(0xFF1A2028), title: const Text( 'What is a handle?', style: TextStyle(color: Colors.white), ), content: const Text( - 'Your handle is your unique identifier on the atProto network, ' - 'like alice.bsky.social. If you don\'t have one yet, you can create ' - 'an account at bsky.app.', + 'Your handle is your unique identifier ' + 'on the atProto network, like ' + 'alice.bsky.social. If you don\'t have one ' + 'yet, you can create an account at bsky.app.', style: TextStyle(color: Color(0xFFB6C2D2)), ), actions: [ diff --git a/lib/screens/home/feed_screen.dart b/lib/screens/home/feed_screen.dart index f9a8db3..6e7f492 100644 --- a/lib/screens/home/feed_screen.dart +++ b/lib/screens/home/feed_screen.dart @@ -39,15 +39,13 @@ class _FeedScreenState extends State { /// Load feed - business logic is now in FeedProvider void _loadFeed() { - final feedProvider = Provider.of(context, listen: false); - feedProvider.loadFeed(refresh: true); + Provider.of(context, listen: false).loadFeed(refresh: true); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { - final feedProvider = Provider.of(context, listen: false); - feedProvider.loadMore(); + Provider.of(context, listen: false).loadMore(); } } @@ -104,15 +102,17 @@ class _FeedScreenState extends State { required bool isLoadingMore, required bool isAuthenticated, }) { - // Loading state (only show full-screen loader for initial load, not refresh) + // Loading state (only show full-screen loader for initial load, + // not refresh) if (isLoading && posts.isEmpty) { return const Center( child: CircularProgressIndicator(color: Color(0xFFFF6B35)), ); } - // Error state (only show full-screen error when no posts loaded yet) - // If we have posts but pagination failed, we'll show the error at the bottom + // Error state (only show full-screen error when no posts loaded + // yet). If we have posts but pagination failed, we'll show the error + // at the bottom if (error != null && posts.isEmpty) { return Center( child: Padding( @@ -143,11 +143,10 @@ class _FeedScreenState extends State { const SizedBox(height: 24), ElevatedButton( onPressed: () { - final feedProvider = Provider.of( + Provider.of( context, listen: false, - ); - feedProvider.retry(); + ).retry(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFF6B35), @@ -199,7 +198,8 @@ class _FeedScreenState extends State { child: ListView.builder( controller: _scrollController, // Add extra item for loading indicator or pagination error - itemCount: posts.length + (isLoadingMore || error != null ? 1 : 0), + itemCount: + posts.length + (isLoadingMore || error != null ? 1 : 0), itemBuilder: (context, index) { // Footer: loading indicator or error message if (index == posts.length) { @@ -241,12 +241,12 @@ class _FeedScreenState extends State { const SizedBox(height: 12), TextButton( onPressed: () { - final feedProvider = Provider.of( + Provider.of( context, listen: false, - ); - feedProvider.clearError(); - feedProvider.loadMore(); + ) + ..clearError() + ..loadMore(); }, style: TextButton.styleFrom( foregroundColor: const Color(0xFFFF6B35), @@ -261,8 +261,9 @@ class _FeedScreenState extends State { final post = posts[index]; return Semantics( - label: - 'Feed post in ${post.post.community.name} by ${post.post.author.displayName ?? post.post.author.handle}. ${post.post.title ?? ""}', + label: 'Feed post in ${post.post.community.name} by ' + '${post.post.author.displayName ?? post.post.author.handle}. ' + '${post.post.title ?? ""}', button: true, child: _PostCard(post: post), ); @@ -351,7 +352,8 @@ class _PostCard extends StatelessWidget { ), ), Text( - 'Posted by ${post.post.author.displayName ?? post.post.author.handle}', + 'Posted by ${post.post.author.displayName ?? '' + '${post.post.author.handle}'}', style: const TextStyle( color: Color(0xFFB6C2D2), fontSize: 12, @@ -430,7 +432,9 @@ class _EmbedCard extends StatelessWidget { @override Widget build(BuildContext context) { // Only show image if thumbnail exists - if (embed.thumb == null) return const SizedBox.shrink(); + if (embed.thumb == null) { + return const SizedBox.shrink(); + } return Container( decoration: BoxDecoration( @@ -443,8 +447,7 @@ class _EmbedCard extends StatelessWidget { width: double.infinity, height: 180, fit: BoxFit.cover, - placeholder: - (context, url) => Container( + placeholder: (context, url) => Container( width: double.infinity, height: 180, color: const Color(0xFF1A1F26), diff --git a/lib/services/api_exceptions.dart b/lib/services/api_exceptions.dart index 1214f17..ff9fcfe 100644 --- a/lib/services/api_exceptions.dart +++ b/lib/services/api_exceptions.dart @@ -2,15 +2,16 @@ /// /// Custom exception classes for different types of API failures. /// This allows better error handling and user-friendly error messages. +library; /// Base class for all API exceptions class ApiException implements Exception { + + ApiException(this.message, {this.statusCode, this.originalError}); final String message; final int? statusCode; final dynamic originalError; - ApiException(this.message, {this.statusCode, this.originalError}); - @override String toString() => message; } @@ -18,34 +19,33 @@ class ApiException implements Exception { /// Authentication failure (401) /// Token expired, invalid, or missing class AuthenticationException extends ApiException { - AuthenticationException(String message, {dynamic originalError}) - : super(message, statusCode: 401, originalError: originalError); + AuthenticationException(super.message, {super.originalError}) + : super(statusCode: 401); } /// Resource not found (404) /// PDS, community, post, or user not found class NotFoundException extends ApiException { - NotFoundException(String message, {dynamic originalError}) - : super(message, statusCode: 404, originalError: originalError); + NotFoundException(super.message, {super.originalError}) + : super(statusCode: 404); } /// Server error (500+) /// Backend or PDS server failure class ServerException extends ApiException { - ServerException(String message, {int? statusCode, dynamic originalError}) - : super(message, statusCode: statusCode, originalError: originalError); + ServerException(super.message, {super.statusCode, super.originalError}); } /// Network connectivity failure /// No internet, connection refused, timeout class NetworkException extends ApiException { - NetworkException(String message, {dynamic originalError}) - : super(message, statusCode: null, originalError: originalError); + NetworkException(super.message, {super.originalError}) + : super(statusCode: null); } /// Federation error /// atProto PDS unreachable or DID resolution failure class FederationException extends ApiException { - FederationException(String message, {dynamic originalError}) - : super(message, statusCode: null, originalError: originalError); + FederationException(super.message, {super.originalError}) + : super(statusCode: null); } diff --git a/lib/services/coves_api_service.dart b/lib/services/coves_api_service.dart index caa0e49..9c8a0b8 100644 --- a/lib/services/coves_api_service.dart +++ b/lib/services/coves_api_service.dart @@ -42,14 +42,16 @@ class CovesApiService { } else { if (kDebugMode) { debugPrint( - '⚠️ Token getter returned null - making unauthenticated request', + '⚠️ Token getter returned null - ' + 'making unauthenticated request', ); } } } else { if (kDebugMode) { debugPrint( - '⚠️ No token getter provided - making unauthenticated request', + '⚠️ No token getter provided - ' + 'making unauthenticated request', ); } } @@ -68,7 +70,8 @@ class CovesApiService { ), ); - // Add logging interceptor AFTER auth (so it can see the Authorization header) + // Add logging interceptor AFTER auth (so it can see the + // Authorization header) if (kDebugMode) { _dio.interceptors.add( LogInterceptor( @@ -89,7 +92,8 @@ class CovesApiService { /// /// Parameters: /// - [sort]: 'hot', 'top', or 'new' (default: 'hot') - /// - [timeframe]: 'hour', 'day', 'week', 'month', 'year', 'all' (default: 'day' for top sort) + /// - [timeframe]: 'hour', 'day', 'week', 'month', 'year', 'all' + /// (default: 'day' for top sort) /// - [limit]: Number of posts per page (default: 15, max: 50) /// - [cursor]: Pagination cursor from previous response Future getTimeline({ @@ -120,7 +124,8 @@ class CovesApiService { if (kDebugMode) { debugPrint( - '✅ Timeline fetched: ${response.data['feed']?.length ?? 0} posts', + '✅ Timeline fetched: ' + '${response.data['feed']?.length ?? 0} posts', ); } @@ -162,7 +167,8 @@ class CovesApiService { if (kDebugMode) { debugPrint( - '✅ Discover feed fetched: ${response.data['feed']?.length ?? 0} posts', + '✅ Discover feed fetched: ' + '${response.data['feed']?.length ?? 0} posts', ); } @@ -188,17 +194,20 @@ class CovesApiService { // Handle specific HTTP status codes if (e.response != null) { final statusCode = e.response!.statusCode; - final message = e.response!.data?['error'] ?? e.response!.data?['message']; + final message = + e.response!.data?['error'] ?? e.response!.data?['message']; if (statusCode != null) { if (statusCode == 401) { throw AuthenticationException( - message?.toString() ?? 'Authentication failed. Token expired or invalid', + message?.toString() ?? + 'Authentication failed. Token expired or invalid', originalError: e, ); } else if (statusCode == 404) { throw NotFoundException( - message?.toString() ?? 'Resource not found. PDS or content may not exist', + message?.toString() ?? + 'Resource not found. PDS or content may not exist', originalError: e, ); } else if (statusCode >= 500) { diff --git a/lib/services/oauth_service.dart b/lib/services/oauth_service.dart index de794d8..a321777 100644 --- a/lib/services/oauth_service.dart +++ b/lib/services/oauth_service.dart @@ -3,14 +3,17 @@ import 'package:atproto_oauth_flutter/atproto_oauth_flutter.dart'; import 'package:flutter/foundation.dart'; import '../config/oauth_config.dart'; -/// OAuth Service for atProto authentication using the new atproto_oauth_flutter package +/// OAuth Service for atProto authentication using the new +/// atproto_oauth_flutter package /// /// Key improvements over the old implementation: -/// ✅ Proper decentralized OAuth discovery - works with ANY PDS (not just bsky.social) +/// ✅ Proper decentralized OAuth discovery - works with ANY PDS +/// (not just bsky.social) /// ✅ Built-in session management - no manual token storage /// ✅ Automatic token refresh with concurrency control /// ✅ Session event streams for updates and deletions -/// ✅ Secure storage handled internally (iOS Keychain, Android EncryptedSharedPreferences) +/// ✅ Secure storage handled internally +/// (iOS Keychain, Android EncryptedSharedPreferences) /// /// The new package handles the complete OAuth flow: /// 1. Handle/DID resolution @@ -64,7 +67,9 @@ class OAuthService { /// Set up listeners for session events void _setupEventListeners() { - if (_client == null) return; + if (_client == null) { + return; + } // Listen for session updates (token refresh, etc.) _onUpdatedSubscription = _client!.onUpdated.listen((event) { @@ -121,7 +126,8 @@ class OAuthService { } // Call the new package's signIn method - // This does EVERYTHING: handle resolution, PDS discovery, OAuth flow, token storage + // This does EVERYTHING: handle resolution, PDS discovery, OAuth flow, + // token storage if (kDebugMode) { print('📞 Calling FlutterOAuthClient.signIn()...'); } @@ -168,7 +174,8 @@ class OAuthService { print('$stackTrace'); } - // Check if user cancelled (flutter_web_auth_2 throws PlatformException with "CANCELED" code) + // Check if user cancelled (flutter_web_auth_2 throws + // PlatformException with "CANCELED" code) if (e.toString().contains('CANCELED') || e.toString().contains('User cancelled')) { throw Exception('Sign in cancelled by user'); @@ -196,7 +203,7 @@ class OAuthService { /// Returns the restored session or null if no session found. Future restoreSession( String did, { - refresh = 'auto', + String refresh = 'auto', }) async { try { if (_client == null) { @@ -219,7 +226,7 @@ class OAuthService { } return session; - } catch (e) { + } on Exception catch (e) { if (kDebugMode) { print('⚠️ Failed to restore session: $e'); } diff --git a/lib/services/pds_discovery_service.dart b/lib/services/pds_discovery_service.dart index 777378e..cd2ed99 100644 --- a/lib/services/pds_discovery_service.dart +++ b/lib/services/pds_discovery_service.dart @@ -2,9 +2,10 @@ import 'package:dio/dio.dart'; /// PDS Discovery Service /// -/// Handles the resolution of atProto handles to their Personal Data Servers (PDS). -/// This is crucial for proper decentralized authentication - each user may be on -/// a different PDS, and we need to redirect them to THEIR PDS's OAuth server. +/// Handles the resolution of atProto handles to their Personal Data +/// Servers (PDS). This is crucial for proper decentralized +/// authentication - each user may be on a different PDS, and we need to +/// redirect them to THEIR PDS's OAuth server. /// /// Flow: /// 1. Resolve handle to DID using a handle resolver (bsky.social) @@ -67,7 +68,8 @@ class PDSDiscoveryService { /// Fetch a DID document from the PLC directory Future> _fetchDIDDocument(String did) async { try { - final response = await _dio.get('https://plc.directory/$did'); + final response = + await _dio.get('https://plc.directory/$did'); if (response.statusCode != 200) { throw Exception('Failed to fetch DID document: ${response.statusCode}'); diff --git a/lib/widgets/primary_button.dart b/lib/widgets/primary_button.dart index 6ed915d..9e66d4b 100644 --- a/lib/widgets/primary_button.dart +++ b/lib/widgets/primary_button.dart @@ -26,8 +26,8 @@ class PrimaryButton extends StatelessWidget { style: ElevatedButton.styleFrom( backgroundColor: _getBackgroundColor(), foregroundColor: _getTextColor(), - disabledBackgroundColor: _getBackgroundColor().withOpacity(0.5), - disabledForegroundColor: _getTextColor().withOpacity(0.5), + disabledBackgroundColor: _getBackgroundColor().withValues(alpha: 0.5), + disabledForegroundColor: _getTextColor().withValues(alpha: 0.5), overlayColor: _getOverlayColor(), splashFactory: NoSplash.splashFactory, shape: RoundedRectangleBorder( @@ -37,7 +37,7 @@ class PrimaryButton extends StatelessWidget { elevation: variant == ButtonVariant.solid ? 8 : 0, shadowColor: variant == ButtonVariant.solid - ? const Color(0xFFD84315).withOpacity(0.4) + ? const Color(0xFFD84315).withValues(alpha: 0.4) : Colors.transparent, padding: const EdgeInsets.symmetric(vertical: 12), ), @@ -60,7 +60,7 @@ class PrimaryButton extends StatelessWidget { case ButtonVariant.solid: return const Color(0xFFFF6B35); case ButtonVariant.outline: - return const Color(0xFF5A6B7F).withOpacity(0.1); + return const Color(0xFF5A6B7F).withValues(alpha: 0.1); case ButtonVariant.tertiary: return const Color(0xFF1A1F27); } @@ -87,11 +87,11 @@ class PrimaryButton extends StatelessWidget { Color _getOverlayColor() { switch (variant) { case ButtonVariant.solid: - return const Color(0xFFD84315).withOpacity(0.2); + return const Color(0xFFD84315).withValues(alpha: 0.2); case ButtonVariant.outline: - return const Color(0xFF5A6B7F).withOpacity(0.15); + return const Color(0xFF5A6B7F).withValues(alpha: 0.15); case ButtonVariant.tertiary: - return const Color(0xFF2A3441).withOpacity(0.3); + return const Color(0xFF2A3441).withValues(alpha: 0.3); } } } diff --git a/pubspec.lock b/pubspec.lock index c221da0..94a5c62 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d url: "https://pub.dev" source: hosted - version: "88.0.0" + version: "91.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.4.1" args: dependency: transitive description: @@ -29,50 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" - at_identifier: - dependency: transitive - description: - name: at_identifier - sha256: "7c8778202d17ec4e63b38a6a58480503fbf0d7fc1d62e0d64580a9b6cbe142f7" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - at_uri: - dependency: transitive - description: - name: at_uri - sha256: "1156d9d70460fcfcb30e744d7f8c7d544eff073b3142b772f0d02aca10dd064f" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - atproto: - dependency: transitive - description: - name: atproto - sha256: "0f3d342c4d629e9994d58dbadd4281074641ac75a18cd514b212a3b15f86019e" - url: "https://pub.dev" - source: hosted - version: "0.13.3" - atproto_core: - dependency: transitive - description: - name: atproto_core - sha256: "13e7f5f0f3d9e5be59eefd5f427adf45ffdeaa59001d4ea7c91764ba21f1e9ba" - url: "https://pub.dev" - source: hosted - version: "0.11.2" - atproto_oauth: - dependency: transitive - description: - name: atproto_oauth - sha256: "8a0c64455c38c45773ebab5fdd55bf214541461f3a97fe0e6184a5eeb8222f03" - url: "https://pub.dev" - source: hosted - version: "0.1.0" + version: "2.13.0" atproto_oauth_flutter: dependency: "direct main" description: @@ -80,22 +40,6 @@ packages: relative: true source: path version: "0.1.0" - base_codecs: - dependency: transitive - description: - name: base_codecs - sha256: "41701a12ede9912663decd708279924ece5018566daa7d1f484d5f4f10894f91" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - bluesky: - dependency: "direct main" - description: - name: bluesky - sha256: "207135e189278936dfc6bad0d59835a359f06b97ecd73eee1bccf6b993969428" - url: "https://pub.dev" - source: hosted - version: "0.18.10" boolean_selector: dependency: transitive description: @@ -104,14 +48,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - buffer: - dependency: transitive - description: - name: buffer - sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" - url: "https://pub.dev" - source: hosted - version: "1.2.3" build: dependency: transitive description: @@ -184,14 +120,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" - cbor: - dependency: transitive - description: - name: cbor - sha256: f5239dd6b6ad24df67d1449e87d7180727d6f43b87b3c9402e6398c7a2d9609b - url: "https://pub.dev" - source: hosted - version: "6.3.7" characters: dependency: transitive description: @@ -204,10 +132,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" clock: dependency: transitive description: @@ -256,14 +184,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dart_multihash: - dependency: transitive - description: - name: dart_multihash - sha256: "7bef7091497c531f94bf82102805a69d97e4e5d120000dcbbc4a1da679060e0a" - url: "https://pub.dev" - source: hosted - version: "1.0.1" dart_style: dependency: transitive description: @@ -300,10 +220,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -345,10 +265,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_secure_storage: dependency: "direct main" description: @@ -431,14 +351,6 @@ packages: description: flutter source: sdk version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 - url: "https://pub.dev" - source: hosted - version: "2.4.4" glob: dependency: transitive description: @@ -463,16 +375,8 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - hex: - dependency: transitive - description: - name: hex - sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" - url: "https://pub.dev" - source: hosted - version: "0.2.0" http: - dependency: transitive + dependency: "direct dev" description: name: http sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 @@ -495,14 +399,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - ieee754: - dependency: transitive - description: - name: ieee754 - sha256: "7d87451c164a56c156180d34a4e93779372edd191d2c219206100b976203128c" - url: "https://pub.dev" - source: hosted - version: "1.0.3" io: dependency: transitive description: @@ -531,34 +427,34 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" logging: dependency: transitive description: @@ -595,10 +491,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" mockito: dependency: "direct dev" description: @@ -607,22 +503,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.1" - multiformats: - dependency: transitive - description: - name: multiformats - sha256: aa2fa36d2e4d0069dac993b35ee52e5165d67f15b995d68f797466065a6d05a5 - url: "https://pub.dev" - source: hosted - version: "0.2.3" - nanoid: - dependency: transitive - description: - name: nanoid - sha256: be3f8752d9046c825df2f3914195151eb876f3ad64b9d833dd0b799b77b8759e - url: "https://pub.dev" - source: hosted - version: "1.0.0" nested: dependency: transitive description: @@ -631,14 +511,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - nsid: - dependency: transitive - description: - name: nsid - sha256: f0e58c3899f7c224a7c9fb991be5bb2c18de0f920bec4e807ae2d3572cb718c1 - url: "https://pub.dev" - source: hosted - version: "0.4.1" octo_image: dependency: transitive description: @@ -683,18 +555,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + sha256: e122c5ea805bb6773bb12ce667611265980940145be920cd09a4b0ec0285cb16 url: "https://pub.dev" source: hosted - version: "2.2.19" + version: "2.2.20" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" path_provider_linux: dependency: transitive description: @@ -723,10 +595,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.1" platform: dependency: transitive description: @@ -803,18 +675,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + sha256: "34266009473bf71d748912da4bf62d439185226c03e01e2d9687bc65bbfcb713" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "1c33a907142607c40a7542768ec9badfd16293bac51da3a4482623d15845f88b" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_linux: dependency: transitive description: @@ -904,18 +776,18 @@ packages: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" sqflite_darwin: dependency: transitive description: @@ -968,10 +840,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" term_glyph: dependency: transitive description: @@ -984,10 +856,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" typed_data: dependency: transitive description: @@ -996,14 +868,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" - url: "https://pub.dev" - source: hosted - version: "2.2.2" url_launcher: dependency: transitive description: @@ -1016,18 +880,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.24" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9" url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.3.5" url_launcher_linux: dependency: transitive description: @@ -1040,10 +904,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.4" url_launcher_platform_interface: dependency: transitive description: @@ -1104,18 +968,18 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.2" watcher: dependency: transitive description: @@ -1152,10 +1016,10 @@ packages: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.15.0" window_to_front: dependency: transitive description: @@ -1176,18 +1040,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" - xrpc: - dependency: transitive - description: - name: xrpc - sha256: bacfa0f6824fdeaa631aad1a5fd064c3f140c771fed94cbd04df3b7d1e008709 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "6.6.1" yaml: dependency: transitive description: @@ -1197,5 +1053,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.2 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index e71df40..b838d47 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,6 @@ dependencies: go_router: ^16.3.0 provider: ^6.1.5+1 flutter_svg: ^2.2.1 - bluesky: ^0.18.10 dio: ^5.9.0 cached_network_image: ^3.4.1 @@ -55,7 +54,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # Testing dependencies mockito: ^5.4.4 @@ -65,6 +64,7 @@ dev_dependencies: # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. + http: any flutter: # The following line ensures that the Material Icons font is diff --git a/test/providers/auth_provider_test.dart b/test/providers/auth_provider_test.dart index 266f23e..0606758 100644 --- a/test/providers/auth_provider_test.dart +++ b/test/providers/auth_provider_test.dart @@ -206,16 +206,22 @@ void main() { // that are not exported from atproto_oauth_flutter package. // These tests would need integration testing or a different approach. - test('should return null when not authenticated (skipped - needs integration test)', () async { + test( + 'should return null when not authenticated ' + '(skipped - needs integration test)', + () async { // This test is skipped as it requires mocking internal OAuth classes // that cannot be mocked with mockito - }, skip: true); + }, skip: true,); - test('should sign out user if token refresh fails (skipped - needs integration test)', () async { + test( + 'should sign out user if token refresh fails ' + '(skipped - needs integration test)', + () async { // This test demonstrates the critical fix for issue #7 // Token refresh failure should trigger sign out // Skipped as it requires mocking internal OAuth classes - }, skip: true); + }, skip: true,); }); group('State Management', () { diff --git a/test/providers/feed_provider_test.dart b/test/providers/feed_provider_test.dart index 106d253..509f355 100644 --- a/test/providers/feed_provider_test.dart +++ b/test/providers/feed_provider_test.dart @@ -193,7 +193,6 @@ void main() { sort: anyNamed('sort'), timeframe: anyNamed('timeframe'), limit: anyNamed('limit'), - cursor: null, ), ).thenAnswer((_) async => refreshResponse); @@ -277,7 +276,6 @@ void main() { sort: anyNamed('sort'), timeframe: anyNamed('timeframe'), limit: anyNamed('limit'), - cursor: null, ), ).thenAnswer((_) async => firstResponse); @@ -304,7 +302,7 @@ void main() { }); test('should not load more if already loading', () async { - feedProvider.fetchTimeline(refresh: true); + await feedProvider.fetchTimeline(refresh: true); await feedProvider.loadMore(); // Should not make additional calls while loading diff --git a/test/widgets/feed_screen_test.dart b/test/widgets/feed_screen_test.dart index 75f33f3..572efbf 100644 --- a/test/widgets/feed_screen_test.dart +++ b/test/widgets/feed_screen_test.dart @@ -17,12 +17,12 @@ class FakeAuthProvider extends AuthProvider { @override bool get isLoading => _isLoading; - void setAuthenticated(bool value) { + void setAuthenticated({required bool value}) { _isAuthenticated = value; notifyListeners(); } - void setLoading(bool value) { + void setLoading({required bool value}) { _isLoading = value; notifyListeners(); } @@ -63,12 +63,12 @@ class FakeFeedProvider extends FeedProvider { notifyListeners(); } - void setLoading(bool value) { + void setLoading({required bool value}) { _isLoading = value; notifyListeners(); } - void setLoadingMore(bool value) { + void setLoadingMore({required bool value}) { _isLoadingMore = value; notifyListeners(); } @@ -78,7 +78,7 @@ class FakeFeedProvider extends FeedProvider { notifyListeners(); } - void setHasMore(bool value) { + void setHasMore({required bool value}) { _hasMore = value; notifyListeners(); } @@ -122,7 +122,7 @@ void main() { testWidgets('should display loading indicator when loading', ( tester, ) async { - fakeFeedProvider.setLoading(true); + fakeFeedProvider.setLoading(value: true); await tester.pumpWidget(createTestWidget()); @@ -136,7 +136,10 @@ void main() { expect(find.text('Failed to load feed'), findsOneWidget); // Error message is transformed to user-friendly message - expect(find.text('Please check your internet connection'), findsOneWidget); + expect( + find.text('Please check your internet connection'), + findsOneWidget, + ); expect(find.text('Retry'), findsOneWidget); // Test retry button @@ -148,7 +151,7 @@ void main() { testWidgets('should display empty state when no posts', (tester) async { fakeFeedProvider.setPosts([]); - fakeAuthProvider.setAuthenticated(false); + fakeAuthProvider.setAuthenticated(value: false); await tester.pumpWidget(createTestWidget()); @@ -160,7 +163,7 @@ void main() { tester, ) async { fakeFeedProvider.setPosts([]); - fakeAuthProvider.setAuthenticated(true); + fakeAuthProvider.setAuthenticated(value: true); await tester.pumpWidget(createTestWidget()); @@ -188,7 +191,7 @@ void main() { testWidgets('should display "Feed" title when authenticated', ( tester, ) async { - fakeAuthProvider.setAuthenticated(true); + fakeAuthProvider.setAuthenticated(value: true); await tester.pumpWidget(createTestWidget()); @@ -198,7 +201,7 @@ void main() { testWidgets('should display "Explore" title when not authenticated', ( tester, ) async { - fakeAuthProvider.setAuthenticated(false); + fakeAuthProvider.setAuthenticated(value: false); await tester.pumpWidget(createTestWidget()); @@ -223,8 +226,9 @@ void main() { tester, ) async { final mockPosts = [_createMockPost('Test Post')]; - fakeFeedProvider.setPosts(mockPosts); - fakeFeedProvider.setLoadingMore(true); + fakeFeedProvider + ..setPosts(mockPosts) + ..setLoadingMore(value: true); await tester.pumpWidget(createTestWidget());