From e9b24188b2c35010983c581f842adda3b4af17a8 Mon Sep 17 00:00:00 2001 From: Jhalak Upadhyay <89909502+Jhalakupadhyay@users.noreply.github.com> Date: Sat, 3 Aug 2024 16:16:26 +0530 Subject: [PATCH] feat: Added the logic to save the user drawn badge to local storage. (#980) --- .../utils/data_to_bytearray_converter.dart | 1 - lib/bademagic_module/utils/file_helper.dart | 131 ++++++++++++++++++ lib/bademagic_module/utils/image_utils.dart | 48 +++++++ lib/providers/imageprovider.dart | 6 + lib/view/draw_badge_screen.dart | 13 +- lib/view/homescreen.dart | 3 + lib/view/widgets/vectorview.dart | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 112 +++++++++++++-- pubspec.yaml | 2 + 10 files changed, 304 insertions(+), 15 deletions(-) create mode 100644 lib/bademagic_module/utils/file_helper.dart diff --git a/lib/bademagic_module/utils/data_to_bytearray_converter.dart b/lib/bademagic_module/utils/data_to_bytearray_converter.dart index 7463f859c..bd53dbcdc 100644 --- a/lib/bademagic_module/utils/data_to_bytearray_converter.dart +++ b/lib/bademagic_module/utils/data_to_bytearray_converter.dart @@ -132,7 +132,6 @@ class DataToByteArrayConverter { for (int x = 0; x < chunks.length; x++) { ans.add(hexStringToByteArray(chunks[x])); } - logger.d("ans ka length ${ans[0].length}"); return ans; } diff --git a/lib/bademagic_module/utils/file_helper.dart b/lib/bademagic_module/utils/file_helper.dart new file mode 100644 index 000000000..b56a7c590 --- /dev/null +++ b/lib/bademagic_module/utils/file_helper.dart @@ -0,0 +1,131 @@ +import 'dart:io'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; +import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; +import 'package:badgemagic/providers/imageprovider.dart'; +import 'package:flutter/services.dart'; +import 'package:get_it/get_it.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; + +class FileHelper { + final InlineImageProvider imageCacheProvider = + GetIt.instance(); + ImageUtils imageUtils = ImageUtils(); + static const Uuid uuid = Uuid(); + + static Future _getFilePath(String filename) async { + final directory = await getApplicationDocumentsDirectory(); + return '${directory.path}/$filename'; + } + + static Future _writeToFile(String filename, String data) async { + final path = await _getFilePath(filename); + + logger.d('Writing to file: $path'); + + return File(path).writeAsString(data); + } + + // static Future _readFromFile(String filename) async { + // try { + // final path = await _getFilePath(filename); + // return await File(path).readAsString(); + // } catch (e) { + // return ''; // Return an empty string if there's an error + // } + // } + + static String _generateUniqueFilename() { + final String uniqueId = uuid.v4(); + final String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); + return 'data_${timestamp}_$uniqueId.json'; + } + + // Add a new image to the cache + void addToCache(Uint8List imageData) { + int key; + if (imageCacheProvider.availableKeys.isNotEmpty) { + // Reuse the lowest available key + key = imageCacheProvider.availableKeys.first; + imageCacheProvider.availableKeys.remove(key); + } else { + // Assign a new key + key = imageCacheProvider.imageCache.length; + while (imageCacheProvider.imageCache.containsKey(key)) { + key++; + } + } + imageCacheProvider.imageCache[key] = imageData; + } + + // Remove an image from the cache + void removeFromCache(int key) { + if (imageCacheProvider.imageCache.containsKey(key)) { + imageCacheProvider.imageCache.remove(key); + imageCacheProvider.availableKeys + .add(key); // Add key to the pool of available keys + } + } + + // Generate a Uint8List from a 2D list (image data) and add it to the cache + Future _addImageDataToCache(List> imageData) async { + // Convert List> to List> + List> intImageData = + imageData.map((list) => list.cast()).toList(); + Uint8List imageBytes = + await imageUtils.convert2DListToUint8List(intImageData); + addToCache(imageBytes); + } + + // Read all files, parse the 2D lists, and add to cache + Future loadImageCacheFromFiles() async { + final directory = await getApplicationDocumentsDirectory(); + final List files = directory.listSync(); + + for (var file in files) { + if (file is File && file.path.endsWith('.json')) { + final String content = await file.readAsString(); + if (content.isNotEmpty) { + // Ensure correct type casting + final List decodedData = jsonDecode(content); + final List> imageData = + decodedData.cast>(); + await _addImageDataToCache(imageData); + } + } + } + } + + // Save a 2D list to a file with a unique name + Future saveImage(List> imageData) async { + List> image = List.generate( + imageData.length, (i) => List.filled(imageData[i].length, 0)); + + //convert the 2D list of bool into 2D list of int + for (int i = 0; i < imageData.length; i++) { + for (int j = 0; j < imageData[i].length; j++) { + image[i][j] = imageData[i][j] ? 1 : 0; + } + } + + // Generate a unique filename + String filename = _generateUniqueFilename(); + + logger.d('Saving image to file: $filename'); + + // Convert the 2D list to JSON string + String jsonData = jsonEncode(image); + + logger.d('JSON data: $jsonData'); + + // Write the JSON string to a file + await _writeToFile(filename, jsonData); + + logger.d('Image saved to file: $filename'); + + //Add the image to the image cache after saving it to a file + await _addImageDataToCache(image); + } +} diff --git a/lib/bademagic_module/utils/image_utils.dart b/lib/bademagic_module/utils/image_utils.dart index 3aecca80e..5deca0276 100644 --- a/lib/bademagic_module/utils/image_utils.dart +++ b/lib/bademagic_module/utils/image_utils.dart @@ -12,6 +12,54 @@ class ImageUtils { late ui.Picture picture; + //convert the 2D list to Uint8List + //this funcction will be ustilised to convert the user drawn badge to Uint8List + //and thus will be able to display with other vectors in the badge + Future convert2DListToUint8List(List> twoDList) async { + int height = twoDList.length; + int width = twoDList[0].length; + + // Create a buffer to hold the pixel data + Uint8List pixels = + Uint8List(width * height * 4); // 4 bytes per pixel (RGBA) + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int value = twoDList[y][x] == 1 ? 0 : 255; + int offset = (y * width + x) * 4; + pixels[offset] = value; // Red + pixels[offset + 1] = value; // Green + pixels[offset + 2] = value; // Blue + pixels[offset + 3] = 255; // Alpha + } + } + + // Create an ImmutableBuffer from the pixel data + ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(pixels); + + // Create an ImageDescriptor from the buffer + ui.ImageDescriptor descriptor = ui.ImageDescriptor.raw( + buffer, + width: width, + height: height, + pixelFormat: ui.PixelFormat.rgba8888, + ); + + // Instantiate a codec + ui.Codec codec = await descriptor.instantiateCodec(); + + // Get the first frame from the codec + ui.FrameInfo frameInfo = await codec.getNextFrame(); + + // Get the image from the frame + ui.Image image = frameInfo.image; + + // Convert the image to PNG format + ByteData? pngBytes = await image.toByteData(format: ui.ImageByteFormat.png); + + return pngBytes!.buffer.asUint8List(); + } + //function that generates the Picture from the given asset Future _loadSVG(String asset) async { //loading the Svg from the assets diff --git a/lib/providers/imageprovider.dart b/lib/providers/imageprovider.dart index a5d774ab3..ba85a7bfc 100644 --- a/lib/providers/imageprovider.dart +++ b/lib/providers/imageprovider.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:ui' as ui; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; +import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -9,6 +10,9 @@ class InlineImageProvider extends ChangeNotifier { //boolean variable to check for isCacheInitialized bool isCacheInitialized = false; + //set of available keys + Set availableKeys = {}; + //list of vectors List vectors = []; @@ -54,6 +58,7 @@ class InlineImageProvider extends ChangeNotifier { //function that generates the image cache //it fills the map with the Unit8List(byte Array) of the images Future generateImageCache() async { + FileHelper fileHelper = FileHelper(); await initVectors(); for (int x = 0; x < vectors.length; x++) { ui.Image image = await imageUtils.generateImageView(vectors[x]); @@ -62,6 +67,7 @@ class InlineImageProvider extends ChangeNotifier { var unit8List = byteData!.buffer.asUint8List(); imageCache[x] = unit8List; } + fileHelper.loadImageCacheFromFiles(); notifyListeners(); } diff --git a/lib/view/draw_badge_screen.dart b/lib/view/draw_badge_screen.dart index 7051232b5..aff778a8f 100644 --- a/lib/view/draw_badge_screen.dart +++ b/lib/view/draw_badge_screen.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/constants.dart'; import 'package:badgemagic/providers/drawbadge_provider.dart'; import 'package:badgemagic/view/widgets/common_scaffold_widget.dart'; @@ -5,7 +6,6 @@ import 'package:badgemagic/virtualbadge/view/draw_badge.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; class DrawBadge extends StatefulWidget { @@ -16,8 +16,6 @@ class DrawBadge extends StatefulWidget { } class _DrawBadgeState extends State { - DrawBadgeProvider cellStateToggle = GetIt.instance(); - @override void initState() { SystemChrome.setPreferredOrientations([ @@ -34,13 +32,14 @@ class _DrawBadgeState extends State { DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); - cellStateToggle.resetGrid(); super.dispose(); } @override Widget build(BuildContext context) { - DrawBadgeProvider drawToggle = Provider.of(context); + DrawBadgeProvider drawToggle = + Provider.of(context, listen: false); + FileHelper fileHelper = FileHelper(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); return CommonScaffold( title: 'BadgeMagic', @@ -126,7 +125,9 @@ class _DrawBadgeState extends State { ), ), TextButton( - onPressed: () {}, + onPressed: () { + fileHelper.saveImage(drawToggle.getGrid()); + }, child: const Column( children: [ Icon( diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index b6227f763..5d6a0739c 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -3,6 +3,7 @@ import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:badgemagic/constants.dart'; import 'package:badgemagic/providers/badge_message_provider.dart'; import 'package:badgemagic/providers/cardsprovider.dart'; +import 'package:badgemagic/providers/drawbadge_provider.dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/view/special_text_field.dart'; import 'package:badgemagic/view/widgets/common_scaffold_widget.dart'; @@ -31,10 +32,12 @@ class _HomeScreenState extends State with TickerProviderStateMixin { InlineImageProvider inlineImageProvider = GetIt.instance(); Converters converters = Converters(); + DrawBadgeProvider drawBadgeProvider = GetIt.instance(); bool isPrefixIconClicked = false; @override void initState() { + drawBadgeProvider.resetGrid(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); diff --git a/lib/view/widgets/vectorview.dart b/lib/view/widgets/vectorview.dart index 029756c61..583137bb3 100644 --- a/lib/view/widgets/vectorview.dart +++ b/lib/view/widgets/vectorview.dart @@ -31,6 +31,7 @@ class _VectorGridViewState extends State { borderRadius: BorderRadius.circular(5), ), surfaceTintColor: Colors.white, + color: Colors.white, elevation: 5, child: Padding( padding: const EdgeInsets.all(10.0), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e56ee822c..bb4b4e2ff 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import flutter_blue_plus +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 2c80199ac..6887e379f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" file: dependency: transitive description: @@ -129,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -138,10 +154,10 @@ packages: dependency: "direct main" description: name: flutter_blue_plus - sha256: ce8241302bf955bfa885457aa571cc215c10444e0c75c3e55d90b5fc05cc7e93 + sha256: "1bf31abe0c6d4992c10212fe80b3b9529b082eb62b451bf9858d3a172ca76a3a" url: "https://pub.dev" source: hosted - version: "1.32.8" + version: "1.32.11" flutter_driver: dependency: transitive description: flutter @@ -201,10 +217,10 @@ packages: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -254,10 +270,10 @@ packages: dependency: "direct main" description: name: logger - sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" matcher: dependency: transitive description: @@ -314,6 +330,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -330,6 +394,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" process: dependency: transitive description: @@ -367,6 +439,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -423,6 +503,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + url: "https://pub.dev" + source: hosted + version: "4.4.2" vector_graphics: dependency: transitive description: @@ -475,10 +563,10 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" webdriver: dependency: transitive description: @@ -487,6 +575,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b85b65502..e5251e832 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,8 @@ dependencies: get_it: ^7.7.0 flutter_screenutil: ^5.9.3 extended_text_field: ^15.0.0 + path_provider: ^2.1.4 + uuid: ^4.4.2 dev_dependencies: flutter_test: