diff --git a/lib/data/service/pin_service.g.dart b/lib/data/service/pin_service.g.dart index 24c2da08..3f9902df 100644 --- a/lib/data/service/pin_service.g.dart +++ b/lib/data/service/pin_service.g.dart @@ -6,7 +6,7 @@ part of 'pin_service.dart'; // RiverpodGenerator // ************************************************************************** -String _$pinByIdHash() => r'e9a8f482c6f7f1d5dc6885ad4cad5aecedd68c64'; +String _$pinByIdHash() => r'b0696d4be4b207ba641594822b20e7500b1417dc'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/features/camera/data/camera_state.dart b/lib/features/camera/data/camera_state.dart index 9ccebc25..aa9ff24b 100644 --- a/lib/features/camera/data/camera_state.dart +++ b/lib/features/camera/data/camera_state.dart @@ -72,7 +72,7 @@ Future cameraController(Ref ref) async { final controller = CameraController( cameras[cameraIndex], - ResolutionPreset.medium, + ResolutionPreset.high, enableAudio: false, ); diff --git a/lib/features/camera/data/camera_state.g.dart b/lib/features/camera/data/camera_state.g.dart index a64ce89f..ce094d56 100644 --- a/lib/features/camera/data/camera_state.g.dart +++ b/lib/features/camera/data/camera_state.g.dart @@ -6,7 +6,7 @@ part of 'camera_state.dart'; // RiverpodGenerator // ************************************************************************** -String _$cameraControllerHash() => r'b86905301ef75bfa61c40e6e204b01312925699c'; +String _$cameraControllerHash() => r'babeccea41cd9c8976ba0f674841adbd0548ab87'; /// See also [cameraController]. @ProviderFor(cameraController) diff --git a/lib/features/camera/presentation/camera.dart b/lib/features/camera/presentation/camera.dart index 7f094684..d7c92eb6 100644 --- a/lib/features/camera/presentation/camera.dart +++ b/lib/features/camera/presentation/camera.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:geolocator/geolocator.dart'; import 'package:go_router/go_router.dart'; +import 'package:image/image.dart' as img; import 'package:image_cropper/image_cropper.dart'; import 'package:latlong2/latlong.dart'; import 'package:mutex/mutex.dart'; @@ -76,24 +77,29 @@ class _CameraState extends ConsumerState with WidgetsBindingObserver { children: [ Align( alignment: Alignment.bottomCenter, - // We handle the AsyncValue of the CONTROLLER here child: controllerAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, stack) => Center(child: Text("Camera Error: $err")), data: (controller) { - // Once controller is ready, we check the Values state return cameraStateAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, stack) => Text(err.toString()), - data: (cameraState) => GestureDetector( - onDoubleTap: ref.read(cameraIndexProvider.notifier).increment, - onScaleStart: (_) => basScaleFactor = scaleFactor, - onScaleUpdate: (details) => handleZoom(details, controller, cameraState), - child: Padding( - padding: const EdgeInsets.all(5.0), - child: CameraPreview(controller), - ), - ), + data: (cameraState) { + return GestureDetector( + onDoubleTap: ref.read(cameraIndexProvider.notifier).increment, + onScaleStart: (_) => basScaleFactor = scaleFactor, + onScaleUpdate: (details) => handleZoom(details, controller, cameraState), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: ClipRect( + child: AspectRatio( + aspectRatio: 3/4, + child: CameraPreview(controller), + ), + ), + ), + ); + }, ); }, ), @@ -256,31 +262,89 @@ class _CameraState extends ConsumerState with WidgetsBindingObserver { Future takePicture(String groupId, int index) async { final indexProvider = ref.read(cameraGroupIndexProvider); - if(index != indexProvider) { + if (index != indexProvider) { pageController.animateToPage(index, duration: const Duration(milliseconds: 200), curve: Curves.easeIn); return; } + final controller = ref.read(cameraControllerProvider).value; if (_m.isLocked || controller == null) return; + await _m.acquire(); ref.read(cameraCapturingProvider.notifier).setCapturing(true); - try { - final image = await controller.takePicture(); - final Uint8List bytes = await image.readAsBytes(); - final Position position = await Geolocator.getCurrentPosition(); - final pos = LatLng(position.latitude, position.longitude); - // Pause preview while the ImageUpload screen is on top - if (controller.value.isInitialized) { - await controller.pausePreview().catchError((_) {}); - } - if (!mounted) return; - context.pushNamed('imageUpload', queryParameters: {"lat": pos.latitude.toString(), "long": pos.longitude.toString()}, extra: bytes); - } catch (e) { - if(kDebugMode) print(e); - } finally { - _m.release(); - ref.read(cameraCapturingProvider.notifier).setCapturing(false); + + try { + final image = await controller.takePicture(); + Uint8List bytes = await image.readAsBytes(); + + // Hardcode the target ratio to exactly 3:4 + const double targetRatio = 3 / 4; + + // Crop the image in a background thread using the function from the previous step + bytes = await compute(_cropImageToRatio, (bytes: bytes, targetRatio: targetRatio)); + + final Position position = await Geolocator.getCurrentPosition(); + final pos = LatLng(position.latitude, position.longitude); + + if (controller.value.isInitialized) { + await controller.pausePreview().catchError((_) {}); } + + if (!mounted) return; + context.pushNamed( + 'imageUpload', + queryParameters: {"lat": pos.latitude.toString(), "long": pos.longitude.toString()}, + extra: bytes, + ); + + } catch (e) { + if (kDebugMode) print(e); + } finally { + _m.release(); + ref.read(cameraCapturingProvider.notifier).setCapturing(false); + } + } + /// Crops the image center to match the target aspect ratio + Uint8List _cropImageToRatio(({Uint8List bytes, double targetRatio}) data) { + // Decode the image from bytes + final originalImage = img.decodeImage(data.bytes); + if (originalImage == null) return data.bytes; // Fallback if decode fails + + final int w = originalImage.width; + final int h = originalImage.height; + final double currentRatio = w / h; + + // If the aspect ratios already match, just return the original bytes + if ((currentRatio - data.targetRatio).abs() < 0.05) { + return data.bytes; + } + + int cropW = w; + int cropH = h; + int cropX = 0; + int cropY = 0; + + if (currentRatio > data.targetRatio) { + // The image is wider than the UI. Crop the left and right sides. + cropW = (h * data.targetRatio).round(); + cropX = ((w - cropW) / 2).round(); + } else { + // The image is taller than the UI. Crop the top and bottom. + cropH = (w / data.targetRatio).round(); + cropY = ((h - cropH) / 2).round(); + } + + // Perform the crop + final croppedImage = img.copyCrop( + originalImage, + x: cropX, + y: cropY, + width: cropW, + height: cropH, + ); + + // Encode back to JPG and return + return img.encodeJpg(croppedImage); } } diff --git a/pubspec.lock b/pubspec.lock index 530c3847..60941298 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1046,7 +1046,7 @@ packages: source: hosted version: "4.1.2" image: - dependency: transitive + dependency: "direct main" description: name: image sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce diff --git a/pubspec.yaml b/pubspec.yaml index 6d91580c..8b7dd93e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: isar_community_flutter_libs: rxdart: ^0.28.0 go_router: ^17.1.0 + image: ^4.8.0 dev_dependencies: build_runner: diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index 081927fe..436f6beb 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 2e4e4b4b..89e7a148 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/index.html b/web/index.html index 6ed1fc5c..424f871c 100644 --- a/web/index.html +++ b/web/index.html @@ -1,38 +1,91 @@ - - + - - + - - buff_lisa + Stick-It + + + + + + + +
+ +
+
+ + + + - + \ No newline at end of file diff --git a/web/manifest.json b/web/manifest.json index 955c4981..af97742e 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -3,8 +3,8 @@ "short_name": "Stick-It", "start_url": ".", "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", + "background_color": "#e1a064", + "theme_color": "#e1a064", "description": "A sticker app.", "orientation": "portrait-primary", "prefer_related_applications": false,