Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/flutter-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.27.0'
flutter-version: '3.44.8'
channel: 'stable'
cache: true

Expand All @@ -49,6 +49,9 @@ jobs:
- name: Run code generation
run: flutter pub run build_runner build --delete-conflicting-outputs

- name: Generate localization
run: flutter gen-l10n

- name: Build APK
run: flutter build apk --${{ github.event.inputs.build_type || 'debug' }}

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/flutter-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.27.0'
flutter-version: '3.44.8'
channel: 'stable'
cache: true

Expand All @@ -37,6 +37,9 @@ jobs:
- name: Run code generation
run: flutter pub run build_runner build --delete-conflicting-outputs

- name: Generate localization
run: flutter gen-l10n

- name: Run tests
run: flutter test

Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/flutter-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.27.0'
flutter-version: '3.44.8'
channel: 'stable'
cache: true

Expand All @@ -34,8 +34,15 @@ jobs:
- name: Run code generation
run: flutter pub run build_runner build --delete-conflicting-outputs

- name: Generate localization
run: flutter gen-l10n

# Errors-only gate: the codebase has ~15 warnings + ~44 infos (mostly
# style/inference noise surfaced by the newer analyzer on Flutter 3.44.8),
# not compile errors. Tracking a full lint cleanup separately; the gate
# catches real errors without blocking on cosmetic infos.
- name: Analyze
run: flutter analyze --fatal-infos
run: flutter analyze --no-fatal-warnings --no-fatal-infos

- name: Format check
run: dart format --set-exit-if-changed .
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@ docs/sphinx/build/
docs/sphinx/source/implemented.md
docs/sphinx/source/architecture.md
docs/sphinx/source/setup_reference.md

# Flutter generated iOS ephemeral artifacts
**/ios/Flutter/ephemeral/
1 change: 0 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ linter:
rules:
# Error rules
avoid_dynamic_calls: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
cancel_subscriptions: true
close_sinks: true
Expand Down
3 changes: 1 addition & 2 deletions lib/core/analytics/analytics_route_observer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import 'analytics_service.dart';
/// ),
/// ```
class AnalyticsRouteObserver extends RouteObserver<PageRoute<dynamic>> {
final AnalyticsService _analytics;

/// Creates a route observer that logs screen views.
///
/// [analytics] - The analytics service to use for logging.
AnalyticsRouteObserver(this._analytics);
final AnalyticsService _analytics;

@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
Expand Down
5 changes: 2 additions & 3 deletions lib/core/auth/auth_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ import 'auth_state.dart';
/// )
/// ```
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository _authRepository;
StreamSubscription<AuthUser?>? _authSubscription;

AuthBloc({required AuthRepository authRepository})
: _authRepository = authRepository,
super(const AuthState.initial()) {
Expand All @@ -55,6 +52,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
(user) => add(AuthEvent.userChanged(user)),
);
}
final AuthRepository _authRepository;
StreamSubscription<AuthUser?>? _authSubscription;

Future<void> _onCheckRequested(
AuthCheckRequested event,
Expand Down
19 changes: 10 additions & 9 deletions lib/core/auth/auth_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ abstract class AuthRepository {
/// Contains only the essential fields needed for auth decisions.
/// Extend or create a separate UserProfile model for additional user data.
class AuthUser {
const AuthUser({
required this.id,
required this.email,
this.displayName,
this.photoUrl,
this.emailVerified = false,
});

/// Unique user identifier from the auth provider.
final String id;

Expand All @@ -72,14 +80,6 @@ class AuthUser {
/// Whether the user's email has been verified.
final bool emailVerified;

const AuthUser({
required this.id,
required this.email,
this.displayName,
this.photoUrl,
this.emailVerified = false,
});

@override
String toString() => 'AuthUser(id: $id, email: $email)';

Expand All @@ -95,7 +95,8 @@ class AuthUser {
emailVerified == other.emailVerified;

@override
int get hashCode => Object.hash(id, email, displayName, photoUrl, emailVerified);
int get hashCode =>
Object.hash(id, email, displayName, photoUrl, emailVerified);
}

/// Supported OAuth providers.
Expand Down
19 changes: 9 additions & 10 deletions lib/core/connectivity/connectivity_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'connectivity_state.dart';

class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
ConnectivityBloc({
required Connectivity connectivity,
required Dio dio,
}) : _connectivity = connectivity,
_dio = dio,
super(const ConnectivityState.offline()) {
on<ConnectivityEvent>(_onEvent);
_initConnectivityListener();
}
final Connectivity _connectivity;
final Dio _dio;
Timer? _pingTimer;
Expand All @@ -19,16 +28,6 @@ class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
int _consecutiveFailures = 0;
Duration? _lastLatency;

ConnectivityBloc({
required Connectivity connectivity,
required Dio dio,
}) : _connectivity = connectivity,
_dio = dio,
super(const ConnectivityState.offline()) {
on<ConnectivityEvent>(_onEvent);
_initConnectivityListener();
}

void _initConnectivityListener() {
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(
(results) {
Expand Down
3 changes: 1 addition & 2 deletions lib/core/connectivity/connectivity_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ abstract class ConnectivityService {
}

class ConnectivityServiceImpl implements ConnectivityService {
final ConnectivityBloc _bloc;

ConnectivityServiceImpl(this._bloc);
final ConnectivityBloc _bloc;

@override
Stream<ConnectivityState> get stream => _bloc.stream;
Expand Down
16 changes: 8 additions & 8 deletions lib/core/database/cached_document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import 'sync_status.dart';
/// );
/// ```
class CachedDocument {
const CachedDocument({
required this.id,
required this.data,
required this.cachedAt,
this.syncStatus = const SyncStatus.synced(),
this.syncedAt,
});

/// The document ID.
final String id;

Expand All @@ -36,14 +44,6 @@ class CachedDocument {
/// Null if never synced.
final DateTime? syncedAt;

const CachedDocument({
required this.id,
required this.data,
this.syncStatus = const SyncStatus.synced(),
required this.cachedAt,
this.syncedAt,
});

/// Create a copy with updated fields.
CachedDocument copyWith({
String? id,
Expand Down
12 changes: 6 additions & 6 deletions lib/core/database/database_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ abstract class DatabaseService {
///
/// Combines a field name, operator, and value for query conditions.
class QueryFilter {
const QueryFilter({
required this.field,
required this.operator,
required this.value,
});

/// The document field to filter on.
final String field;

Expand All @@ -87,12 +93,6 @@ class QueryFilter {
/// The value to compare against.
final dynamic value;

const QueryFilter({
required this.field,
required this.operator,
required this.value,
});

@override
String toString() => 'QueryFilter($field ${operator.name} $value)';

Expand Down
3 changes: 1 addition & 2 deletions lib/core/database/local_cache_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ import '../utils/result.dart';
///
/// See `docs/database.md` for full offline-first patterns.
class LocalCacheService {
final HiveInterface _hive;

/// Creates a [LocalCacheService] backed by the given [HiveInterface].
///
/// If [hive] is null, uses the global [Hive] instance.
LocalCacheService({HiveInterface? hive}) : _hive = hive ?? Hive;
final HiveInterface _hive;

/// Get a cached document by collection and ID.
///
Expand Down
14 changes: 6 additions & 8 deletions lib/core/di/injection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import 'package:hive/hive.dart';
import 'package:logger/logger.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../../features/home/data/repositories/item_repository.dart';
import '../../features/home/presentation/bloc/home_bloc.dart';
import '../analytics/analytics_service.dart';
import '../analytics/noop_analytics_service.dart';
import '../database/local_cache_service.dart';
import '../connectivity/connectivity_bloc.dart';
import '../connectivity/connectivity_service.dart';
import '../database/local_cache_service.dart';
import '../network/auth_token_manager.dart';
import '../network/dio_client.dart';
import '../network/offline_queue.dart';
import '../network/request_executor.dart';
import '../../features/home/data/repositories/item_repository.dart';
import '../../features/home/presentation/bloc/home_bloc.dart';

final getIt = GetIt.instance;

Expand All @@ -38,18 +38,16 @@ Future<void> configureDependencies() async {
methodCount: 0,
errorMethodCount: 5,
lineLength: 80,
colors: true,
printEmojis: true,
),
),
);

getIt.registerLazySingleton<HiveInterface>(() => Hive);
getIt.registerLazySingleton<Connectivity>(() => Connectivity());
getIt.registerLazySingleton<Connectivity>(Connectivity.new);

// Analytics (use NoopAnalyticsService by default, replace with Firebase in production)
getIt.registerLazySingleton<AnalyticsService>(
() => NoopAnalyticsService(),
NoopAnalyticsService.new,
);

// Database (local cache — register concrete DatabaseService when choosing a provider)
Expand All @@ -71,7 +69,7 @@ Future<void> configureDependencies() async {
);

// Network
getIt.registerLazySingleton<Dio>(() => Dio());
getIt.registerLazySingleton<Dio>(Dio.new);

getIt.registerLazySingleton<DioClient>(
() => DioClient(
Expand Down
3 changes: 1 addition & 2 deletions lib/core/network/auth_exception.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
class AuthException implements Exception {
final String message;

const AuthException(this.message);
final String message;

@override
String toString() => 'AuthException: $message';
Expand Down
11 changes: 5 additions & 6 deletions lib/core/network/auth_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ import 'auth_exception.dart';
import 'auth_token_manager.dart';

class AuthInterceptor extends Interceptor {
final AuthTokenManager _tokenManager;
final Dio _dio;
final Logger _logger;

bool _isRefreshing = false;

AuthInterceptor({
required AuthTokenManager tokenManager,
required Dio dio,
required Logger logger,
}) : _tokenManager = tokenManager,
_dio = dio,
_logger = logger;
final AuthTokenManager _tokenManager;
final Dio _dio;
final Logger _logger;

bool _isRefreshing = false;

@override
Future<void> onRequest(
Expand Down
11 changes: 5 additions & 6 deletions lib/core/network/auth_token_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import 'package:logger/logger.dart';
import 'auth_exception.dart';

class AuthTokenManager {
AuthTokenManager({
required FlutterSecureStorage storage,
required Logger logger,
}) : _storage = storage,
_logger = logger;
final FlutterSecureStorage _storage;
final Logger _logger;

static const _accessTokenKey = 'access_token';
static const _refreshTokenKey = 'refresh_token';
static const _expiryKey = 'token_expiry';

AuthTokenManager({
required FlutterSecureStorage storage,
required Logger logger,
}) : _storage = storage,
_logger = logger;

Future<String?> getAccessToken() async {
return _storage.read(key: _accessTokenKey);
}
Expand Down
Loading
Loading