Skip to content

feat: added ImageLibrary Section. #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

Dhruv1797
Copy link
Contributor

@Dhruv1797 Dhruv1797 commented Jun 18, 2025

Fixes: #54
Introduce a library feature that allows users to store and manage all finalized images in one place. Add a dedicated transfer section to easily select and send any saved image directly to the ePaper device.

Summary by Sourcery

Introduce a dedicated Image Library feature to store, manage, preview, and transfer finalized images, and integrate it into the image editor for saving and selecting library items.

New Features:

  • Add ImageLibraryScreen with grid view, search, and source-based filtering for saved images
  • Implement ImageLibraryProvider to persist and manage image files and metadata using local storage and SharedPreferences
  • Provide dialogs and widgets for saving, previewing, renaming, single deletion, and batch deletion of library images
  • Enable direct transfer of saved images to the ePaper device

Enhancements:

  • Integrate 'Save to Library' and 'Image Library' buttons into the image editor toolbar and bottom action menu
  • Introduce ImageSaveHandler and ImageOperationsService for unified save, rename, delete, and transfer workflows
  • Display contextual snackbars to inform users of save, delete, rename, and transfer operations

Build:

  • Add shared_preferences dependency

Copy link
Contributor

sourcery-ai bot commented Jun 18, 2025

Reviewer's Guide

This PR introduces a full-featured Image Library section backed by persistent storage and a provider, with end-to-end workflows for saving, renaming, deleting (single and batch), filtering/searching, previewing, and direct transfer of images to the ePaper device, and integrates it into the existing editor and app state.

Sequence diagram for saving an image from the editor to the library

sequenceDiagram
    actor User
    participant Editor as ImageEditor
    participant SaveHandler as ImageSaveHandler
    participant SaveDialog as ImageSaveDialog
    participant Provider as ImageLibraryProvider
    participant OpsService as ImageOperationsService

    User->>Editor: Clicks 'Save to Library'
    Editor->>SaveHandler: _saveCurrentImage()
    SaveHandler->>SaveDialog: Show save dialog
    User->>SaveDialog: Enters image name, confirms save
    SaveDialog->>SaveHandler: onSave(imageName)
    SaveHandler->>OpsService: saveImageWithFeedback(...)
    OpsService->>Provider: saveImage(...)
    Provider-->>OpsService: Image saved
    OpsService-->>SaveHandler: Show success feedback
Loading

Sequence diagram for deleting images (single and batch) from the library

sequenceDiagram
    actor User
    participant Library as ImageLibraryScreen
    participant Provider as ImageLibraryProvider
    participant OpsService as ImageOperationsService
    participant DeleteDialog as DeleteConfirmationDialog
    participant BatchDialog as BatchDeleteConfirmationDialog

    User->>Library: Selects image(s) to delete
    alt Single delete
        Library->>DeleteDialog: Show delete confirmation
        User->>DeleteDialog: Confirms delete
        DeleteDialog->>OpsService: deleteImage(image, provider)
        OpsService->>Provider: deleteImage(id)
        Provider-->>OpsService: Image deleted
        OpsService-->>Library: Show success feedback
    else Batch delete
        Library->>BatchDialog: Show batch delete confirmation
        User->>BatchDialog: Confirms delete
        BatchDialog->>OpsService: batchDeleteImages(selected, provider)
        OpsService->>Provider: deleteImage(id) (for each)
        Provider-->>OpsService: Images deleted
        OpsService-->>Library: Show batch success feedback
    end
Loading

Entity relationship diagram for SavedImage and persistent storage

erDiagram
    SAVED_IMAGE {
      string id
      string name
      string filePath
      datetime createdAt
      string source
      json metadata
    }
    SAVED_IMAGE ||..|| IMAGE_LIBRARY_PROVIDER : manages
    IMAGE_LIBRARY_PROVIDER ||..|| SHARED_PREFERENCES : persists_metadata
    IMAGE_LIBRARY_PROVIDER ||..o| FILE : stores_image_file
    FILE {
      string path
      bytes data
    }
    SHARED_PREFERENCES {
      string key
      string value
    }
Loading

Class diagram for new and updated Image Library types

classDiagram
    class ImageLibraryProvider {
      - List~SavedImage~ _savedImages
      - bool _isLoading
      - String _searchQuery
      - String _selectedSource
      + List~SavedImage~ get savedImages
      + bool get isLoading
      + String get searchQuery
      + String get selectedSource
      + List~SavedImage~ get filteredImages
      + Future~void~ loadSavedImages()
      + Future~void~ saveImage(...)
      + Future~void~ deleteImage(String id)
      + Future~void~ renameImage(String id, String newName)
      + void updateSearchQuery(String query)
      + void updateSourceFilter(String source)
    }
    class SavedImage {
      + String id
      + String name
      + String filePath
      + DateTime createdAt
      + String source
      + Map~String, dynamic~? metadata
      + Map~String, dynamic~ toJson()
      + Future~Uint8List?~ getImageData()
      + Future~bool~ fileExists()
    }
    class ImageOperationsService {
      + Future~void~ renameImage(...)
      + Future~void~ deleteImage(...)
      + Future~void~ batchDeleteImages(...)
      + Future~void~ saveImageWithFeedback(...)
      + Future~void~ transferSingleImage(...)
      + String getFilterNameByIndex(...)
    }
    class ImageSaveHandler {
      + Future~void~ saveCurrentImage(...)
    }
    ImageLibraryProvider "1" o-- "*" SavedImage
    ImageSaveHandler --> ImageOperationsService
    ImageOperationsService --> ImageLibraryProvider
Loading

File-Level Changes

Change Details Files
Integrate Image Library into app state and editor
  • Register ImageLibraryProvider in MultiProvider and add shared_preferences dependency
  • Import provider and ImageSaveHandler in ImageEditor and initialize in didChangeDependencies
  • Add _saveCurrentImage method and wire save button in app bar
  • Add navigation to ImageLibraryScreen from editor app bar and bottom menu
  • Expose onSourceChanged callback in BottomActionMenu to tag saved images' source
lib/main.dart
pubspec.yaml
lib/view/image_editor.dart
Implement persistent Image Library provider
  • Create ImageLibraryProvider with load, save, delete, rename, search/filter, and metadata persistence
  • Use SharedPreferences for metadata and file system for image storage
  • Add directory initialization and orphaned file cleanup
lib/image_library/provider/image_library_provider.dart
Build Image Library UI and navigation
  • Create ImageLibraryScreen with grid view, search/filter widget, empty state, and delete mode
  • Implement LibraryAppBar, SearchAndFilterWidget, ImageGridWidget, ImageCardWidget, and EmptyStateWidget
  • Add ImagePreviewDialog and dialogs for save, rename, single delete, and batch delete
lib/image_library/image_library.dart
lib/image_library/widgets/app_bar_widget.dart
lib/image_library/widgets/search_and_filter_widget.dart
lib/image_library/widgets/image_grid_widget.dart
lib/image_library/widgets/image_card_widget.dart
lib/image_library/widgets/empty_state_widget.dart
lib/image_library/widgets/dialogs/image_save_dialog.dart
lib/image_library/widgets/dialogs/image_preview_dialog.dart
lib/image_library/widgets/dialogs/image_rename_dialog.dart
lib/image_library/widgets/dialogs/delete_confirmation_dialog.dart
lib/image_library/widgets/dialogs/batch_delete_confirmation_dialog.dart
Add services to handle image operations and saving
  • Implement ImageOperationsService for rename, delete, batch delete, save-feedback, and transfer with snackbars
  • Implement ImageSaveHandler to generate final PNG, show save dialog, and delegate to service
  • Add utility classes for EPD mapping, date formatting, filter/source labels
lib/image_library/services/image_operations_service.dart
lib/image_library/services/image_save_handler.dart
lib/image_library/utils/epd_utils.dart
lib/image_library/utils/date_utils.dart
lib/image_library/utils/filter_utils.dart
lib/image_library/utils/source_utils.dart

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@AsCress AsCress self-requested a review June 19, 2025 10:14
@AsCress
Copy link

AsCress commented Jun 19, 2025

@Dhruv1797 Does this have some immediate changes in the UI which you could demo ?

@Dhruv1797
Copy link
Contributor Author

@Dhruv1797 Dhruv1797 marked this pull request as ready for review June 22, 2025 04:12
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Dhruv1797 - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `lib/image_library/provider/image_library_provider.dart:56` </location>
<code_context>
+      final prefs = await SharedPreferences.getInstance();
+      final savedImagesJson =
+          prefs.getStringList('saved_images_metadata') ?? [];
+      _savedImages = [];
+      for (String json in savedImagesJson) {
+        try {
+          final image = SavedImage.fromJson(jsonDecode(json));
</code_context>

<issue_to_address>
Clearing _savedImages before loading may cause issues if loadSavedImages is called concurrently.

Concurrent calls to this method could lead to race conditions. Use synchronization (e.g., a lock) or prevent concurrent execution to avoid data inconsistency.
</issue_to_address>

### Comment 2
<location> `lib/image_library/services/image_operations_service.dart:117` </location>
<code_context>
+      ImageProcessing.bwrTriColorAtkinsonDither: 'BWR Atkinson',
+      ImageProcessing.bwrThreshold: 'BWR Threshold',
+    };
+    if (index < 0 || index >= processingMethods.length) return "Unknown";
+    return filterMap[processingMethods[index]] ?? "Unknown";
+  }
</code_context>

<issue_to_address>
Returning 'Unknown' for invalid filter index may hide bugs.

Consider logging a warning or raising an error in debug mode when an invalid index is encountered to help identify logic errors early.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    if (index < 0 || index >= processingMethods.length) return "Unknown";
    return filterMap[processingMethods[index]] ?? "Unknown";
=======
    if (index < 0 || index >= processingMethods.length) {
      assert(() {
        // This will only run in debug mode.
        print('Warning: Invalid filter index $index encountered in getFilterName.');
        return true;
      }());
      return "Unknown";
    }
    return filterMap[processingMethods[index]] ?? "Unknown";
>>>>>>> REPLACE

</suggested_fix>

### Comment 3
<location> `lib/image_library/services/image_save_handler.dart:29` </location>
<code_context>
+  }) async {
+    if (rawImages.isEmpty) return;
+
+    img.Image finalImg = rawImages[selectedFilterIndex];
+
+    if (flipHorizontal) {
</code_context>

<issue_to_address>
No bounds check for selectedFilterIndex in rawImages.

Accessing rawImages with an invalid selectedFilterIndex will cause an exception. Please validate the index before using it.
</issue_to_address>

### Comment 4
<location> `lib/image_library/services/image_save_handler.dart:31` </location>
<code_context>
+
+    img.Image finalImg = rawImages[selectedFilterIndex];
+
+    if (flipHorizontal) {
+      finalImg = img.flipHorizontal(finalImg);
+    }
+    if (flipVertical) {
+      finalImg = img.flipVertical(finalImg);
+    }
</code_context>

<issue_to_address>
Image flipping is applied in-place, which may affect other references.

Since flipHorizontal and flipVertical may mutate the original image, clone rawImages[selectedFilterIndex] before applying these transformations to avoid unintended side effects.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 56 to 57
_savedImages = [];
for (String json in savedImagesJson) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Clearing _savedImages before loading may cause issues if loadSavedImages is called concurrently.

Concurrent calls to this method could lead to race conditions. Use synchronization (e.g., a lock) or prevent concurrent execution to avoid data inconsistency.

Comment on lines 117 to 118
if (index < 0 || index >= processingMethods.length) return "Unknown";
return filterMap[processingMethods[index]] ?? "Unknown";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Returning 'Unknown' for invalid filter index may hide bugs.

Consider logging a warning or raising an error in debug mode when an invalid index is encountered to help identify logic errors early.

Suggested change
if (index < 0 || index >= processingMethods.length) return "Unknown";
return filterMap[processingMethods[index]] ?? "Unknown";
if (index < 0 || index >= processingMethods.length) {
assert(() {
// This will only run in debug mode.
print('Warning: Invalid filter index $index encountered in getFilterName.');
return true;
}());
return "Unknown";
}
return filterMap[processingMethods[index]] ?? "Unknown";

}) async {
if (rawImages.isEmpty) return;

img.Image finalImg = rawImages[selectedFilterIndex];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): No bounds check for selectedFilterIndex in rawImages.

Accessing rawImages with an invalid selectedFilterIndex will cause an exception. Please validate the index before using it.

Comment on lines +31 to +34
if (flipHorizontal) {
finalImg = img.flipHorizontal(finalImg);
}
if (flipVertical) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Image flipping is applied in-place, which may affect other references.

Since flipHorizontal and flipVertical may mutate the original image, clone rawImages[selectedFilterIndex] before applying these transformations to avoid unintended side effects.

@Dhruv1797 Dhruv1797 requested review from kienvo and Vishveshwara June 22, 2025 04:15
@Dhruv1797
Copy link
Contributor Author

i marked this draft pr as as ready to review, @AsCress @kienvo @Vishveshwara please review it once.

Here is the demo video:
https://drive.google.com/file/d/1SYdFmFOwunXSvkStybtoGMHFvlcQorLp/view?usp=sharing

@Vishveshwara
Copy link
Contributor

@AsCress @Dhruv1797 @kienvo , is it better saving the image with the filter like Floyd steinberg (which is what dhruv has done in this PR) or should it take the saved image to the filter screen to select a filter again? What are your thoughts guys ? 2nd option might be better for last minute changes or , if we are using it on a different display(different colors supported) where another filter might look good.

@Dhruv1797
Copy link
Contributor Author

@AsCress @Dhruv1797 @kienvo , is it better saving the image with the filter like Floyd steinberg (which is what dhruv has done in this PR) or should it take the saved image to the filter screen to select a filter again? What are your thoughts guys ? 2nd option might be better for last minute changes or , if we are using it on a different display(different colors supported) where another filter might look good.

@Vishveshwara In my opinion, it would be better to save the image with the applied filter. From my perspective, the purpose of the Image Library is to ensure that the image is ready for immediate transfer, ideally just one click away. If we only save the original image and require the user to go through the Filter screen again, it feels quite similar to simply importing an image from the device gallery

@Dhruv1797
Copy link
Contributor Author

if we are using it on a different display(different colors supported) where another filter might look good.

For this i think If a different display requires a different filter, users can save separate images with filters tailored to each display. Since the display name is already shown in the bottom-right corner in ImageLibrary card, we could further enhance usability by adding filter options based on display type in the Image Library.

Here like for different Displays GDEY037T03 and GDEY037Z03 :

WhatsApp Image 2025-06-23 at 1 43 47 AM

@Vishveshwara
Copy link
Contributor

@Dhruv1797
I get your point , that this Image library is for immediate transfers.
I will review this PR today . other than this case I pointed out , rest of the UI and UX looks very good , Well done!

@mariobehling
Copy link
Member

Please resolve conflicts.

Copy link
Contributor

@Vishveshwara Vishveshwara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just add the strings in string_constants.dart, rest are fine.

@Dhruv1797
Copy link
Contributor Author

Please resolve conflicts.

@mariobehling I’ve resolved the conflicts. Please review it.

@Dhruv1797 Dhruv1797 requested a review from Vishveshwara June 24, 2025 15:35
Copy link
Contributor

@Vishveshwara Vishveshwara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks Good To Me.

@Dhruv1797
Copy link
Contributor Author

@AsCress @kienvo please review it once.

Copy link
Member

@kienvo kienvo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3000+ changes, is this worth it? I'm more on the side of saving the original image. That would be much simpler.

@AsCress
Copy link

AsCress commented Jun 27, 2025

@Dhruv1797 @Vishveshwara General suggestion: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/getting-started/helping-others-review-your-changes.
Keep your PRs around 250 effective LOC.

@Dhruv1797
Copy link
Contributor Author

3000+ changes, is this worth it? I'm more on the side of saving the original image. That would be much simpler.

@kienvo @AsCress actually, most of the 3000+ changes are due to UI/UX improvements like batch delete, single delete, rename, search & filter management, showing metadata (editor, imported image, display compatibility, filter applied, size, resolution, format, dimensions, etc.), saved date.

and various dialogs (delete, save, rename, delete-batch, single delete, image properties, etc.). The code is also modularized for better maintainability, which naturally adds more lines.

If you prefer, we can simplify it by just saving the original image and redirecting to the filter screen, as you said. But for a better UI/UX experience, this increase in code is kind of unavoidable.

Here is the demo video:

screen-20250627-100629.1.mp4

@Dhruv1797
Copy link
Contributor Author

@kienvo @AsCress do you want me to break each features in sperate PR's for easy review ?

@Jhalakupadhyay
Copy link
Contributor

@Dhruv1797

  1. Make the PR in smaller logical sections, rather than having 29 file changes at one go, it is impossible to review.
  2. We should not use shared preferences because if the user uninstalls the app and installs it again, the data will be gone.
  3. Save the processed image in the storage after compressing it.
    benefits:- 1. less storage taken on the user device.
    2. Don't need to apply all the filters again.
  4. Use the local storage for storing the metadata, get one file where the metadata with image location in the device is stored, and you just need to parse that JSON and can get all the data, and the data won't be lost even if the app is uninstalled and installed again.

@Dhruv1797
Copy link
Contributor Author

Nice catch! @Jhalakupadhyay , i have update the saving logic and removed the dependencies on shared_preferences package and now we are storing image in Internal storage with Folder MagicEpaper which having Images and json data on images_metadata.json

Now the images and metadata is persisting even if we uninstall and reinstall the app !

Here is the demo video For storing the images and metadata on internal storage and storage permission handling:

WhatsApp.Video.2025-07-02.at.6.41.48.PM.mp4

image
image

@Jhalakupadhyay @kienvo @AsCress @Vishveshwara please check it once.

@hpdang hpdang requested a review from marcnause July 3, 2025 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Finalized Image Library with Direct ePaper Transfer Support.
6 participants