From cdfb9c74caed037a04804784ccb5722c309c0e68 Mon Sep 17 00:00:00 2001 From: Shreeyash Thorat <120039092+ShreeyashThorat@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:15:21 +0530 Subject: [PATCH] upload, edit postcard image with filter --- lib/networkApiServices/api_urls.dart | 9 +- .../edit_image_filter_bloc.dart | 150 +++++++ .../edit_image_filter_event.dart | 19 + .../edit_image_filter_state.dart | 44 ++ .../edit_postcard/edit_postcard_bloc.dart | 1 + .../edit_postcard/edit_postcard_event.dart | 3 +- .../blocs/pick_images/pick_images_bloc.dart | 80 ++++ .../blocs/pick_images/pick_images_event.dart | 19 + .../blocs/pick_images/pick_images_state.dart | 23 + .../repository/my_postcard_repository.dart | 24 +- lib/postcard/views/edit_image_filter.dart | 424 ++++++++++++++++++ lib/postcard/views/edit_postcard_view.dart | 289 ++++++++---- .../views/my_postcard_drafts_view.dart | 148 +++--- .../widgets/edit_post_card/your_details.dart | 19 +- pubspec.lock | 2 +- pubspec.yaml | 1 + 16 files changed, 1082 insertions(+), 173 deletions(-) create mode 100644 lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart create mode 100644 lib/postcard/blocs/edit_image_filter/edit_image_filter_event.dart create mode 100644 lib/postcard/blocs/edit_image_filter/edit_image_filter_state.dart create mode 100644 lib/postcard/blocs/pick_images/pick_images_bloc.dart create mode 100644 lib/postcard/blocs/pick_images/pick_images_event.dart create mode 100644 lib/postcard/blocs/pick_images/pick_images_state.dart create mode 100644 lib/postcard/views/edit_image_filter.dart diff --git a/lib/networkApiServices/api_urls.dart b/lib/networkApiServices/api_urls.dart index bab2aec..1861647 100644 --- a/lib/networkApiServices/api_urls.dart +++ b/lib/networkApiServices/api_urls.dart @@ -1,8 +1,9 @@ class ApiUrls { - - // static const baseUrl = "https://devapi.citycards.betadelivery.com";//Normal API - // static const baseUrl = "https://testingapi.citycards.betadelivery.com";// Test API - static const baseUrl = "https://uatapi.citycard.betadelivery.com";// Production Lvl API + static const baseUrl = + "https://devapi.citycards.betadelivery.com"; //Normal API + //static const baseUrl = + // "https://testingapi.citycards.betadelivery.com"; // Test API + // static const baseUrl = "https://uatapi.citycard.betadelivery.com";// Production Lvl API static const refreshToken = "$baseUrl/auth/refresh"; diff --git a/lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart b/lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart new file mode 100644 index 0000000..a392ff9 --- /dev/null +++ b/lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart @@ -0,0 +1,150 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:dio/dio.dart'; +import 'package:equatable/equatable.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:image/image.dart' as img; + +part 'edit_image_filter_event.dart'; +part 'edit_image_filter_state.dart'; + +enum EditImageType { network, file } + +class EditImageFilterBloc + extends Bloc { + EditImageFilterBloc() : super(EditImageFilterInitial()) { + on((event, emit) async { + try { + emit(DownloadImageLoading()); + if (event.type == EditImageType.network) { + final Dio dio = Dio(); + + final directory = await getTemporaryDirectory(); + + final String fileName = + '${DateTime.now().millisecondsSinceEpoch}.png'; + + final String filePath = '${directory.path}/$fileName'; + + await dio.download( + event.url, + filePath, + options: Options(responseType: ResponseType.bytes), + ); + + emit( + DownloadImageSuccessfully( + filePath: filePath, + filteredImagePath: filePath, + filter: 'original', + ), + ); + } else { + emit( + DownloadImageSuccessfully( + filePath: event.url, + filteredImagePath: event.url, + filter: 'original', + ), + ); + } + } catch (e) { + emit(DownloadImageFailed()); + } + }); + on((event, emit) async { + if (state is! DownloadImageSuccessfully) return; + + final currentState = state as DownloadImageSuccessfully; + + try { + log("Selected Filter ${event.filterName}"); + emit(currentState.copyWith(processing: true)); + + if (event.filterName == "none" || event.filterName == "original") { + emit( + currentState.copyWith( + filteredImagePath: currentState.filePath, + processing: false, + filter: "original", + ), + ); + return; + } + + final originalFile = File(currentState.filePath); + final bytes = await originalFile.readAsBytes(); + img.Image? image = img.decodeImage(bytes); + + if (image == null) { + emit(currentState.copyWith(processing: false)); + return; + } + + switch (event.filterName) { + case "vintage": + image = img.adjustColor( + image, + saturation: 0.8, + gamma: 1.1, + contrast: 0.9, + ); + break; + case "bw": + image = img.grayscale(image); + break; + case "sepia": + image = img.sepia(image); + break; + case "cool": + // hue is normalized 0.0–1.0; -15 degrees ≈ -15/360 ≈ -0.042 + image = img.adjustColor(image, hue: -0.042, contrast: 1.05); + break; + case "contrast": + image = img.adjustColor(image, contrast: 1.4); + break; + case "soft": + image = img.adjustColor( + image, + brightness: 0.1, + gamma: 0.9, + saturation: 1.1, + ); + break; + default: + emit(currentState.copyWith(filter: "none", processing: false)); + return; + } + + final filteredPath = + "${originalFile.parent.path}/filtered_${event.filterName}_${DateTime.now().millisecondsSinceEpoch}.jpg"; + + final filteredFile = File(filteredPath) + ..writeAsBytesSync(img.encodeJpg(image, quality: 95)); + + if (currentState.filteredImagePath != currentState.filePath) { + final oldFile = File(currentState.filteredImagePath); + if (await oldFile.exists()) await oldFile.delete(); + } + + log( + "Filter applied: ${filteredFile.path} | filter: ${event.filterName}", + ); + + emit( + currentState.copyWith( + filteredImagePath: filteredFile.path, + filter: event.filterName, + processing: false, + ), + ); + return; + } catch (e) { + log("SelectFilter error: ${e.toString()}"); + emit(currentState.copyWith(processing: false)); // don't leave UI stuck + } + }); + } +} diff --git a/lib/postcard/blocs/edit_image_filter/edit_image_filter_event.dart b/lib/postcard/blocs/edit_image_filter/edit_image_filter_event.dart new file mode 100644 index 0000000..69b2e02 --- /dev/null +++ b/lib/postcard/blocs/edit_image_filter/edit_image_filter_event.dart @@ -0,0 +1,19 @@ +part of 'edit_image_filter_bloc.dart'; + +class EditImageFilterEvent extends Equatable { + const EditImageFilterEvent(); + + @override + List get props => []; +} + +class DownloadImage extends EditImageFilterEvent { + final String url; + final EditImageType type; + const DownloadImage({required this.url, required this.type}); +} + +class SelectFilter extends EditImageFilterEvent { + final String filterName; + const SelectFilter({required this.filterName}); +} diff --git a/lib/postcard/blocs/edit_image_filter/edit_image_filter_state.dart b/lib/postcard/blocs/edit_image_filter/edit_image_filter_state.dart new file mode 100644 index 0000000..eca9d59 --- /dev/null +++ b/lib/postcard/blocs/edit_image_filter/edit_image_filter_state.dart @@ -0,0 +1,44 @@ +part of 'edit_image_filter_bloc.dart'; + +class EditImageFilterState extends Equatable { + const EditImageFilterState(); + + @override + List get props => []; +} + +class EditImageFilterInitial extends EditImageFilterState {} + +class DownloadImageLoading extends EditImageFilterState {} + +class DownloadImageSuccessfully extends EditImageFilterState { + final String filePath; + final String filteredImagePath; + final bool processing; + final String filter; + + const DownloadImageSuccessfully({ + required this.filePath, + required this.filteredImagePath, + this.processing = false, + required this.filter, + }); + @override + List get props => [filePath, filteredImagePath, processing, filter]; + + DownloadImageSuccessfully copyWith({ + String? filePath, + String? filteredImagePath, + bool? processing, + String? filter, + }) { + return DownloadImageSuccessfully( + filePath: filePath ?? this.filePath, + filteredImagePath: filteredImagePath ?? this.filteredImagePath, + processing: processing ?? this.processing, + filter: filter ?? this.filter, + ); + } +} + +class DownloadImageFailed extends EditImageFilterState {} diff --git a/lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart b/lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart index 5303700..518630a 100644 --- a/lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart +++ b/lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart @@ -15,6 +15,7 @@ class EditPostcardBloc extends Bloc { emit(EditPostcardLoading()); await MyPostCardsRepository().editMyPostCards( postcard: event.myPostCard, + image: event.editImage, ); log("Edit PostCard Successfully"); emit(EditPostcardSuccessfull()); diff --git a/lib/postcard/blocs/edit_postcard/edit_postcard_event.dart b/lib/postcard/blocs/edit_postcard/edit_postcard_event.dart index 61b14be..692d032 100644 --- a/lib/postcard/blocs/edit_postcard/edit_postcard_event.dart +++ b/lib/postcard/blocs/edit_postcard/edit_postcard_event.dart @@ -9,5 +9,6 @@ class EditPostcardEvent extends Equatable { class EditPostCard extends EditPostcardEvent { final MyPostCard myPostCard; - const EditPostCard({required this.myPostCard}); + final String? editImage; + const EditPostCard({required this.myPostCard, this.editImage}); } diff --git a/lib/postcard/blocs/pick_images/pick_images_bloc.dart b/lib/postcard/blocs/pick_images/pick_images_bloc.dart new file mode 100644 index 0000000..2f9301b --- /dev/null +++ b/lib/postcard/blocs/pick_images/pick_images_bloc.dart @@ -0,0 +1,80 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'pick_images_event.dart'; +part 'pick_images_state.dart'; + +class PickImagesBloc extends Bloc { + PickImagesBloc() : super(PickImagesState()) { + final ImagePicker imagePicker = ImagePicker(); + on((event, emit) async { + PickImagesState currentState = state; + try { + emit(currentState.copyWith(loading: true)); + final XFile? pickedFile = await imagePicker.pickImage( + source: ImageSource.camera, + imageQuality: 80, + maxWidth: 1920, + maxHeight: 1920, + ); + + if (pickedFile != null) { + emit( + currentState.copyWith( + loading: false, + file: pickedFile.path, + filteredFile: pickedFile.path, + ), + ); + } else { + emit(currentState.copyWith(loading: false)); + } + } catch (e) { + emit(currentState.copyWith(loading: false)); + } + }); + + on((event, emit) async { + PickImagesState currentState = state; + try { + emit(currentState.copyWith(loading: true)); + final XFile? pickedFile = await imagePicker.pickImage( + source: ImageSource.gallery, + imageQuality: 80, + maxWidth: 1920, + maxHeight: 1920, + ); + + if (pickedFile != null) { + emit( + currentState.copyWith( + loading: false, + file: pickedFile.path, + filteredFile: pickedFile.path, + ), + ); + } else { + emit(currentState.copyWith(loading: false)); + } + } catch (e) { + emit(currentState.copyWith(loading: false)); + } + }); + + on((event, emit) async { + PickImagesState currentState = state; + try { + emit( + currentState.copyWith( + loading: false, + file: currentState.file ?? event.imagePath, + filteredFile: event.imagePath, + ), + ); + } catch (e) { + emit(currentState.copyWith(loading: false)); + } + }); + } +} diff --git a/lib/postcard/blocs/pick_images/pick_images_event.dart b/lib/postcard/blocs/pick_images/pick_images_event.dart new file mode 100644 index 0000000..c6f72f9 --- /dev/null +++ b/lib/postcard/blocs/pick_images/pick_images_event.dart @@ -0,0 +1,19 @@ +part of 'pick_images_bloc.dart'; + +class PickImagesEvent extends Equatable { + const PickImagesEvent(); + + @override + List get props => []; +} + +class TakePhoto extends PickImagesEvent {} + +class PickPhoto extends PickImagesEvent {} + +class RemovePhoto extends PickImagesEvent {} + +class SelectedFilter extends PickImagesEvent { + final String imagePath; + const SelectedFilter({required this.imagePath}); +} diff --git a/lib/postcard/blocs/pick_images/pick_images_state.dart b/lib/postcard/blocs/pick_images/pick_images_state.dart new file mode 100644 index 0000000..1b19c04 --- /dev/null +++ b/lib/postcard/blocs/pick_images/pick_images_state.dart @@ -0,0 +1,23 @@ +part of 'pick_images_bloc.dart'; + +class PickImagesState extends Equatable { + final String? file; + final String? filteredFile; + final bool? loading; + const PickImagesState({this.file, this.loading = false, this.filteredFile}); + + PickImagesState copyWith({ + String? file, + bool? loading, + String? filteredFile, + }) { + return PickImagesState( + file: file ?? this.file, + loading: loading ?? this.loading, + filteredFile: filteredFile ?? this.filteredFile, + ); + } + + @override + List get props => [file, filteredFile, loading]; +} diff --git a/lib/postcard/repository/my_postcard_repository.dart b/lib/postcard/repository/my_postcard_repository.dart index 61e63f7..af9817a 100644 --- a/lib/postcard/repository/my_postcard_repository.dart +++ b/lib/postcard/repository/my_postcard_repository.dart @@ -20,7 +20,10 @@ class MyPostCardsRepository { return (response.data as List).map((e) => MyPostCard.fromJson(e)).toList(); } - Future editMyPostCards({required MyPostCard postcard}) async { + Future editMyPostCards({ + required MyPostCard postcard, + String? image, + }) async { try { final formData = FormData(); @@ -44,16 +47,15 @@ class MyPostCardsRepository { if (postcard.address2.isNotEmpty) { formData.fields.add(MapEntry('address2', postcard.address2)); } - // final fileName = postcard.pcImagePath.split('/').last; - // formData.files.add( - // MapEntry( - // 'pcImage', - // await MultipartFile.fromFile( - // postcard.pcImagePath, - // filename: fileName, - // ), - // ), - // ); + if (image != null && image.isNotEmpty) { + final fileName = image.split('/').last; + formData.files.add( + MapEntry( + 'pcImage', + await MultipartFile.fromFile(image, filename: fileName), + ), + ); + } await _apiService.putApi( url: '${ApiUrls.editPostcard}/${postcard.id}', data: formData, diff --git a/lib/postcard/views/edit_image_filter.dart b/lib/postcard/views/edit_image_filter.dart new file mode 100644 index 0000000..342998d --- /dev/null +++ b/lib/postcard/views/edit_image_filter.dart @@ -0,0 +1,424 @@ +import 'dart:io'; + +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart'; +import 'package:citycards_customer/postcard/blocs/pick_images/pick_images_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../widgets/dotted_border_holder.dart'; + +class EditImageFilter extends StatefulWidget { + final EditImageType type; + final String url; + final PickImagesBloc pickImagesBloc; + const EditImageFilter({ + super.key, + required this.type, + required this.url, + required this.pickImagesBloc, + }); + + @override + State createState() => _EditImageFilterState(); +} + +class _EditImageFilterState extends State { + final EditImageFilterBloc editImageFilterBloc = EditImageFilterBloc(); + + @override + void initState() { + editImageFilterBloc.add(DownloadImage(url: widget.url, type: widget.type)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + bloc: editImageFilterBloc, + listener: (ctx, state) { + if (state is DownloadImageFailed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Failed to fetch edit details")), + ); + Navigator.pop(context); + } + }, + builder: (context, state) { + if (state is DownloadImageLoading) { + return const Scaffold( + body: SafeArea(child: Center(child: CircularProgressIndicator())), + ); + } + + if (state is DownloadImageSuccessfully) { + return Scaffold( + body: SafeArea( + child: Stack( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showDivider: true, + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + children: [ + const Icon(Icons.arrow_back, size: 20), + const SizedBox(width: 8), + const Text( + "Back", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + const Text( + "Add a Filter", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 6), + Text( + "Choose your favorite filter and enhance your postcard.", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color(0xff2D3134), + ), + ), + const SizedBox(height: 10), + DottedBorderContainerHolder( + imagePath: state.filteredImagePath, + filter: state.filter, + ), + const SizedBox(height: 20), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + buildFilterOption( + context, + "Original", + File(state.filePath), + "original", + state.filter == "original", + ), + buildFilterOption( + context, + "Black & White", + File(state.filePath), + "bw", + state.filter == "bw", + ), + buildFilterOption( + context, + "Sepia", + File(state.filePath), + "sepia", + state.filter == "sepia", + ), + buildFilterOption( + context, + "Vintage", + File(state.filePath), + "vintage", + state.filter == "vintage", + ), + buildFilterOption( + context, + "Cool Tone", + File(state.filePath), + "cool", + state.filter == "cool", + ), + buildFilterOption( + context, + "Contrast", + File(state.filePath), + "contrast", + state.filter == "contrast", + ), + buildFilterOption( + context, + "Soft Glow", + File(state.filePath), + "soft", + state.filter == "soft", + ), + ], + ), + ), + SizedBox(height: 20.h), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffF95F62), + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + onPressed: () { + widget.pickImagesBloc.add( + SelectedFilter( + imagePath: state.filteredImagePath, + ), + ); + Navigator.pop(context); + }, + child: Text( + "Save Changes", + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + + // Processing overlay + if (state.processing == true) + Container( + color: Colors.black.withValues(alpha: .4), + child: const Center( + child: CircularProgressIndicator( + color: Color(0xffF95F62), + ), + ), + ), + ], + ), + ), + ); + } + + return const Scaffold( + body: SafeArea(child: Center(child: CircularProgressIndicator())), + ); + }, + ); + } + + /// Builds a single filter preview thumbnail + Widget buildFilterOption( + BuildContext context, + String label, + File imageFile, + String filter, + bool isSelected, + ) { + return GestureDetector( + onTap: () => editImageFilterBloc.add(SelectFilter(filterName: filter)), + child: Container( + margin: const EdgeInsets.only(right: 12), + width: 90, + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: ColorFiltered( + colorFilter: getColorFilter(filter), + child: Image.file( + imageFile, + height: 70, + width: 90, + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(height: 6), + Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: isSelected + ? const Color(0xffF95F62) + : const Color(0xff2D3134), + ), + ), + ], + ), + ), + ); + } + + ColorFilter getColorFilter(String? filter) { + switch (filter) { + case "vintage": + // Muted, warm tones without overflow + return const ColorFilter.matrix([ + 0.9, + 0.3, + 0.1, + 0, + 0, + 0.2, + 0.8, + 0.1, + 0, + 0, + 0.1, + 0.3, + 0.7, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + ]); + + case "bw": + // Grayscale + return const ColorFilter.matrix([ + 0.2126, + 0.7152, + 0.0722, + 0, + 0, + 0.2126, + 0.7152, + 0.0722, + 0, + 0, + 0.2126, + 0.7152, + 0.0722, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + ]); + + case "sepia": + // Classic soft brown + return const ColorFilter.matrix([ + 0.393, + 0.769, + 0.189, + 0, + 0, + 0.349, + 0.686, + 0.168, + 0, + 0, + 0.272, + 0.534, + 0.131, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + ]); + + case "cool": + // Gentle blue tone — no gamma boost to avoid clipping + return const ColorFilter.matrix([ + 1.0, + 0, + 0, + 0, + 0, + 0, + 1.0, + 0, + 0, + 0, + 0, + 0, + 1.1, + 0, + 10, + 0, + 0, + 0, + 1, + 0, + ]); + + case "contrast": + // Slight contrast increase, safe range + return const ColorFilter.matrix([ + 1.1, + 0, + 0, + 0, + -10, + 0, + 1.1, + 0, + 0, + -10, + 0, + 0, + 1.1, + 0, + -10, + 0, + 0, + 0, + 1, + 0, + ]); + + case "soft": + // Gentle brightness and warmth — fixed to avoid pixelation + return const ColorFilter.matrix([ + 1.02, + 0, + 0, + 0, + 5, + 0, + 1.02, + 0, + 0, + 5, + 0, + 0, + 1.02, + 0, + 5, + 0, + 0, + 0, + 1, + 0, + ]); + + default: + return const ColorFilter.mode(Colors.transparent, BlendMode.srcOver); + } + } +} diff --git a/lib/postcard/views/edit_postcard_view.dart b/lib/postcard/views/edit_postcard_view.dart index 2a04026..aa90f25 100644 --- a/lib/postcard/views/edit_postcard_view.dart +++ b/lib/postcard/views/edit_postcard_view.dart @@ -1,4 +1,8 @@ +import 'dart:io'; + +import 'package:citycards_customer/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart'; import 'package:citycards_customer/postcard/blocs/edit_postcard/edit_postcard_bloc.dart'; +import 'package:citycards_customer/postcard/blocs/pick_images/pick_images_bloc.dart'; import 'package:citycards_customer/postcard/models/my_postcard_model.dart'; import 'package:citycards_customer/postcard/widgets/dotted_border_container.dart'; import 'package:flutter/material.dart'; @@ -12,6 +16,7 @@ import '../../common_packages/custom_text.dart'; import '../../networkApiServices/api_urls.dart'; import '../widgets/edit_post_card/edit_message.dart'; import '../widgets/edit_post_card/your_details.dart'; +import 'edit_image_filter.dart'; class EditPostcardView extends StatefulWidget { final MyPostCard myPostCard; @@ -58,6 +63,8 @@ class _EditPostcardViewState extends State { super.initState(); } + String? selectedImage; + @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; @@ -122,90 +129,213 @@ class _EditPostcardViewState extends State { ), ), SizedBox(height: 10.h), - Row( - // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: CustomPaint( - painter: DottedBorderPainter(), - child: Container( - padding: EdgeInsets.all(10), - height: size.width * 0.45, - width: size.width, - constraints: BoxConstraints(minHeight: 150), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Image.network( - '${ApiUrls.baseUrl}${postCard!.pcImagePath}', + BlocConsumer( + listener: (ctx, state) { + if (state.file != null && state.file!.isNotEmpty) { + setState(() { + selectedImage = + state.filteredFile ?? state.file!; + }); + } + }, + builder: (context, state) { + return Row( + children: [ + Expanded( + child: CustomPaint( + painter: DottedBorderPainter(), + child: Container( + padding: EdgeInsets.all(10), height: size.width * 0.45, width: size.width, - fit: BoxFit.cover, - loadingBuilder: - (context, child, loadingProgress) { - if (loadingProgress == null) - return child; - return Container( - height: size.width * 0.45, - width: size.width, - color: Colors.grey[300], - child: const Center( - child: - CircularProgressIndicator( - color: Color(0xffF95F62), - strokeWidth: 2, - ), + constraints: BoxConstraints( + minHeight: 150, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: + state.file != null && + state.file!.isNotEmpty + ? Image.file( + height: size.width * 0.45, + width: size.width, + fit: BoxFit.cover, + File( + state.filteredFile ?? + state.file!, + ), + ) + : Stack( + children: [ + Image.network( + '${ApiUrls.baseUrl}${postCard!.pcImagePath}', + height: size.width * 0.45, + width: size.width, + fit: BoxFit.cover, + loadingBuilder: + ( + context, + child, + loadingProgress, + ) { + if (loadingProgress == + null) { + return child; + } + return Container( + height: + size.width * + 0.45, + width: size.width, + color: Colors + .grey[300], + child: const Center( + child: + CircularProgressIndicator( + color: Color( + 0xffF95F62, + ), + strokeWidth: + 2, + ), + ), + ); + }, + errorBuilder: + ( + context, + error, + stackTrace, + ) { + return Container( + height: + size.width * + 0.45, + width: size.width, + color: Colors + .grey[300], + child: const Icon( + Icons + .image_not_supported, + color: + Colors.grey, + ), + ); + }, + ), + + Positioned( + child: state.loading == true + ? Container( + height: + size.width * + 0.45, + width: size.width, + decoration: + BoxDecoration( + color: Colors + .black + .withValues( + alpha: + 0.25, + ), + ), + child: Center( + child: SizedBox( + height: 25, + width: 25, + child: CircularProgressIndicator( + color: Colors + .white, + strokeWidth: + 2, + ), + ), + ), + ) + : SizedBox(), + ), + ], ), - ); - }, - errorBuilder: - (context, error, stackTrace) { - return Container( - height: size.width * 0.45, - width: size.width, - color: Colors.grey[300], - child: const Icon( - Icons.image_not_supported, - color: Colors.grey, - ), - ); - }, + ), ), ), ), - ), - ), - Expanded( - child: Container( - height: size.width * 0.5, - width: size.width, - constraints: BoxConstraints(minHeight: 150), - padding: EdgeInsets.all(10), - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - imageButton( - title: 'Take a photo', - icon: Icons.camera_alt_outlined, - width: size.width, + Expanded( + child: Container( + height: size.width * 0.5, + width: size.width, + constraints: BoxConstraints(minHeight: 150), + padding: EdgeInsets.all(10), + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + imageButton( + title: 'Take a photo', + icon: Icons.camera_alt_outlined, + width: size.width, + onPressed: () { + context.read().add( + TakePhoto(), + ); + }, + ), + imageButton( + title: 'Upload Again', + icon: Icons.refresh, + width: size.width, + onPressed: () { + context.read().add( + PickPhoto(), + ); + }, + ), + imageButton( + title: 'Edit Filters', + width: size.width, + onPressed: () { + final pickImagesBloc = context + .read(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider( + create: (context) => + EditImageFilterBloc(), + child: EditImageFilter( + type: + state.file != null && + state + .file! + .isNotEmpty + ? EditImageType.file + : EditImageType.network, + url: + state.file != null && + state + .file! + .isNotEmpty + ? state.file! + : '${ApiUrls.baseUrl}${postCard!.pcImagePath}', + pickImagesBloc: + pickImagesBloc, + ), + ), + ), + ); + }, + ), + ], ), - imageButton( - title: 'Upload Again', - icon: Icons.refresh, - width: size.width, - ), - imageButton( - title: 'Edit Filters', - width: size.width, - ), - ], + ), ), - ), - ), - ], + ], + ); + }, ), SizedBox(height: 10.h), Text( @@ -275,7 +405,10 @@ class _EditPostcardViewState extends State { countryName: _selectedCountry, ); editPostcardBloc.add( - EditPostCard(myPostCard: postCard!), + EditPostCard( + myPostCard: postCard!, + editImage: selectedImage, + ), ); } }, @@ -335,7 +468,7 @@ class _EditPostcardViewState extends State { return SizedBox( width: width, child: OutlinedButton( - onPressed: () {}, + onPressed: onPressed, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), side: const BorderSide(color: Color(0xffF95F62)), diff --git a/lib/postcard/views/my_postcard_drafts_view.dart b/lib/postcard/views/my_postcard_drafts_view.dart index 59fc256..1d07b58 100644 --- a/lib/postcard/views/my_postcard_drafts_view.dart +++ b/lib/postcard/views/my_postcard_drafts_view.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:citycards_customer/postcard/blocs/edit_postcard/edit_postcard_bloc.dart'; +import 'package:citycards_customer/postcard/blocs/pick_images/pick_images_bloc.dart'; import 'package:citycards_customer/postcard/views/edit_postcard_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -108,18 +109,18 @@ class _MyPostCardDraftViewState extends State { ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( - icon: Icon( - Icons.clear, - color: Colors.black38, - size: 20, - ), - onPressed: () { - _searchController.clear(); - context.read().add( - const ClearDraftSearch(), - ); - }, - ) + icon: Icon( + Icons.clear, + color: Colors.black38, + size: 20, + ), + onPressed: () { + _searchController.clear(); + context.read().add( + const ClearDraftSearch(), + ); + }, + ) : null, filled: true, fillColor: Colors.white, @@ -150,7 +151,7 @@ class _MyPostCardDraftViewState extends State { onChanged: (query) { context.read().add( SearchDraftPostCards(query: query), - );// To update clear button visibility + ); // To update clear button visibility }, ), @@ -185,55 +186,58 @@ class _MyPostCardDraftViewState extends State { color: const Color(0xffF95F62), child: state.draftPostCards.isEmpty ? ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: [ - SizedBox(height: 100.h), - Center( - child: Column( + physics: const AlwaysScrollableScrollPhysics(), children: [ - Icon( - Icons.search_off, - size: 48, - color: Colors.black26, - ), - SizedBox(height: 16.h), - Text( - 'No search available', - style: GoogleFonts.poppins( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: Colors.black54, - ), - ), - SizedBox(height: 8.h), - if (state.draftSearchQuery.isNotEmpty) - Padding( - padding: EdgeInsets.symmetric( - horizontal: 32.w, - ), - child: Text( - 'Try searching with different keywords', - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontSize: 14.sp, - color: Colors.black38, + SizedBox(height: 100.h), + Center( + child: Column( + children: [ + Icon( + Icons.search_off, + size: 48, + color: Colors.black26, ), - ), + SizedBox(height: 16.h), + Text( + 'No search available', + style: GoogleFonts.poppins( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.black54, + ), + ), + SizedBox(height: 8.h), + if (state.draftSearchQuery.isNotEmpty) + Padding( + padding: EdgeInsets.symmetric( + horizontal: 32.w, + ), + child: Text( + 'Try searching with different keywords', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 14.sp, + color: Colors.black38, + ), + ), + ), + ], ), + ), ], - ), - ), - ], - ) + ) : ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.symmetric(horizontal: 12.w,vertical: 12.h), - itemCount: state.draftPostCards.length, - itemBuilder: (context, index) { - final postcard = state.draftPostCards[index]; - return _buildDraftCard(context, postcard); - }, - ), + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, + ), + itemCount: state.draftPostCards.length, + itemBuilder: (context, index) { + final postcard = state.draftPostCards[index]; + return _buildDraftCard(context, postcard); + }, + ), ), Positioned( @@ -243,14 +247,14 @@ class _MyPostCardDraftViewState extends State { bottom: 0, child: state.isDeleteLoading == true ? Center( - child: SizedBox( - width: 25, - height: 25, - child: CircularProgressIndicator( - color: Color(0XFFF95F62), - ), - ), - ) + child: SizedBox( + width: 25, + height: 25, + child: CircularProgressIndicator( + color: Color(0XFFF95F62), + ), + ), + ) : SizedBox(), ), ], @@ -445,8 +449,16 @@ class _MyPostCardDraftViewState extends State { onPressed: () async { final result = await Navigator.of(context).push( MaterialPageRoute( - builder: (_) => BlocProvider( - create: (context) => EditPostcardBloc(), + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => EditPostcardBloc(), + ), + BlocProvider( + create: (context) => PickImagesBloc(), + ), + ], + child: EditPostcardView(myPostCard: postcard), ), ), @@ -539,4 +551,4 @@ class _MyPostCardDraftViewState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/postcard/widgets/edit_post_card/your_details.dart b/lib/postcard/widgets/edit_post_card/your_details.dart index efc051f..6d8141d 100644 --- a/lib/postcard/widgets/edit_post_card/your_details.dart +++ b/lib/postcard/widgets/edit_post_card/your_details.dart @@ -33,9 +33,7 @@ class _EditYourdetailsState extends State { String? _selectedState; String? _selectedCountry; - final List countries = [ - 'Australia', - ]; + final List countries = ['Australia']; final List states = [ 'New South Wales', @@ -51,8 +49,12 @@ class _EditYourdetailsState extends State { @override void initState() { setState(() { - _selectedState = widget.selectedState; - _selectedCountry = widget.selectedCountry; + _selectedState = states.contains(widget.selectedState) + ? widget.selectedState + : null; + _selectedCountry = countries.contains(widget.selectedCountry) + ? widget.selectedCountry + : null; }); super.initState(); } @@ -257,10 +259,7 @@ class _EditYourdetailsState extends State { ), ), items: items.map((String item) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); + return DropdownMenuItem(value: item, child: Text(item)); }).toList(), onChanged: onChanged, validator: (value) { @@ -274,4 +273,4 @@ class _EditYourdetailsState extends State { ), ); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index 3282811..ae52304 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -830,7 +830,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/pubspec.yaml b/pubspec.yaml index 17fe786..395ddbb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: bloc: ^9.2.0 csc_picker_plus: ^0.0.3 flutter_slidable: ^4.0.3 + path_provider: ^2.1.5 dev_dependencies: flutter_test: