Fix import paths and test issues

- Fixed test file import paths to point to correct Bloc file locations
- Fixed Bloc file import paths for models (../../../models/models.dart)
- Added explicit type annotations to resolve null safety warnings
- Fixed null safety issues in wishlist_bloc_test.dart
- All 70 tests now passing
This commit is contained in:
Yuriy Panov
2026-02-04 15:28:59 +06:00
parent 310463e89a
commit 2f97873095
46 changed files with 270 additions and 260 deletions

View File

@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../bloc/app_bloc.dart';
import '../bloc/app_event.dart';
import '../models/models.dart';
import '../../bloc/app_bloc.dart';
import '../../bloc/app_event.dart';
import '../../../models/models.dart';
class AddBookScreen extends StatefulWidget {
final dynamic initialData;

View File

@@ -1,5 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'add_book_event.dart';
import 'add_book_state.dart';

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class AddBookState extends Equatable {
final String title;

View File

@@ -1,5 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'book_details_event.dart';
import 'book_details_state.dart';

View File

@@ -1,4 +1,4 @@
import '../../models/models.dart';
import '../../../models/models.dart';
abstract class BookDetailsEvent {
const BookDetailsEvent();

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class BookDetailsState extends Equatable {
final Book? book;

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../bloc/app_bloc.dart';
import '../bloc/app_event.dart';
import '../models/models.dart';
import '../../bloc/app_bloc.dart';
import '../../bloc/app_event.dart';
import '../../../models/models.dart';
class BookDetailsScreen extends StatelessWidget {
final Book book;

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'categories_event.dart';
import 'categories_state.dart';

View File

@@ -1,4 +1,4 @@
import '../../models/models.dart';
import '../../../models/models.dart';
abstract class CategoriesEvent {
const CategoriesEvent();

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class CategoriesState extends Equatable {
final List<Category> categories;

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import '../models/models.dart';
import '../../../models/models.dart';
class CategoriesScreen extends StatelessWidget {
const CategoriesScreen({super.key});

View File

@@ -1,5 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'home_event.dart';
import 'home_state.dart';

View File

@@ -1,4 +1,4 @@
import '../../models/models.dart';
import '../../../models/models.dart';
abstract class HomeEvent {
const HomeEvent();

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class HomeState extends Equatable {
final AppScreen currentScreen;

View File

@@ -1,16 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/app_bloc.dart';
import '../bloc/app_state.dart';
import '../models/models.dart';
import 'library_screen.dart';
import 'categories_screen.dart';
import 'book_details_screen.dart';
import 'add_book_screen.dart';
import 'scanner_screen.dart';
import 'wishlist_screen.dart';
import 'settings_screen.dart';
import '../widgets/bottom_nav.dart';
import '../../bloc/app_bloc.dart';
import '../../bloc/app_state.dart';
import '../../../models/models.dart';
import '../library/library_screen.dart';
import '../categories/categories_screen.dart';
import '../book_details/book_details_screen.dart';
import '../add_book/add_book_screen.dart';
import '../scanner/scanner_screen.dart';
import '../wishlist/wishlist_screen.dart';
import '../settings/settings_screen.dart';
import '../../widgets/bottom_nav.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});

View File

@@ -1,5 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'library_event.dart';
import 'library_state.dart';
@@ -87,7 +87,7 @@ class LibraryBloc extends Bloc<LibraryEvent, LibraryState> {
void _onSearchBooks(SearchBooks event, Emitter<LibraryState> emit) {
final query = event.query.toLowerCase();
final filtered = state.books.where((book) {
final filtered = state.books.where((Book book) {
return book.title.toLowerCase().contains(query) ||
book.author.toLowerCase().contains(query) ||
book.genre.toLowerCase().contains(query);
@@ -104,7 +104,7 @@ class LibraryBloc extends Bloc<LibraryEvent, LibraryState> {
}
void _onFilterByStatus(FilterByStatus event, Emitter<LibraryState> emit) {
final filtered = state.books.where((book) {
final filtered = state.books.where((Book book) {
return book.status == event.status;
}).toList();

View File

@@ -1,4 +1,4 @@
import '../../models/models.dart';
import '../../../models/models.dart';
abstract class LibraryEvent {
const LibraryEvent();

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class LibraryState extends Equatable {
final List<Book> books;

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../bloc/app_bloc.dart';
import '../bloc/app_event.dart';
import '../bloc/app_state.dart';
import '../models/models.dart';
import '../../bloc/app_bloc.dart';
import '../../bloc/app_event.dart';
import '../../bloc/app_state.dart';
import '../../../models/models.dart';
class LibraryScreen extends StatefulWidget {
const LibraryScreen({super.key});

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/app_bloc.dart';
import '../bloc/app_event.dart';
import '../models/models.dart';
import '../../bloc/app_bloc.dart';
import '../../bloc/app_event.dart';
import '../../../models/models.dart';
class ScannerScreen extends StatelessWidget {
const ScannerScreen({super.key});

View File

@@ -1,5 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
import 'wishlist_event.dart';
import 'wishlist_state.dart';

View File

@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import '../../models/models.dart';
import '../../../models/models.dart';
class WishlistState extends Equatable {
final List<Book> books;

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/app_bloc.dart';
import 'screens/home_screen.dart';
import 'features/home/home_screen.dart';
void main() {
runApp(const BookshelfApp());

View File

@@ -0,0 +1,10 @@
// App screen enum
enum AppScreen {
library,
categories,
wishlist,
settings,
details,
addBook,
scanner,
}

View File

@@ -0,0 +1,127 @@
import 'book_status.dart';
// Book record - using Dart records as data classes
typedef Book = ({
String id,
String title,
String author,
String genre,
String annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
BookStatus status,
int? progress,
bool isFavorite,
});
// Helper function to create a Book record
Book createBook({
required String id,
required String title,
required String author,
required String genre,
required String annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
required BookStatus status,
int? progress,
required bool isFavorite,
}) {
return (
id: id,
title: title,
author: author,
genre: genre,
annotation: annotation,
coverUrl: coverUrl,
pages: pages,
language: language,
publishedYear: publishedYear,
rating: rating,
status: status,
progress: progress,
isFavorite: isFavorite,
);
}
// Extension to create Book from partial data
extension BookExtension on Book {
Book copyWith({
String? id,
String? title,
String? author,
String? genre,
String? annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
BookStatus? status,
int? progress,
bool? isFavorite,
}) {
return createBook(
id: id ?? this.id,
title: title ?? this.title,
author: author ?? this.author,
genre: genre ?? this.genre,
annotation: annotation ?? this.annotation,
coverUrl: coverUrl ?? this.coverUrl,
pages: pages ?? this.pages,
language: language ?? this.language,
publishedYear: publishedYear ?? this.publishedYear,
rating: rating ?? this.rating,
status: status ?? this.status,
progress: progress ?? this.progress,
isFavorite: isFavorite ?? this.isFavorite,
);
}
// Convert to JSON for storage/transport
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'author': author,
'genre': genre,
'annotation': annotation,
'coverUrl': coverUrl,
'pages': pages,
'language': language,
'publishedYear': publishedYear,
'rating': rating,
'status': status.name,
'progress': progress,
'isFavorite': isFavorite,
};
}
// Create Book from JSON
static Book fromJson(Map<String, dynamic> json) {
return (
id: json['id'] as String,
title: json['title'] as String,
author: json['author'] as String,
genre: json['genre'] as String,
annotation: json['annotation'] as String,
coverUrl: json['coverUrl'] as String?,
pages: json['pages'] as int?,
language: json['language'] as String?,
publishedYear: json['publishedYear'] as int?,
rating: (json['rating'] as num?)?.toDouble(),
status: BookStatus.values.firstWhere(
(e) => e.name == json['status'],
orElse: () => BookStatus.wantToRead,
),
progress: json['progress'] as int?,
isFavorite: json['isFavorite'] as bool? ?? false,
);
}
}

View File

@@ -0,0 +1,20 @@
// Book status enum
enum BookStatus {
reading,
done,
wantToRead,
}
// Extension for BookStatus to get display name
extension BookStatusExtension on BookStatus {
String get displayName {
switch (this) {
case BookStatus.reading:
return 'Reading Now';
case BookStatus.done:
return 'Completed';
case BookStatus.wantToRead:
return 'Wishlist';
}
}
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
// Category record - using Dart records as data classes
typedef Category = ({
String id,
String name,
int count,
IconData icon,
String colorClass,
});
// Helper function to create a Category record
Category createCategory({
required String id,
required String name,
required int count,
required IconData icon,
required String colorClass,
}) {
return (
id: id,
name: name,
count: count,
icon: icon,
colorClass: colorClass,
);
}

View File

@@ -1,185 +1,5 @@
import 'package:flutter/material.dart';
// App screen enum
enum AppScreen {
library,
categories,
wishlist,
settings,
details,
addBook,
scanner,
}
// Book status enum
enum BookStatus {
reading,
done,
wantToRead,
}
// Extension for BookStatus to get display name
extension BookStatusExtension on BookStatus {
String get displayName {
switch (this) {
case BookStatus.reading:
return 'Reading Now';
case BookStatus.done:
return 'Completed';
case BookStatus.wantToRead:
return 'Wishlist';
}
}
}
// Category record - using Dart records as data classes
typedef Category = ({
String id,
String name,
int count,
IconData icon,
String colorClass,
});
// Book record - using Dart records as data classes
typedef Book = ({
String id,
String title,
String author,
String genre,
String annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
BookStatus status,
int? progress,
bool isFavorite,
});
// Helper function to create a Category record
Category createCategory({
required String id,
required String name,
required int count,
required IconData icon,
required String colorClass,
}) {
return (
id: id,
name: name,
count: count,
icon: icon,
colorClass: colorClass,
);
}
// Helper function to create a Book record
Book createBook({
required String id,
required String title,
required String author,
required String genre,
required String annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
required BookStatus status,
int? progress,
required bool isFavorite,
}) {
return (
id: id,
title: title,
author: author,
genre: genre,
annotation: annotation,
coverUrl: coverUrl,
pages: pages,
language: language,
publishedYear: publishedYear,
rating: rating,
status: status,
progress: progress,
isFavorite: isFavorite,
);
}
// Extension to create Book from partial data
extension BookExtension on Book {
Book copyWith({
String? id,
String? title,
String? author,
String? genre,
String? annotation,
String? coverUrl,
int? pages,
String? language,
int? publishedYear,
double? rating,
BookStatus? status,
int? progress,
bool? isFavorite,
}) {
return createBook(
id: id ?? this.id,
title: title ?? this.title,
author: author ?? this.author,
genre: genre ?? this.genre,
annotation: annotation ?? this.annotation,
coverUrl: coverUrl ?? this.coverUrl,
pages: pages ?? this.pages,
language: language ?? this.language,
publishedYear: publishedYear ?? this.publishedYear,
rating: rating ?? this.rating,
status: status ?? this.status,
progress: progress ?? this.progress,
isFavorite: isFavorite ?? this.isFavorite,
);
}
// Convert to JSON for storage/transport
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'author': author,
'genre': genre,
'annotation': annotation,
'coverUrl': coverUrl,
'pages': pages,
'language': language,
'publishedYear': publishedYear,
'rating': rating,
'status': status.name,
'progress': progress,
'isFavorite': isFavorite,
};
}
// Create Book from JSON
static Book fromJson(Map<String, dynamic> json) {
return (
id: json['id'] as String,
title: json['title'] as String,
author: json['author'] as String,
genre: json['genre'] as String,
annotation: json['annotation'] as String,
coverUrl: json['coverUrl'] as String?,
pages: json['pages'] as int?,
language: json['language'] as String?,
publishedYear: json['publishedYear'] as int?,
rating: (json['rating'] as num?)?.toDouble(),
status: BookStatus.values.firstWhere(
(e) => e.name == json['status'],
orElse: () => BookStatus.wantToRead,
),
progress: json['progress'] as int?,
isFavorite: json['isFavorite'] as bool? ?? false,
);
}
}
// Barrel export for all models
export 'app_screen.dart';
export 'book_status.dart';
export 'category.dart';
export 'book.dart';