From 3209827e92fd16c97acf79b29474381b9afbf5d5 Mon Sep 17 00:00:00 2001 From: Yuriy Panov Date: Sun, 8 Feb 2026 12:05:05 +0600 Subject: [PATCH] open ai service --- books_flutter/.vscode/launch.json | 31 ++ books_flutter/CLAUDE.md | 300 ++++++++++++++++++ books_flutter/OPENAI_SERVICE_SETUP.md | 133 ++++++++ books_flutter/REFACTORING_SUMMARY.md | 241 ++++++++++++++ books_flutter/SCANNER_SETUP.md | 131 ++++++++ books_flutter/TESTING_GUIDE.md | 102 ++++++ books_flutter/assets/icon/ICON_SPECS.md | 113 +++++++ books_flutter/assets/icon/README.md | 137 ++++++++ .../lib/bloc/add_book/add_book_bloc.dart | 97 ++++++ .../lib/bloc/add_book/add_book_event.dart | 37 +++ .../lib/bloc/add_book/add_book_state.dart | 39 +++ books_flutter/lib/bloc/book/book_bloc.dart | 47 +++ books_flutter/lib/bloc/book/book_event.dart | 23 ++ books_flutter/lib/bloc/book/book_state.dart | 6 + .../lib/bloc/library/library_bloc.dart | 21 ++ .../lib/bloc/library/library_event.dart | 11 + .../lib/bloc/library/library_state.dart | 13 + .../lib/bloc/scanner/scanner_bloc.dart | 126 ++++++++ .../lib/bloc/scanner/scanner_event.dart | 21 ++ .../lib/bloc/scanner/scanner_state.dart | 39 +++ books_flutter/lib/config/api_config.dart | 19 ++ books_flutter/lib/models/book.dart | 15 + books_flutter/lib/models/category.dart | 10 + .../lib/services/camera_service.dart | 90 ++++++ .../lib/services/openai_service.dart | 152 +++++++++ .../lib/widgets/bottom_nav_shell.dart | 99 ++++++ .../samples/photo_2026-02-07_15-05-17.jpg | Bin 0 -> 111499 bytes books_flutter/test/openai_service_test.dart | 58 ++++ books_flutter/test_openai_service.dart | 64 ++++ 29 files changed, 2175 insertions(+) create mode 100644 books_flutter/.vscode/launch.json create mode 100644 books_flutter/CLAUDE.md create mode 100644 books_flutter/OPENAI_SERVICE_SETUP.md create mode 100644 books_flutter/REFACTORING_SUMMARY.md create mode 100644 books_flutter/SCANNER_SETUP.md create mode 100644 books_flutter/TESTING_GUIDE.md create mode 100644 books_flutter/assets/icon/ICON_SPECS.md create mode 100644 books_flutter/assets/icon/README.md create mode 100644 books_flutter/lib/bloc/add_book/add_book_bloc.dart create mode 100644 books_flutter/lib/bloc/add_book/add_book_event.dart create mode 100644 books_flutter/lib/bloc/add_book/add_book_state.dart create mode 100644 books_flutter/lib/bloc/book/book_bloc.dart create mode 100644 books_flutter/lib/bloc/book/book_event.dart create mode 100644 books_flutter/lib/bloc/book/book_state.dart create mode 100644 books_flutter/lib/bloc/library/library_bloc.dart create mode 100644 books_flutter/lib/bloc/library/library_event.dart create mode 100644 books_flutter/lib/bloc/library/library_state.dart create mode 100644 books_flutter/lib/bloc/scanner/scanner_bloc.dart create mode 100644 books_flutter/lib/bloc/scanner/scanner_event.dart create mode 100644 books_flutter/lib/bloc/scanner/scanner_state.dart create mode 100644 books_flutter/lib/config/api_config.dart create mode 100644 books_flutter/lib/models/book.dart create mode 100644 books_flutter/lib/models/category.dart create mode 100644 books_flutter/lib/services/camera_service.dart create mode 100644 books_flutter/lib/services/openai_service.dart create mode 100644 books_flutter/lib/widgets/bottom_nav_shell.dart create mode 100644 books_flutter/samples/photo_2026-02-07_15-05-17.jpg create mode 100644 books_flutter/test/openai_service_test.dart create mode 100644 books_flutter/test_openai_service.dart diff --git a/books_flutter/.vscode/launch.json b/books_flutter/.vscode/launch.json new file mode 100644 index 0000000..edc44da --- /dev/null +++ b/books_flutter/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter", + "type": "dart", + "request": "launch", + "program": "lib/main.dart" + }, + { + "name": "books_flutter", + "request": "launch", + "type": "dart" + }, + { + "name": "books_flutter (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "books_flutter (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/books_flutter/CLAUDE.md b/books_flutter/CLAUDE.md new file mode 100644 index 0000000..7234422 --- /dev/null +++ b/books_flutter/CLAUDE.md @@ -0,0 +1,300 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Flutter mobile application for managing a personal book library. The app features book cataloging, categorization, reading status tracking, and cover scanning capabilities (via camera + Gemini AI). + +**Tech Stack:** +- Flutter SDK ^3.10.8 +- State Management: flutter_bloc ^9.1.0 +- UI: Material 3 with Google Fonts (Inter) +- AI: google_generative_ai ^0.4.6 (for book cover analysis) +- Camera: camera ^0.11.1 + +## Development Commands + +### Running the App +```bash +# Run on connected device/simulator +flutter run + +# Run on specific device +flutter run -d + +# Run in release mode +flutter run --release + +# List available devices +flutter devices +``` + +### Building +```bash +# Build APK for Android +flutter build apk + +# Build iOS +flutter build ios + +# Build for web (not currently configured) +flutter build web +``` + +### Code Quality +```bash +# Install/update dependencies +flutter pub get + +# Check for outdated packages +flutter pub outdated + +# Run static analysis +flutter analyze + +# Format all Dart files +flutter format lib/ + +# Run tests +flutter test +``` + +### Using Dart MCP Tools +When the Dart MCP server is available, prefer using these tools instead of bash commands: +- `mcp__dart__analyze_files` instead of `flutter analyze` +- `mcp__dart__dart_format` instead of `flutter format` +- `mcp__dart__run_tests` instead of `flutter test` +- `mcp__dart__list_devices` to see available devices +- `mcp__dart__launch_app` to run the app with DTD integration + +## Architecture + +### State Management (BLoC Pattern) + +The app uses **flutter_bloc** for state management with two main BLoCs: + +#### 1. BookBloc (`lib/bloc/book_bloc.dart`) +Manages the book collection state and operations: +- **State**: `BookState` containing `List` +- **Events**: + - `AddBook(book)` - Add new book to library + - `UpdateBook(book)` - Update existing book + - `DeleteBook(id)` - Remove book from library + - `ToggleFavorite(id)` - Toggle favorite status +- **Initial State**: Loads from `initialBooks` in `constants.dart` +- **Note**: Currently uses in-memory storage; no persistence layer + +#### 2. NavigationBloc (`lib/bloc/navigation_bloc.dart`) +Manages app navigation and screen state: +- **State**: `NavigationState` with: + - `screen` (AppScreen enum) - Current screen + - `selectedBook` - Book being viewed/edited + - `prefilledData` - Data for pre-populating forms +- **Event**: `NavigateTo(screen, {selectedBook, prefilledData})` +- **Pattern**: Declarative navigation where UI rebuilds based on state +- **Important**: `prefilledData` is used when scanning covers to prefill book form, while `selectedBook` is used for editing existing books + +### Data Models (`lib/models/models.dart`) + +Uses Dart 3 **record types** for immutability: + +```dart +typedef Book = ({ + String id, + String title, + String author, + String genre, + String annotation, + String? coverUrl, + int? pages, + String? language, + int? publishedYear, + double? rating, + String status, // 'reading', 'done', 'want_to_read' + double? progress, // 0-100 for reading progress + bool isFavorite, +}); +``` + +**Important**: Records are immutable. To update a book, create a new record with updated fields using record syntax: +```dart +final updatedBook = ( + id: book.id, + title: newTitle, + // ... copy all other fields +); +``` + +### Navigation Flow + +The app uses a custom navigation system via `NavigationBloc`: + +1. **Library Screen** (default) → Shows all books in grid/category view +2. **Categories Screen** → Browse books by predefined categories +3. **Book Details** → View/edit single book (triggered by tapping book card) +4. **Add/Edit Book** → Form for adding new books or editing existing +5. **Scanner Screen** → Camera interface for scanning book covers +6. **Wishlist/Settings** → Placeholder screens + +**Navigation Pattern:** +```dart +context.read().add( + NavigateTo(AppScreen.details, selectedBook: book) +); +``` + +The main shell (`_AppShell` in `main.dart`) rebuilds based on `NavigationState.screen`. + +### Theme System (Material 3) + +**Critical**: This app uses a **centralized theme system**. Never hardcode colors, text styles, or spacing. + +**Theme Files:** +- `lib/theme/app_colors.dart` - Semantic color constants (cyan-based palette) +- `lib/theme/app_spacing.dart` - Spacing scale (8px base) and border radius +- `lib/theme/app_theme.dart` - Material 3 ThemeData with component themes + +**Usage Pattern:** +```dart +final colorScheme = Theme.of(context).colorScheme; +final textTheme = Theme.of(context).textTheme; + +// Use semantic colors +Container(color: colorScheme.primary) + +// Use text styles +Text('Title', style: textTheme.displayMedium) + +// Use spacing constants +Padding(padding: EdgeInsets.all(AppSpacing.md)) + +// Use shadows +BoxDecoration(boxShadow: AppTheme.shadowMd) +``` + +**Color Scheme:** +- Primary: #0891B2 (Cyan-600) +- Success/CTA: #22C55E (Green-500) +- Background: #ECFEFF (Cyan-50) +- Surface: #FFFFFF + +**Typography:** Inter font family loaded via Google Fonts with weights 300-700. + +### Screen Structure + +All main screens follow this pattern: +1. Wrap in `SafeArea` for notch/status bar handling +2. Use `BlocBuilder` to listen to relevant BLoC state +3. Access theme via `Theme.of(context)` +4. Use `AppSpacing.*` constants for all padding/margins +5. Use theme colors and text styles exclusively + +**Example:** +```dart +class MyScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return SafeArea( + child: BlocBuilder( + builder: (context, state) { + return Padding( + padding: EdgeInsets.all(AppSpacing.lg), + child: Text( + 'Hello', + style: textTheme.headlineMedium, + ), + ); + }, + ), + ); + } +} +``` + +### Widget Conventions + +**BookCard** (`lib/widgets/book_card.dart`): +- Displays book cover with metadata overlay +- Includes Hero animation with tag `'book-cover-${book.id}'` +- Shows shimmer loading while image loads +- Has hover effect on desktop/web (1.02 scale) +- Displays favorite badge, status badge, or progress bar based on book state + +**Hero Animations:** +When navigating from library to book details, book covers animate smoothly: +```dart +Hero( + tag: 'book-cover-${book.id}', + child: Image.network(book.coverUrl), +) +``` +Both `BookCard` and `BookDetailsScreen` must use matching tags. + +### Gemini AI Integration + +The `GeminiService` (`lib/services/gemini_service.dart`) is a placeholder for future AI-powered book cover scanning: +- Takes base64-encoded image from camera +- Will analyze cover and extract metadata (title, author, etc.) +- Returns `Book?` record with prefilled data +- Currently returns `null` - implementation pending + +**Intended Flow:** +1. User opens Scanner Screen +2. Takes photo of book cover +3. Image sent to `GeminiService.analyzeBookCover()` +4. Extracted data passed to Add Book screen via `NavigationBloc` with `prefilledData` + +## Important Patterns + +### When Adding New Features + +1. **New Book Fields**: Update the `Book` typedef in `models.dart` and all places that construct book records +2. **New Screens**: Add to `AppScreen` enum, handle in `_AppShell` switch statement +3. **Theme Changes**: Only modify theme files, never inline styles +4. **Navigation**: Always use `NavigationBloc`, never `Navigator.push()` + +### Code Style Requirements + +- **Immutability**: Use records for data models, never mutable classes +- **Theme Compliance**: Zero hardcoded colors/styles/spacing +- **BLoC Pattern**: UI is always a pure function of state +- **Const Constructors**: Use `const` for all stateless widgets and values +- **Reduced Motion**: Check `MediaQuery.of(context).disableAnimations` for animations + +### Testing Gotchas + +- Books are stored in-memory only; restarting app resets to `initialBooks` +- Camera requires physical device or simulator with camera support +- Gemini API requires valid API key (not implemented yet) +- Hero animations require matching tags between screens + +## Project-Specific Notes + +### Why Records Instead of Classes? + +This codebase uses Dart 3 record types for immutability and simplicity. When updating books, create new records rather than mutating fields. This makes BLoC state updates predictable and prevents accidental mutations. + +### Navigation Without Navigator + +The app doesn't use Flutter's built-in Navigator. Instead, `NavigationBloc` tracks the current screen, and `_AppShell` rebuilds the entire UI tree based on state. This gives centralized control over navigation state but means: +- No native back button handling (would need to emit `NavigateTo` events) +- No deep linking support (yet) +- All screens must be handled in the main switch statement + +### Initial Data + +Books are initialized from `initialBooks` constant in `lib/constants/constants.dart`. Categories are defined in the same file. To add sample data, modify these constants. + +### Future Enhancements + +Based on the codebase structure, likely next steps: +- Implement persistence (SharedPreferences, SQLite, or Firebase) +- Complete Gemini AI integration for cover scanning +- Add native back button handling +- Implement book search/filtering +- Add reading statistics/charts +- Support for book series and collections diff --git a/books_flutter/OPENAI_SERVICE_SETUP.md b/books_flutter/OPENAI_SERVICE_SETUP.md new file mode 100644 index 0000000..9217b61 --- /dev/null +++ b/books_flutter/OPENAI_SERVICE_SETUP.md @@ -0,0 +1,133 @@ +# OpenAI Service Setup Guide + +This document explains how to configure and use the OpenAI service for parsing book metadata from images. + +## Overview + +The Bookshelf app now supports two AI services for book cover analysis: +1. **Google Gemini** (original service) +2. **OpenAI** (new alternate service) + +The app will automatically try OpenAI first if configured, and fall back to Gemini if OpenAI fails or is not configured. + +## Configuration + +### Step 1: Configure API Keys + +Edit `lib/config/api_config.dart`: + +```dart +class ApiConfig { + // Gemini API (original service) + static const String geminiApiKey = 'YOUR_GEMINI_API_KEY_HERE'; + + // OpenAI API (new service) + static const String openaiApiKey = 'YOUR_OPENAI_API_KEY_HERE'; + + // OpenAI API endpoint + static const String openaiBaseUrl = 'http://localhost:8317'; +} +``` + +### Step 2: Replace API Keys + +Replace the placeholder values with your actual API keys: + +- **OpenAI API Key**: Get from your OpenAI account or local OpenAI-compatible server +- **Gemini API Key**: Get from [Google AI Studio](https://makersuite.google.com/app/apikey) (fallback) + +## OpenAI Service Details + +### Endpoint Configuration + +The OpenAI service is configured to use: +- **Default endpoint**: `http://localhost:8317/v1/chat/completions` +- **Model**: `glm-4` (vision-capable) +- **Max tokens**: 500 +- **Temperature**: 0.3 + +**Important**: The server must support the `glm-4` model. This is a required model name for this OpenAI-compatible endpoint. + +You can customize the endpoint by changing the `openaiBaseUrl` in `api_config.dart`. + +### How It Works + +1. The app captures an image using the camera +2. The image is converted to base64 format +3. A prompt is sent to the OpenAI API with the image +4. The API analyzes the image and extracts: + - Book title + - Author name + - Genre (fiction/fantasy/science/detective/biography/other) + - Brief annotation/description +5. The parsed data is returned and pre-filled in the add book form + +### Service Priority + +The app follows this priority order: +1. **OpenAI** (if API key is configured) - tried first +2. **Gemini** (if OpenAI fails or is not configured) - fallback + +This ensures you always have a working service available. + +## Testing + +To test the OpenAI service: + +1. Make sure your OpenAI server is running at `http://localhost:8317` +2. Configure your OpenAI API key in `api_config.dart` +3. Run the app: `flutter run` +4. Navigate to the "Add Book" screen +5. Tap the camera icon to scan a book cover +6. The app will use OpenAI to analyze the image + +## Troubleshooting + +### "API ключ не настроен (ни OpenAI, ни Gemini)" + +This error means neither OpenAI nor Gemini API keys are configured. At least one must be set in `api_config.dart`. + +### "Не удалось распознать книгу" + +This can occur if: +- The API request failed (check your server logs) +- The image quality is poor +- The API returned invalid JSON +- Network connectivity issues + +### OpenAI Service Not Working + +If OpenAI fails, the app will automatically fall back to Gemini. Check the console output to see which service is being used: + +``` +Using OpenAI service for analysis +``` +or +``` +Using Gemini service for analysis +``` + +### Network Issues + +Make sure: +- Your OpenAI server is accessible from the device/emulator +- For Android emulator: Use `10.0.2.2` instead of `localhost` +- For iOS simulator: `localhost` should work +- For physical device: Use your machine's actual IP address + +## Files Modified + +1. **lib/services/openai_service.dart** - New OpenAI service implementation +2. **lib/config/api_config.dart** - Added OpenAI configuration +3. **lib/screens/scanner_screen.dart** - Updated to support both services +4. **lib/screens/add_book_screen.dart** - Updated to pass OpenAI configuration +5. **pubspec.yaml** - Added `http` package dependency + +## Future Enhancements + +Possible improvements: +- Add UI option to manually select which service to use +- Add retry logic with different services +- Implement caching of recognized books +- Add support for multiple OpenAI models +- Add detailed error messages and logging \ No newline at end of file diff --git a/books_flutter/REFACTORING_SUMMARY.md b/books_flutter/REFACTORING_SUMMARY.md new file mode 100644 index 0000000..6610644 --- /dev/null +++ b/books_flutter/REFACTORING_SUMMARY.md @@ -0,0 +1,241 @@ +# Refactoring Summary: BLoC Architecture Improvements + +## Overview + +All screens have been refactored to follow best practices with dedicated BLoCs and separate event/state files. This improves code organization, testability, and maintainability. + +## Architecture Pattern + +Each screen now follows this structure: +``` +lib/bloc/ + ├── [feature]_event.dart # Event definitions + ├── [feature]_state.dart # State definitions + └── [feature]_bloc.dart # Business logic + +lib/screens/ + └── [feature]_screen.dart # UI only (stateless) +``` + +## Changes Made + +### 1. BookBloc (Refactored) +**Files Created:** +- `lib/bloc/book_event.dart` - Events: AddBook, UpdateBook, DeleteBook, ToggleFavorite +- `lib/bloc/book_state.dart` - State containing List + +**Changes:** +- Separated events and state from main BLoC file +- BLoC handles global book collection management +- Used across all screens for book data access + +### 2. ScannerBloc (New) +**Files Created:** +- `lib/bloc/scanner_event.dart` - Events: InitializeCamera, CaptureAndAnalyze, SwitchCamera, DismissError +- `lib/bloc/scanner_state.dart` - State: isInitialized, isCapturing, isAnalyzing, hasPermissionError, errorMessage, analyzedBook +- `lib/bloc/scanner_bloc.dart` - Camera and AI analysis business logic + +**Screen Changes:** +- `lib/screens/scanner_screen.dart` converted from StatefulWidget to StatelessWidget +- Removed all setState() calls and local state management +- Uses BlocProvider for state management +- Uses BlocListener for side effects (errors, navigation) +- Uses BlocBuilder for reactive UI + +**Business Logic Moved to BLoC:** +- Camera initialization and permission handling +- Image capture process +- AI service selection (OpenAI first, Gemini fallback) +- Error state management +- Temporary file cleanup + +### 3. LibraryBloc (New) +**Files Created:** +- `lib/bloc/library_event.dart` - Events: UpdateSearchQuery, ChangeTab +- `lib/bloc/library_state.dart` - State: searchQuery, tabIndex +- `lib/bloc/library_bloc.dart` - Search and tab management logic + +**Screen Changes:** +- `lib/screens/library_screen.dart` converted from StatefulWidget to StatelessWidget +- Removed local state (_search, _tabIndex) +- Uses LibraryBloc for UI state +- Uses BookBloc for book data +- Nested BlocBuilders for optimal rebuilds + +**Business Logic Moved to BLoC:** +- Search query management +- Tab selection state +- Book filtering logic (still in UI, but uses BLoC state) + +### 4. AddBookBloc (New) +**Files Created:** +- `lib/bloc/add_book_event.dart` - Events: InitializeForm, UpdateTitle, UpdateAuthor, UpdateAnnotation, UpdateGenre, ApplyScannedBook, SaveBook +- `lib/bloc/add_book_state.dart` - State: title, author, annotation, genre, editBook, isSaved +- `lib/bloc/add_book_bloc.dart` - Form management and save logic + +**Screen Changes:** +- `lib/screens/add_book_screen.dart` converted outer widget to StatelessWidget +- Created internal StatefulWidget for TextController lifecycle +- Uses BlocProvider with callbacks to BookBloc +- Uses BlocListener to update controllers and handle navigation +- Uses BlocBuilder for reactive form state + +**Business Logic Moved to BLoC:** +- Form field state management +- Edit vs Add mode detection +- Scanned book data application +- Book creation/update logic with proper field mapping +- Save completion state + +### 5. BookDetailsScreen (No Changes) +**Status:** Already stateless and has minimal business logic +- Displays book data passed as parameter +- Navigates to edit screen +- Calls BookBloc for delete operation +- No dedicated BLoC needed as it's a simple presentation screen + +## Benefits + +### ✅ Separation of Concerns +- UI components only handle presentation +- Business logic isolated in BLoCs +- Clear boundaries between layers + +### ✅ Testability +- BLoCs can be unit tested independently +- No UI dependencies in business logic +- Events and states are simple data classes + +### ✅ Maintainability +- Each file has single responsibility +- Easy to locate and modify logic +- Consistent pattern across all screens + +### ✅ Scalability +- Easy to add new events and states +- BLoCs can be reused across screens +- State changes are predictable and traceable + +### ✅ Reduced Boilerplate +- No manual setState() management +- Automatic UI rebuilds on state changes +- Side effects handled declaratively + +## File Structure + +``` +lib/ +├── bloc/ +│ ├── book_event.dart # Book collection events +│ ├── book_state.dart # Book collection state +│ ├── book_bloc.dart # Book collection logic +│ ├── scanner_event.dart # Scanner events +│ ├── scanner_state.dart # Scanner state +│ ├── scanner_bloc.dart # Scanner logic +│ ├── library_event.dart # Library UI events +│ ├── library_state.dart # Library UI state +│ ├── library_bloc.dart # Library UI logic +│ ├── add_book_event.dart # Add/Edit book events +│ ├── add_book_state.dart # Add/Edit book state +│ └── add_book_bloc.dart # Add/Edit book logic +├── screens/ +│ ├── library_screen.dart # Stateless - uses LibraryBloc + BookBloc +│ ├── scanner_screen.dart # Stateless - uses ScannerBloc +│ ├── add_book_screen.dart # Stateless wrapper + Stateful content +│ └── book_details_screen.dart # Stateless - no dedicated BLoC +└── ... +``` + +## Migration Guide + +### Before (StatefulWidget with setState): +```dart +class MyScreen extends StatefulWidget { + @override + State createState() => _MyScreenState(); +} + +class _MyScreenState extends State { + String _value = ''; + + void _updateValue(String newValue) { + setState(() => _value = newValue); + } + + @override + Widget build(BuildContext context) { + return Text(_value); + } +} +``` + +### After (StatelessWidget with BLoC): +```dart +// Event +class UpdateValue extends MyEvent { + final String value; + UpdateValue(this.value); +} + +// State +class MyState { + final String value; + const MyState({this.value = ''}); + MyState copyWith({String? value}) => MyState(value: value ?? this.value); +} + +// BLoC +class MyBloc extends Bloc { + MyBloc() : super(const MyState()) { + on((event, emit) => emit(state.copyWith(value: event.value))); + } +} + +// Screen +class MyScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => MyBloc(), + child: BlocBuilder( + builder: (context, state) => Text(state.value), + ), + ); + } +} +``` + +## Testing Recommendations + +### Unit Tests for BLoCs: +```dart +test('UpdateSearchQuery updates search query', () { + final bloc = LibraryBloc(); + bloc.add(UpdateSearchQuery('test')); + expect(bloc.state.searchQuery, 'test'); +}); +``` + +### Widget Tests for Screens: +```dart +testWidgets('LibraryScreen displays books', (tester) async { + await tester.pumpWidget( + MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => BookBloc()), + BlocProvider(create: (_) => LibraryBloc()), + ], + child: MaterialApp(home: LibraryScreen()), + ), + ); + expect(find.byType(BookCard), findsWidgets); +}); +``` + +## Next Steps + +1. Add unit tests for all BLoCs +2. Add widget tests for all screens +3. Consider adding integration tests +4. Monitor performance and optimize if needed +5. Document any screen-specific BLoC patterns diff --git a/books_flutter/SCANNER_SETUP.md b/books_flutter/SCANNER_SETUP.md new file mode 100644 index 0000000..b8249a5 --- /dev/null +++ b/books_flutter/SCANNER_SETUP.md @@ -0,0 +1,131 @@ +# Book Scanner Setup Guide + +The book scanning feature allows users to scan book covers using their device camera and automatically extract book information using Google Gemini AI. + +## Prerequisites + +1. **Google Gemini API Key**: Get your free API key from [Google AI Studio](https://makersuite.google.com/app/apikey) +2. **Device with camera**: The feature requires a camera (front or back) +3. **Camera permissions**: Users must grant camera access when prompted + +## Setup Instructions + +### 1. Add Your Gemini API Key + +Edit the `lib/config/api_config.dart` file and replace the placeholder: + +```dart +class ApiConfig { + // TODO: Replace with your actual Gemini API key + static const String geminiApiKey = 'YOUR_GEMINI_API_KEY_HERE'; +} +``` + +Replace `YOUR_GEMINI_API_KEY_HERE` with your actual Google Gemini API key. + +### 2. Permissions + +The app automatically requests camera permissions. However, you may need to configure platform-specific settings: + +#### Android +- Camera permissions are already configured in `android/app/src/main/AndroidManifest.xml` +- No additional setup required + +#### iOS +- Camera usage description is configured in `ios/Runner/Info.plist` +- The app will request camera permission when first launched + +## How It Works + +1. **Camera Preview**: The scanner screen shows a live camera preview with a scanning frame +2. **Capture**: Users tap the capture button to take a photo of the book cover +3. **AI Analysis**: The image is sent to Google Gemini AI for analysis +4. **Book Extraction**: Gemini extracts: + - Book title + - Author name + - Genre (categorized into: fiction, fantasy, science, detective, biography, other) + - Annotation/description +5. **Auto-fill**: The extracted information automatically fills the book form + +## Usage + +1. Open the "Add Book" screen +2. Tap the camera/scanner area +3. Grant camera permissions if prompted +4. Position the book cover within the scanning frame +5. Ensure the text is clearly visible and readable +6. Tap the capture button (large white circle) +7. Wait for the AI analysis (2-5 seconds) +8. Review and edit the auto-filled information if needed +9. Save the book + +## Tips for Better Scanning + +- Ensure good lighting +- Hold the device steady +- Position the book cover within the green scanning frame +- Make sure text is not blurred or obscured +- Use high contrast books (avoid glare or reflections) +- Try different angles if the first scan doesn't work + +## Troubleshooting + +### Camera not working +- Check if camera permissions are granted +- Close other apps that might be using the camera +- Restart the app + +### Scanning fails or produces incorrect results +- Ensure the book cover text is clearly visible +- Try scanning in better lighting conditions +- Some covers with complex designs may be harder to recognize +- You can always manually edit the extracted information + +### API errors +- Verify your Gemini API key is correctly configured +- Check your internet connection +- Ensure you have available API quota (free tier is generous) + +## Technical Details + +### Services Created + +1. **CameraService** (`lib/services/camera_service.dart`) + - Manages camera initialization and lifecycle + - Handles permissions + - Provides image capture functionality + +2. **GeminiService** (`lib/services/gemini_service.dart`) + - Integrates with Google Gemini AI + - Processes book cover images + - Extracts structured book metadata + - Handles error cases gracefully + +### Dependencies Added + +- `camera: ^0.11.1` - Camera functionality +- `google_generative_ai: ^0.4.6` - Gemini AI integration +- `permission_handler: ^11.0.0` - Permission management + +### Privacy & Security + +- Images are sent to Google's servers for AI analysis +- Temporary images are deleted after processing +- API keys should be kept secure and not committed to version control +- Consider using environment variables for API keys in production + +## Cost Considerations + +- Google Gemini API has a generous free tier +- Typical book scan uses minimal tokens +- Monitor your API usage in the Google Cloud Console if needed + +## Future Enhancements + +Potential improvements to consider: +- Barcode/ISBN scanning as alternative +- Offline scanning capability +- Batch scanning for multiple books +- Image quality enhancement before sending to AI +- Support for multiple languages +- Custom AI prompts for better recognition \ No newline at end of file diff --git a/books_flutter/TESTING_GUIDE.md b/books_flutter/TESTING_GUIDE.md new file mode 100644 index 0000000..e86b1e2 --- /dev/null +++ b/books_flutter/TESTING_GUIDE.md @@ -0,0 +1,102 @@ +# Testing OpenAI Service + +This guide explains how to test the OpenAI service with the sample book cover image. + +## Quick Test (Recommended) + +Use the standalone test script for quick testing: + +### Step 1: Set Your API Key + +Edit `test_openai_service.dart` and replace the placeholder: + +```dart +const apiKey = 'YOUR_OPENAI_API_KEY_HERE'; +``` + +Replace `YOUR_OPENAI_API_KEY_HERE` with your actual OpenAI API key. + +### Step 2: Run the Test + +From the project root directory, run: + +```bash +dart run test_openai_service.dart +``` + +## Expected Output + +If successful, you'll see: + +``` +======================================== +📖 Testing OpenAI Book Cover Analysis +======================================== + +Image path: samples/photo_2026-02-07_15-05-17.jpg +Image size: XXXXXX bytes +API endpoint: http://localhost:8317/v1/chat/completions + +Analyzing book cover... (this may take a few seconds) + +======================================== +✅ Successfully analyzed book cover! +======================================== + +📚 Book Details: + Title: [Book Title] + Author: [Author Name] + Genre: [Genre] + Annotation: [Book description] + Language: Russian + Published Year: 2026 + Rating: 5.0 + +======================================== +``` + +## Troubleshooting + +### Error: "Please set your OpenAI API key" + +You need to edit `test_openai_service.dart` and add your actual API key. + +### Error: "Image file not found" + +Make sure you're running the test from the project root directory where the `samples/` folder is located. + +### Error: "Failed to analyze book cover" + +Check the following: + +1. **Server Running**: Ensure your OpenAI server is running at `http://localhost:8317` +2. **API Key**: Verify your API key is correct +3. **Server Logs**: Check your OpenAI server logs for errors +4. **Model Support**: Ensure your server supports the `glm-4` model +5. **Network**: Check network connectivity + +### Testing on Different Environments + +If you're testing from a different environment (not the same machine running the server), update the `baseUrl`: + +```dart +const baseUrl = 'http://YOUR_SERVER_IP:8317'; // For remote server +// or +const baseUrl = 'http://10.0.2.2:8317'; // For Android emulator +``` + +## Running Formal Flutter Tests + +If you prefer to run the formal Flutter test: + +```bash +flutter test test/openai_service_test.dart +``` + +Make sure to update the API key in `test/openai_service_test.dart` before running. + +## Sample Image + +The test uses the sample image at: `samples/photo_2026-02-07_15-05-17.jpg` + +You can replace this with any book cover image you want to test. \ No newline at end of file diff --git a/books_flutter/assets/icon/ICON_SPECS.md b/books_flutter/assets/icon/ICON_SPECS.md new file mode 100644 index 0000000..7c4368e --- /dev/null +++ b/books_flutter/assets/icon/ICON_SPECS.md @@ -0,0 +1,113 @@ +# Open Book Icon - Exact Specifications + +## Design Layout (1024x1024px) + +``` +┌─────────────────────────────────┐ +│ │ +│ (padding: 150px) │ +│ │ +│ ┌───────────────────┐ │ +│ │ │ │ +│ │ 📖 Open Book │ │ +│ │ White (#FFF) │ │ +│ │ ~700px width │ │ +│ │ │ │ +│ └───────────────────┘ │ +│ │ +│ Background: #0891B2 │ +│ │ +└─────────────────────────────────┘ +``` + +## Two Files Needed + +### 1. app_icon.png (Complete Icon) +- Size: 1024x1024px +- Background: Solid cyan `#0891B2` +- Icon: White open book, centered +- Icon size: ~700px wide, maintains aspect ratio +- Padding: ~150px from edges + +### 2. app_icon_foreground.png (Adaptive Icon Foreground) +- Size: 1024x1024px +- Background: **Transparent** +- Icon: Same white book as above +- Keep in "safe zone": center 66% of canvas (~676x676px) +- Android will add the cyan background automatically + +--- + +## Color Codes (Copy-Paste Ready) + +- Cyan Background: `#0891B2` or `rgb(8, 145, 178)` +- Icon Color: `#FFFFFF` or `rgb(255, 255, 255)` + +--- + +## Recommended Free Book Icons (Download & Use) + +### Option A: Heroicons Book-Open (Clean, Modern) +- URL: https://heroicons.com +- Search: "book-open" +- Style: Outline (recommended) or Solid +- License: MIT (free to use) + +### Option B: Lucide Book-Open (Minimal) +- URL: https://lucide.dev/icons/book-open +- Very clean, minimal design +- License: ISC (free to use) + +### Option C: Bootstrap Icons Book (Simple) +- URL: https://icons.getbootstrap.com/icons/book/ +- Several book variants available +- License: MIT + +--- + +## Quick Canva Instructions + +1. **Create design:** + - Go to Canva → "Custom size" → 1024 x 1024 + +2. **Version 1 (app_icon.png):** + - Background: Click background → Color → `#0891B2` + - Elements → Upload downloaded SVG book icon + - Change icon color to white + - Resize to 600-700px, center it + - Download → PNG → save as `app_icon.png` + +3. **Version 2 (app_icon_foreground.png):** + - Duplicate the design + - Remove background (make transparent) + - Keep only the white book + - Download → PNG → **check "Transparent background"** + - Save as `app_icon_foreground.png` + +--- + +## What the Final Icon Will Look Like + +**On Home Screen:** +- iOS: Rounded square with cyan background + white book +- Android (modern): Adaptive shape (circle/squircle/square) with cyan background + white book +- Android (old): Rounded square like iOS + +**Visual Balance:** +- Book icon should be easily recognizable even at 40x40px +- Good contrast ensures readability +- White on cyan matches your app's Material 3 theme + +--- + +## Alternative: Use Icon Generator + +If you prefer automated approach: + +1. Go to [icon.kitchen](https://icon.kitchen) +2. Upload any book icon image +3. Set background color: `#0891B2` +4. Adjust size/position +5. Download all sizes + +(But manual creation gives you more control!) diff --git a/books_flutter/assets/icon/README.md b/books_flutter/assets/icon/README.md new file mode 100644 index 0000000..f9e9a49 --- /dev/null +++ b/books_flutter/assets/icon/README.md @@ -0,0 +1,137 @@ +# App Icon Creation Guide + +Your icon system is now configured! You need to create **two icon images** and place them in this directory. + +## Required Files + +1. **app_icon.png** (1024x1024px) - Main icon for iOS +2. **app_icon_foreground.png** (1024x1024px) - Foreground for Android adaptive icon + +## Design Recommendations + +**Theme Colors:** +- Primary Cyan: `#0891B2` (already set as adaptive background) +- Success Green: `#22C55E` +- White: `#FFFFFF` + +**Style:** Clean, minimalistic, book-themed + +--- + +## Option 1: Free Online Icon Makers (Easiest) + +### A) Canva (Recommended) +1. Go to [canva.com](https://www.canva.com) +2. Create custom size: 1024x1024px +3. Use their free templates or design from scratch: + - Search "book icon" or "library icon" + - Change background color to `#0891B2` + - Add white book symbol/icon +4. Download as PNG (both with and without background) + +### B) Figma (More Control) +1. Go to [figma.com](https://www.figma.com) (free account) +2. Create 1024x1024 frame +3. Design suggestions: + - **Simple:** Rectangle with rounded corners (#0891B2) + white book emoji 📚 + - **Modern:** Gradient (cyan to teal) + minimalist book outline + - **Detailed:** Stack of 3 books with slight perspective + +### C) App Icon Generators +- [appicon.co](https://appicon.co) - Upload an image, generates all sizes +- [makeappicon.com](https://makeappicon.com) - Similar service +- [iconkitchen.com](https://icon.kitchen) - Android Studio's web tool + +--- + +## Option 2: Quick Placeholder (For Testing) + +Create a simple solid color icon with text: + +1. Use any image editor (even Preview on Mac) +2. Create 1024x1024px canvas +3. Fill with `#0891B2` +4. Add white text: "📚" or "BL" (Book Library) +5. Save as `app_icon.png` and `app_icon_foreground.png` + +--- + +## Option 3: AI-Generated Icon + +Use AI tools to generate: +- **ChatGPT/DALL-E**: "Create a minimalistic app icon for a book library app, cyan background (#0891B2), white book symbol, 1024x1024" +- **Midjourney**: "minimalist book library app icon, cyan gradient, white geometric book, flat design, 1024x1024" + +--- + +## What Each File Does + +### app_icon.png +- Used for iOS (all sizes) +- Used as fallback for older Android devices +- Should be a **complete icon** (background + foreground) + +### app_icon_foreground.png +- Android's adaptive icon foreground layer +- Should be **transparent background** with just the icon symbol +- Android will apply the cyan background automatically +- Keep important elements in the "safe zone" (center ~66% of canvas) + +--- + +## Once You Have Your Icons + +1. Save both PNG files in this directory: + - `assets/icon/app_icon.png` + - `assets/icon/app_icon_foreground.png` + +2. Run these commands: +```bash +flutter pub get +dart run flutter_launcher_icons +``` + +3. Verify the icons were generated: +```bash +# Check Android icons +ls android/app/src/main/res/mipmap-*/ + +# Check iOS icons +ls ios/Runner/Assets.xcassets/AppIcon.appiconset/ +``` + +4. Build and test: +```bash +flutter run --profile +``` + +--- + +## Design Tips + +✅ **DO:** +- Use simple, recognizable shapes +- Ensure good contrast (white on cyan works great) +- Test at small sizes (looks good as 40x40?) +- Keep foreground centered for adaptive icons +- Use vector shapes when possible + +❌ **DON'T:** +- Use gradients that look muddy when small +- Add tiny text (won't be readable) +- Use too many colors (stick to 2-3) +- Put important details near edges (Android will crop) + +--- + +## Quick Start Suggestion + +**Easiest path:** +1. Open Canva → Custom 1024x1024 +2. Add cyan (#0891B2) background +3. Add white book icon from their library +4. Export as PNG → save as `app_icon.png` +5. Remove background → export → save as `app_icon_foreground.png` +6. Run `dart run flutter_launcher_icons` + +**Total time: ~5 minutes** diff --git a/books_flutter/lib/bloc/add_book/add_book_bloc.dart b/books_flutter/lib/bloc/add_book/add_book_bloc.dart new file mode 100644 index 0000000..51f8c86 --- /dev/null +++ b/books_flutter/lib/bloc/add_book/add_book_bloc.dart @@ -0,0 +1,97 @@ +import 'dart:math'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../models/models.dart'; +import 'add_book_event.dart'; +import 'add_book_state.dart'; + +class AddBookBloc extends Bloc { + final void Function(Book book) onAddBook; + final void Function(Book book) onUpdateBook; + + AddBookBloc({required this.onAddBook, required this.onUpdateBook}) + : super(const AddBookState()) { + on(_onInitializeForm); + on(_onUpdateTitle); + on(_onUpdateAuthor); + on(_onUpdateAnnotation); + on(_onUpdateGenre); + on(_onApplyScannedBook); + on(_onSaveBook); + } + + void _onInitializeForm(InitializeForm event, Emitter emit) { + final source = event.editBook ?? event.prefilledData; + if (source != null) { + emit( + AddBookState( + title: source.title, + author: source.author, + annotation: source.annotation, + genre: source.genre.isNotEmpty ? source.genre : 'fiction', + editBook: event.editBook, + ), + ); + } else if (event.editBook != null) { + emit(state.copyWith(editBook: event.editBook)); + } + } + + void _onUpdateTitle(UpdateTitle event, Emitter emit) { + emit(state.copyWith(title: event.title)); + } + + void _onUpdateAuthor(UpdateAuthor event, Emitter emit) { + emit(state.copyWith(author: event.author)); + } + + void _onUpdateAnnotation(UpdateAnnotation event, Emitter emit) { + emit(state.copyWith(annotation: event.annotation)); + } + + void _onUpdateGenre(UpdateGenre event, Emitter emit) { + emit(state.copyWith(genre: event.genre)); + } + + void _onApplyScannedBook(ApplyScannedBook event, Emitter emit) { + final scanned = event.scannedBook; + emit( + state.copyWith( + title: scanned.title, + author: scanned.author, + annotation: scanned.annotation, + genre: scanned.genre.isNotEmpty ? scanned.genre : 'fiction', + ), + ); + } + + void _onSaveBook(SaveBook event, Emitter emit) { + final existing = state.editBook; + final isEditing = existing != null; + + final Book book = ( + id: isEditing ? existing.id : '${Random().nextInt(100000)}', + title: state.title, + author: state.author, + genre: state.genre, + annotation: state.annotation, + coverUrl: isEditing + ? existing.coverUrl + : 'https://picsum.photos/seed/newbook/400/600', + pages: isEditing ? existing.pages : 0, + language: isEditing ? existing.language : 'Russian', + publishedYear: isEditing ? existing.publishedYear : DateTime.now().year, + rating: isEditing ? existing.rating : 5.0, + status: isEditing ? existing.status : 'want_to_read', + progress: isEditing ? existing.progress : null, + isFavorite: isEditing ? existing.isFavorite : false, + ); + + if (isEditing) { + onUpdateBook(book); + } else { + onAddBook(book); + } + + emit(state.copyWith(isSaved: true)); + } +} diff --git a/books_flutter/lib/bloc/add_book/add_book_event.dart b/books_flutter/lib/bloc/add_book/add_book_event.dart new file mode 100644 index 0000000..e731c64 --- /dev/null +++ b/books_flutter/lib/bloc/add_book/add_book_event.dart @@ -0,0 +1,37 @@ +import '../../models/models.dart'; + +sealed class AddBookEvent {} + +class InitializeForm extends AddBookEvent { + final Book? editBook; + final Book? prefilledData; + + InitializeForm({this.editBook, this.prefilledData}); +} + +class UpdateTitle extends AddBookEvent { + final String title; + UpdateTitle(this.title); +} + +class UpdateAuthor extends AddBookEvent { + final String author; + UpdateAuthor(this.author); +} + +class UpdateAnnotation extends AddBookEvent { + final String annotation; + UpdateAnnotation(this.annotation); +} + +class UpdateGenre extends AddBookEvent { + final String genre; + UpdateGenre(this.genre); +} + +class ApplyScannedBook extends AddBookEvent { + final Book scannedBook; + ApplyScannedBook(this.scannedBook); +} + +class SaveBook extends AddBookEvent {} diff --git a/books_flutter/lib/bloc/add_book/add_book_state.dart b/books_flutter/lib/bloc/add_book/add_book_state.dart new file mode 100644 index 0000000..969c480 --- /dev/null +++ b/books_flutter/lib/bloc/add_book/add_book_state.dart @@ -0,0 +1,39 @@ +import '../../models/models.dart'; + +class AddBookState { + final String title; + final String author; + final String annotation; + final String genre; + final Book? editBook; + final bool isSaved; + + const AddBookState({ + this.title = '', + this.author = '', + this.annotation = '', + this.genre = 'fiction', + this.editBook, + this.isSaved = false, + }); + + bool get isEditing => editBook != null; + + AddBookState copyWith({ + String? title, + String? author, + String? annotation, + String? genre, + Book? editBook, + bool? isSaved, + }) { + return AddBookState( + title: title ?? this.title, + author: author ?? this.author, + annotation: annotation ?? this.annotation, + genre: genre ?? this.genre, + editBook: editBook ?? this.editBook, + isSaved: isSaved ?? this.isSaved, + ); + } +} diff --git a/books_flutter/lib/bloc/book/book_bloc.dart b/books_flutter/lib/bloc/book/book_bloc.dart new file mode 100644 index 0000000..705a5cf --- /dev/null +++ b/books_flutter/lib/bloc/book/book_bloc.dart @@ -0,0 +1,47 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../constants/constants.dart'; +import 'book_event.dart'; +import 'book_state.dart'; + +class BookBloc extends Bloc { + BookBloc() : super(const BookState(books: initialBooks)) { + on((event, emit) { + emit(BookState(books: [...state.books, event.book])); + }); + + on((event, emit) { + final updated = state.books.map((b) { + return b.id == event.book.id ? event.book : b; + }).toList(); + emit(BookState(books: updated)); + }); + + on((event, emit) { + emit( + BookState(books: state.books.where((b) => b.id != event.id).toList()), + ); + }); + + on((event, emit) { + final updated = state.books.map((b) { + if (b.id != event.id) return b; + return ( + id: b.id, + title: b.title, + author: b.author, + genre: b.genre, + annotation: b.annotation, + coverUrl: b.coverUrl, + pages: b.pages, + language: b.language, + publishedYear: b.publishedYear, + rating: b.rating, + status: b.status, + progress: b.progress, + isFavorite: !b.isFavorite, + ); + }).toList(); + emit(BookState(books: updated)); + }); + } +} diff --git a/books_flutter/lib/bloc/book/book_event.dart b/books_flutter/lib/bloc/book/book_event.dart new file mode 100644 index 0000000..cabbad4 --- /dev/null +++ b/books_flutter/lib/bloc/book/book_event.dart @@ -0,0 +1,23 @@ +import '../../models/models.dart'; + +sealed class BookEvent {} + +class AddBook extends BookEvent { + final Book book; + AddBook(this.book); +} + +class UpdateBook extends BookEvent { + final Book book; + UpdateBook(this.book); +} + +class DeleteBook extends BookEvent { + final String id; + DeleteBook(this.id); +} + +class ToggleFavorite extends BookEvent { + final String id; + ToggleFavorite(this.id); +} diff --git a/books_flutter/lib/bloc/book/book_state.dart b/books_flutter/lib/bloc/book/book_state.dart new file mode 100644 index 0000000..06a9a60 --- /dev/null +++ b/books_flutter/lib/bloc/book/book_state.dart @@ -0,0 +1,6 @@ +import '../../models/models.dart'; + +class BookState { + final List books; + const BookState({required this.books}); +} diff --git a/books_flutter/lib/bloc/library/library_bloc.dart b/books_flutter/lib/bloc/library/library_bloc.dart new file mode 100644 index 0000000..75485b2 --- /dev/null +++ b/books_flutter/lib/bloc/library/library_bloc.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'library_event.dart'; +import 'library_state.dart'; + +class LibraryBloc extends Bloc { + LibraryBloc() : super(const LibraryState()) { + on(_onUpdateSearchQuery); + on(_onChangeTab); + } + + void _onUpdateSearchQuery( + UpdateSearchQuery event, + Emitter emit, + ) { + emit(state.copyWith(searchQuery: event.query)); + } + + void _onChangeTab(ChangeTab event, Emitter emit) { + emit(state.copyWith(tabIndex: event.tabIndex)); + } +} diff --git a/books_flutter/lib/bloc/library/library_event.dart b/books_flutter/lib/bloc/library/library_event.dart new file mode 100644 index 0000000..3744fe1 --- /dev/null +++ b/books_flutter/lib/bloc/library/library_event.dart @@ -0,0 +1,11 @@ +sealed class LibraryEvent {} + +class UpdateSearchQuery extends LibraryEvent { + final String query; + UpdateSearchQuery(this.query); +} + +class ChangeTab extends LibraryEvent { + final int tabIndex; + ChangeTab(this.tabIndex); +} diff --git a/books_flutter/lib/bloc/library/library_state.dart b/books_flutter/lib/bloc/library/library_state.dart new file mode 100644 index 0000000..448097d --- /dev/null +++ b/books_flutter/lib/bloc/library/library_state.dart @@ -0,0 +1,13 @@ +class LibraryState { + final String searchQuery; + final int tabIndex; + + const LibraryState({this.searchQuery = '', this.tabIndex = 0}); + + LibraryState copyWith({String? searchQuery, int? tabIndex}) { + return LibraryState( + searchQuery: searchQuery ?? this.searchQuery, + tabIndex: tabIndex ?? this.tabIndex, + ); + } +} diff --git a/books_flutter/lib/bloc/scanner/scanner_bloc.dart b/books_flutter/lib/bloc/scanner/scanner_bloc.dart new file mode 100644 index 0000000..ec27b22 --- /dev/null +++ b/books_flutter/lib/bloc/scanner/scanner_bloc.dart @@ -0,0 +1,126 @@ +import 'dart:io'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../models/models.dart'; +import '../../services/camera_service.dart'; +import '../../services/openai_service.dart'; +import 'scanner_event.dart'; +import 'scanner_state.dart'; + +class ScannerBloc extends Bloc { + final CameraService cameraService; + + ScannerBloc({required this.cameraService}) : super(const ScannerState()) { + on(_onInitializeCamera); + on(_onCaptureAndAnalyze); + on(_onSwitchCamera); + on(_onDismissError); + } + + Future _onInitializeCamera( + InitializeCamera event, + Emitter emit, + ) async { + try { + final initialized = await cameraService.initializeCamera(); + emit( + state.copyWith( + isInitialized: initialized, + hasPermissionError: !initialized, + errorMessage: initialized ? null : 'Нет доступа к камере', + ), + ); + } catch (e) { + emit( + state.copyWith( + hasPermissionError: true, + errorMessage: 'Ошибка инициализации камеры: $e', + ), + ); + } + } + + Future _onCaptureAndAnalyze( + CaptureAndAnalyze event, + Emitter emit, + ) async { + if (cameraService.controller == null) return; + + emit(state.copyWith(isCapturing: true)); + + try { + // Capture image + final imagePath = await cameraService.captureImage(); + if (imagePath == null) { + throw Exception('Не удалось сделать снимок'); + } + + emit(state.copyWith(isAnalyzing: true, isCapturing: false)); + + Book? book; + + // Try OpenAI first if available + if (event.openaiApiKey != null && event.openaiApiKey!.isNotEmpty) { + print('Using OpenAI service for analysis'); + final openaiService = OpenAIService( + apiKey: event.openaiApiKey!, + baseUrl: event.openaiBaseUrl, + ); + book = await openaiService.analyzeBookCover(imagePath); + } + + // Fall back to Gemini if OpenAI failed or is not configured + // if (book == null) { + // if (event.geminiApiKey == null || event.geminiApiKey!.isEmpty) { + // throw Exception('API ключ не настроен (ни OpenAI, ни Gemini)'); + // } + // print('Using Gemini service for analysis'); + // final geminiService = GeminiService(apiKey: event.geminiApiKey!); + // book = await geminiService.analyzeBookCover(imagePath); + // } + + if (book == null) { + throw Exception('Не удалось распознать книгу'); + } + + // Clean up temporary image + try { + await File(imagePath).delete(); + } catch (e) { + print('Error deleting temporary file: $e'); + } + + emit( + state.copyWith( + analyzedBook: book, + isAnalyzing: false, + isCapturing: false, + ), + ); + } catch (e) { + emit( + state.copyWith( + errorMessage: e.toString(), + isCapturing: false, + isAnalyzing: false, + ), + ); + } + } + + Future _onSwitchCamera( + SwitchCamera event, + Emitter emit, + ) async { + await cameraService.switchCamera(); + } + + void _onDismissError(DismissError event, Emitter emit) { + emit(state.copyWith(clearError: true)); + } + + @override + Future close() { + cameraService.dispose(); + return super.close(); + } +} diff --git a/books_flutter/lib/bloc/scanner/scanner_event.dart b/books_flutter/lib/bloc/scanner/scanner_event.dart new file mode 100644 index 0000000..b1f94e1 --- /dev/null +++ b/books_flutter/lib/bloc/scanner/scanner_event.dart @@ -0,0 +1,21 @@ +import 'package:books_flutter/config/api_config.dart'; + +sealed class ScannerEvent {} + +class InitializeCamera extends ScannerEvent {} + +class CaptureAndAnalyze extends ScannerEvent { + final String? openaiApiKey; + final String openaiBaseUrl; + final String? geminiApiKey; + + CaptureAndAnalyze({ + this.openaiApiKey, + this.openaiBaseUrl = ApiConfig.openaiBaseUrl, + this.geminiApiKey, + }); +} + +class SwitchCamera extends ScannerEvent {} + +class DismissError extends ScannerEvent {} diff --git a/books_flutter/lib/bloc/scanner/scanner_state.dart b/books_flutter/lib/bloc/scanner/scanner_state.dart new file mode 100644 index 0000000..1ec5ebb --- /dev/null +++ b/books_flutter/lib/bloc/scanner/scanner_state.dart @@ -0,0 +1,39 @@ +import '../../models/models.dart'; + +class ScannerState { + final bool isInitialized; + final bool isCapturing; + final bool isAnalyzing; + final bool hasPermissionError; + final String? errorMessage; + final Book? analyzedBook; + + const ScannerState({ + this.isInitialized = false, + this.isCapturing = false, + this.isAnalyzing = false, + this.hasPermissionError = false, + this.errorMessage, + this.analyzedBook, + }); + + ScannerState copyWith({ + bool? isInitialized, + bool? isCapturing, + bool? isAnalyzing, + bool? hasPermissionError, + String? errorMessage, + Book? analyzedBook, + bool clearError = false, + bool clearBook = false, + }) { + return ScannerState( + isInitialized: isInitialized ?? this.isInitialized, + isCapturing: isCapturing ?? this.isCapturing, + isAnalyzing: isAnalyzing ?? this.isAnalyzing, + hasPermissionError: hasPermissionError ?? this.hasPermissionError, + errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), + analyzedBook: clearBook ? null : (analyzedBook ?? this.analyzedBook), + ); + } +} diff --git a/books_flutter/lib/config/api_config.dart b/books_flutter/lib/config/api_config.dart new file mode 100644 index 0000000..9a785f5 --- /dev/null +++ b/books_flutter/lib/config/api_config.dart @@ -0,0 +1,19 @@ +/// API Configuration +/// +/// Replace YOUR_GEMINI_API_KEY_HERE with your actual Google Gemini API key +/// Get your API key from: https://makersuite.google.com/app/apikey +/// +/// Replace YOUR_OPENAI_API_KEY_HERE with your actual OpenAI API key +/// The default endpoint is set to http://localhost:8317/v1/chat/completions +/// You can configure your OpenAI endpoint below if needed +class ApiConfig { + // TODO: Replace with your actual Gemini API key + static const String geminiApiKey = 'YOUR_GEMINI_API_KEY_HERE'; + + static const String openaiApiKey = 'sk-openai-api-key'; + + // OpenAI API endpoint (default: http://localhost:8317/v1/chat/completions) + static const String openaiBaseUrl = 'http://192.168.102.158:8317'; + + static const String openaiModel = 'gemini-3-pro-image'; +} diff --git a/books_flutter/lib/models/book.dart b/books_flutter/lib/models/book.dart new file mode 100644 index 0000000..fd4b2c5 --- /dev/null +++ b/books_flutter/lib/models/book.dart @@ -0,0 +1,15 @@ +typedef Book = ({ + String id, + String title, + String author, + String genre, + String annotation, + String? coverUrl, + int? pages, + String? language, + int? publishedYear, + double? rating, + String status, + double? progress, + bool isFavorite, +}); diff --git a/books_flutter/lib/models/category.dart b/books_flutter/lib/models/category.dart new file mode 100644 index 0000000..68c97e9 --- /dev/null +++ b/books_flutter/lib/models/category.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +typedef Category = ({ + String id, + String name, + int count, + IconData icon, + Color iconColor, + Color backgroundColor, +}); diff --git a/books_flutter/lib/services/camera_service.dart b/books_flutter/lib/services/camera_service.dart new file mode 100644 index 0000000..cd9500a --- /dev/null +++ b/books_flutter/lib/services/camera_service.dart @@ -0,0 +1,90 @@ +import 'package:camera/camera.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class CameraService { + CameraController? _controller; + List? _cameras; + bool _isInitialized = false; + + bool get isInitialized => _isInitialized; + CameraController? get controller => _controller; + + Future requestPermissions() async { + final cameraStatus = await Permission.camera.request(); + return cameraStatus.isGranted; + } + + Future initializeCamera() async { + try { + // Request camera permissions + final hasPermission = await requestPermissions(); + if (!hasPermission) { + return false; + } + + // Get available cameras + _cameras = await availableCameras(); + if (_cameras == null || _cameras!.isEmpty) { + return false; + } + + // Initialize the back camera (first camera is usually the back one) + _controller = CameraController( + _cameras!.first, + ResolutionPreset.high, + enableAudio: false, + ); + + await _controller!.initialize(); + _isInitialized = true; + return true; + } catch (e) { + print('Error initializing camera: $e'); + return false; + } + } + + Future captureImage() async { + if (_controller == null || !_isInitialized) { + print('Camera not initialized'); + return null; + } + + try { + final image = await _controller!.takePicture(); + return image.path; + } catch (e) { + print('Error capturing image: $e'); + return null; + } + } + + Future dispose() async { + await _controller?.dispose(); + _controller = null; + _isInitialized = false; + } + + Future switchCamera() async { + if (_cameras == null || _cameras!.length < 2) { + return; + } + + try { + final currentCameraIndex = _cameras!.indexOf(_controller!.description); + final nextCameraIndex = (currentCameraIndex + 1) % _cameras!.length; + + await _controller?.dispose(); + + _controller = CameraController( + _cameras![nextCameraIndex], + ResolutionPreset.high, + enableAudio: false, + ); + + await _controller!.initialize(); + } catch (e) { + print('Error switching camera: $e'); + } + } +} diff --git a/books_flutter/lib/services/openai_service.dart b/books_flutter/lib/services/openai_service.dart new file mode 100644 index 0000000..4241e21 --- /dev/null +++ b/books_flutter/lib/services/openai_service.dart @@ -0,0 +1,152 @@ +import 'dart:io'; +import 'dart:convert'; +import 'package:books_flutter/config/api_config.dart'; +import 'package:http/http.dart' as http; +import '../models/models.dart'; + +class OpenAIService { + final String apiKey; + final String baseUrl; + final String model; + late final String _endpoint; + + OpenAIService({ + required this.apiKey, + this.baseUrl = ApiConfig.openaiApiKey, + this.model = ApiConfig.openaiModel, + }) { + _endpoint = '$baseUrl/v1/chat/completions'; + } + + Future analyzeBookCover(String imagePath) async { + try { + // Read the image file + final imageFile = File(imagePath); + final imageBytes = await imageFile.readAsBytes(); + final base64Image = base64Encode(imageBytes); + + // Create the prompt for book analysis + const prompt = ''' +Analyze this book cover image and extract the following information in JSON format: +{ + "title": "book title (required)", + "author": "author name (required)", + "genre": "fiction/fantasy/science/detective/biography/other", + "annotation": "brief description or summary if visible, otherwise generate a generic one" +} + +Rules: +- Extract exact text from the cover +- If genre is unclear, choose the most appropriate one +- If annotation is not visible, create a brief generic description +- Return ONLY valid JSON, no additional text +- Ensure all required fields are present +- Return result in russian language +'''; + + // Create the request body for OpenAI API + final requestBody = { + 'model': model, // Use the configured model + 'messages': [ + { + 'role': 'user', + 'content': [ + {'type': 'text', 'text': prompt}, + { + 'type': 'image_url', + 'image_url': {'url': 'data:image/jpeg;base64,$base64Image'}, + }, + ], + }, + ], + }; + + // Make the API request + final response = await http.post( + Uri.parse(_endpoint), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $apiKey', + }, + body: json.encode(requestBody), + ); + + if (response.statusCode != 200) { + print('OpenAI API error: ${response.statusCode}'); + print('Response body: ${response.body}'); + return null; + } + + final responseData = json.decode(response.body); + + // Extract the message content + final responseText = responseData['choices']?[0]?['message']?['content'] + ?.toString() + .trim(); + + if (responseText == null || responseText.isEmpty) { + print('Empty response from OpenAI'); + return null; + } + + // Extract JSON from response (handle potential markdown formatting) + String jsonString = responseText; + if (jsonString.contains('```json')) { + jsonString = jsonString.split('```json')[1].split('```')[0].trim(); + } else if (jsonString.contains('```')) { + jsonString = jsonString.split('```')[1].split('```')[0].trim(); + } + + // Parse JSON response + final Map jsonData = json.decode(jsonString); + + // Create Book object with extracted data + final Book book = ( + id: DateTime.now().millisecondsSinceEpoch.toString(), + title: jsonData['title']?.toString() ?? 'Неизвестная книга', + author: jsonData['author']?.toString() ?? 'Неизвестный автор', + genre: _normalizeGenre(jsonData['genre']?.toString()), + annotation: jsonData['annotation']?.toString() ?? 'Нет описания', + coverUrl: null, // Will be set by the caller + pages: null, + language: 'Russian', + publishedYear: DateTime.now().year, + rating: 5.0, + status: 'want_to_read', + progress: null, + isFavorite: false, + ); + + return book; + } catch (e) { + print('Error analyzing book cover with OpenAI: $e'); + return null; + } + } + + String _normalizeGenre(String? genre) { + if (genre == null || genre.isEmpty) return 'other'; + + final normalized = genre.toLowerCase().trim(); + + // Map various genre names to our standard genres + final genreMap = { + 'фантастика': 'fiction', + 'fantasy': 'fantasy', + 'фэнтези': 'fantasy', + 'science': 'science', + 'научпоп': 'science', + 'научная': 'science', + 'biography': 'biography', + 'биография': 'biography', + 'detective': 'detective', + 'детектив': 'detective', + 'роман': 'other', + 'novel': 'other', + 'poetry': 'other', + 'поэзия': 'other', + }; + + return genreMap[normalized] ?? normalized; + } +} diff --git a/books_flutter/lib/widgets/bottom_nav_shell.dart b/books_flutter/lib/widgets/bottom_nav_shell.dart new file mode 100644 index 0000000..95a19e1 --- /dev/null +++ b/books_flutter/lib/widgets/bottom_nav_shell.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'bottom_nav.dart'; +import '../screens/library_screen.dart'; +import '../screens/categories_screen.dart'; + +/// Shell widget with bottom navigation and nested navigators for each tab. +/// Uses IndexedStack to preserve navigation state when switching tabs. +class BottomNavShell extends StatefulWidget { + const BottomNavShell({super.key}); + + @override + State createState() => _BottomNavShellState(); +} + +class _BottomNavShellState extends State { + int _currentIndex = 0; + + // Each tab gets its own navigator key to maintain independent navigation stacks + final _navigatorKeys = List.generate(4, (_) => GlobalKey()); + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) async { + if (didPop) return; + final shouldPop = await _onWillPop(); + if (shouldPop && context.mounted) { + Navigator.of(context).pop(); + } + }, + child: Scaffold( + body: IndexedStack( + index: _currentIndex, + children: [ + _buildNavigator(0, (_) => const LibraryScreen()), + _buildNavigator(1, (_) => const CategoriesScreen()), + _buildNavigator(2, (_) => _buildPlaceholder('Избранное')), + _buildNavigator(3, (_) => _buildPlaceholder('Настройки')), + ], + ), + bottomNavigationBar: BottomNav( + currentIndex: _currentIndex, + onTap: _onTabTapped, + ), + ), + ); + } + + /// Builds a nested navigator for a tab + Widget _buildNavigator(int index, WidgetBuilder builder) { + return Navigator( + key: _navigatorKeys[index], + onGenerateRoute: (settings) { + return MaterialPageRoute(builder: builder, settings: settings); + }, + ); + } + + /// Placeholder screen for tabs not yet implemented + Widget _buildPlaceholder(String title) { + return Scaffold( + appBar: AppBar(title: Text(title), automaticallyImplyLeading: false), + body: Center( + child: Text(title, style: Theme.of(context).textTheme.headlineMedium), + ), + ); + } + + /// Handle tab selection + void _onTabTapped(int index) { + if (_currentIndex == index) { + // If tapping the current tab, pop to root of that tab's navigator + final navigator = _navigatorKeys[index].currentState; + if (navigator != null && navigator.canPop()) { + navigator.popUntil((route) => route.isFirst); + } + } else { + // Switch to the selected tab + setState(() { + _currentIndex = index; + }); + } + } + + /// Handle system back button + Future _onWillPop() async { + final navigator = _navigatorKeys[_currentIndex].currentState; + + // If the current tab's navigator can pop, pop it + if (navigator != null && navigator.canPop()) { + navigator.pop(); + return false; // Don't exit app + } + + // If on root of current tab, allow app to exit + return true; + } +} diff --git a/books_flutter/samples/photo_2026-02-07_15-05-17.jpg b/books_flutter/samples/photo_2026-02-07_15-05-17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae7fddf262c2ffeced73d3521ee118448d3e6141 GIT binary patch literal 111499 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 literal 0 HcmV?d00001 diff --git a/books_flutter/test/openai_service_test.dart b/books_flutter/test/openai_service_test.dart new file mode 100644 index 0000000..0177f9a --- /dev/null +++ b/books_flutter/test/openai_service_test.dart @@ -0,0 +1,58 @@ +import 'dart:io'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:books_flutter/services/openai_service.dart'; + +void main() { + test('OpenAI Service - Analyze book cover', () async { + // Configure the OpenAI service + // Note: Make sure to replace with your actual API key + const apiKey = 'sk-proj-1234567890'; + const baseUrl = 'http://localhost:8317'; + const model = + 'gemini-3-pro-image'; //'claude-sonnet-4-5-thinking'; // Model must be glm-4v for vision support + + if (apiKey == 'YOUR_OPENAI_API_KEY_HERE') { + print('Please set your OpenAI API key in the test file'); + return; + } + + final service = OpenAIService( + apiKey: apiKey, + baseUrl: baseUrl, + model: model, + ); + + // Path to the sample image + const imagePath = 'samples/photo_2026-02-07_15-05-17.jpg'; + + // Check if the image file exists + final imageFile = File(imagePath); + if (!imageFile.existsSync()) { + print('Image file not found at: $imagePath'); + return; + } + + print('Analyzing book cover...'); + print('Image path: $imagePath'); + print('Image size: ${imageFile.lengthSync()} bytes'); + + // Analyze the book cover + final book = await service.analyzeBookCover(imagePath); + + if (book != null) { + print('\n✅ Successfully analyzed book cover!\n'); + print('Title: ${book.title}'); + print('Author: ${book.author}'); + print('Genre: ${book.genre}'); + print('Annotation: ${book.annotation}'); + print('\n'); + expect(book.title, isNotEmpty); + expect(book.author, isNotEmpty); + } else { + print('\n❌ Failed to analyze book cover'); + print( + 'Check your API key and ensure the OpenAI server is running at $baseUrl', + ); + } + }); +} diff --git a/books_flutter/test_openai_service.dart b/books_flutter/test_openai_service.dart new file mode 100644 index 0000000..1c2dfd3 --- /dev/null +++ b/books_flutter/test_openai_service.dart @@ -0,0 +1,64 @@ +import 'dart:io'; +import 'lib/services/openai_service.dart'; + +void main() async { + // Configure the OpenAI service + // Note: Replace with your actual API key + const apiKey = 'YOUR_OPENAI_API_KEY_HERE'; + const baseUrl = 'http://localhost:8317'; + + if (apiKey == 'YOUR_OPENAI_API_KEY_HERE') { + print('❌ Please set your OpenAI API key in this file'); + return; + } + + final service = OpenAIService(apiKey: apiKey, baseUrl: baseUrl); + + // Path to the sample image + const imagePath = 'samples/photo_2026-02-07_15-05-17.jpg'; + + // Check if the image file exists + final imageFile = File(imagePath); + if (!imageFile.existsSync()) { + print('❌ Image file not found at: $imagePath'); + print('Current working directory: ${Directory.current.path}'); + return; + } + + print('========================================'); + print('📖 Testing OpenAI Book Cover Analysis'); + print('========================================\n'); + print('Image path: $imagePath'); + print('Image size: ${imageFile.lengthSync()} bytes'); + print('API endpoint: $baseUrl/v1/chat/completions\n'); + print('Analyzing book cover... (this may take a few seconds)\n'); + + // Analyze the book cover + final book = await service.analyzeBookCover(imagePath); + + if (book != null) { + print('========================================'); + print('✅ Successfully analyzed book cover!'); + print('========================================\n'); + print('📚 Book Details:'); + print(' Title: ${book.title}'); + print(' Author: ${book.author}'); + print(' Genre: ${book.genre}'); + print(' Annotation: ${book.annotation}'); + print(' Language: ${book.language}'); + print(' Published Year: ${book.publishedYear}'); + print(' Rating: ${book.rating}'); + print('\n'); + print('========================================'); + } else { + print('========================================'); + print('❌ Failed to analyze book cover'); + print('========================================\n'); + print('Troubleshooting tips:'); + print('1. Check your API key is correct'); + print('2. Ensure the OpenAI server is running at $baseUrl'); + print('3. Check the server logs for errors'); + print('4. Verify the server supports vision models (gpt-4o)'); + print('5. Check network connectivity\n'); + } +}