upload, edit postcard image with filter
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
150
lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart
Normal file
150
lib/postcard/blocs/edit_image_filter/edit_image_filter_bloc.dart
Normal file
@@ -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<EditImageFilterEvent, EditImageFilterState> {
|
||||
EditImageFilterBloc() : super(EditImageFilterInitial()) {
|
||||
on<DownloadImage>((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<SelectFilter>((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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
part of 'edit_image_filter_bloc.dart';
|
||||
|
||||
class EditImageFilterEvent extends Equatable {
|
||||
const EditImageFilterEvent();
|
||||
|
||||
@override
|
||||
List<Object> 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});
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
part of 'edit_image_filter_bloc.dart';
|
||||
|
||||
class EditImageFilterState extends Equatable {
|
||||
const EditImageFilterState();
|
||||
|
||||
@override
|
||||
List<Object> 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<Object> 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 {}
|
||||
@@ -15,6 +15,7 @@ class EditPostcardBloc extends Bloc<EditPostcardEvent, EditPostcardState> {
|
||||
emit(EditPostcardLoading());
|
||||
await MyPostCardsRepository().editMyPostCards(
|
||||
postcard: event.myPostCard,
|
||||
image: event.editImage,
|
||||
);
|
||||
log("Edit PostCard Successfully");
|
||||
emit(EditPostcardSuccessfull());
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
80
lib/postcard/blocs/pick_images/pick_images_bloc.dart
Normal file
80
lib/postcard/blocs/pick_images/pick_images_bloc.dart
Normal file
@@ -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<PickImagesEvent, PickImagesState> {
|
||||
PickImagesBloc() : super(PickImagesState()) {
|
||||
final ImagePicker imagePicker = ImagePicker();
|
||||
on<TakePhoto>((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<PickPhoto>((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<SelectedFilter>((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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
19
lib/postcard/blocs/pick_images/pick_images_event.dart
Normal file
19
lib/postcard/blocs/pick_images/pick_images_event.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
part of 'pick_images_bloc.dart';
|
||||
|
||||
class PickImagesEvent extends Equatable {
|
||||
const PickImagesEvent();
|
||||
|
||||
@override
|
||||
List<Object> 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});
|
||||
}
|
||||
23
lib/postcard/blocs/pick_images/pick_images_state.dart
Normal file
23
lib/postcard/blocs/pick_images/pick_images_state.dart
Normal file
@@ -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<dynamic> get props => [file, filteredFile, loading];
|
||||
}
|
||||
@@ -20,7 +20,10 @@ class MyPostCardsRepository {
|
||||
return (response.data as List).map((e) => MyPostCard.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<void> editMyPostCards({required MyPostCard postcard}) async {
|
||||
Future<void> 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,
|
||||
|
||||
424
lib/postcard/views/edit_image_filter.dart
Normal file
424
lib/postcard/views/edit_image_filter.dart
Normal file
@@ -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<EditImageFilter> createState() => _EditImageFilterState();
|
||||
}
|
||||
|
||||
class _EditImageFilterState extends State<EditImageFilter> {
|
||||
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<EditImageFilterBloc, EditImageFilterState>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<EditPostcardView> {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
String? selectedImage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
@@ -122,8 +129,17 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
BlocConsumer<PickImagesBloc, PickImagesState>(
|
||||
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(
|
||||
@@ -132,47 +148,118 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
padding: EdgeInsets.all(10),
|
||||
height: size.width * 0.45,
|
||||
width: size.width,
|
||||
constraints: BoxConstraints(minHeight: 150),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 150,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.network(
|
||||
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)
|
||||
(
|
||||
context,
|
||||
child,
|
||||
loadingProgress,
|
||||
) {
|
||||
if (loadingProgress ==
|
||||
null) {
|
||||
return child;
|
||||
}
|
||||
return Container(
|
||||
height: size.width * 0.45,
|
||||
height:
|
||||
size.width *
|
||||
0.45,
|
||||
width: size.width,
|
||||
color: Colors.grey[300],
|
||||
color: Colors
|
||||
.grey[300],
|
||||
child: const Center(
|
||||
child:
|
||||
CircularProgressIndicator(
|
||||
color: Color(0xffF95F62),
|
||||
strokeWidth: 2,
|
||||
color: Color(
|
||||
0xffF95F62,
|
||||
),
|
||||
strokeWidth:
|
||||
2,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) {
|
||||
(
|
||||
context,
|
||||
error,
|
||||
stackTrace,
|
||||
) {
|
||||
return Container(
|
||||
height: size.width * 0.45,
|
||||
height:
|
||||
size.width *
|
||||
0.45,
|
||||
width: size.width,
|
||||
color: Colors.grey[300],
|
||||
color: Colors
|
||||
.grey[300],
|
||||
child: const Icon(
|
||||
Icons.image_not_supported,
|
||||
color: Colors.grey,
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -191,21 +278,64 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
title: 'Take a photo',
|
||||
icon: Icons.camera_alt_outlined,
|
||||
width: size.width,
|
||||
onPressed: () {
|
||||
context.read<PickImagesBloc>().add(
|
||||
TakePhoto(),
|
||||
);
|
||||
},
|
||||
),
|
||||
imageButton(
|
||||
title: 'Upload Again',
|
||||
icon: Icons.refresh,
|
||||
width: size.width,
|
||||
onPressed: () {
|
||||
context.read<PickImagesBloc>().add(
|
||||
PickPhoto(),
|
||||
);
|
||||
},
|
||||
),
|
||||
imageButton(
|
||||
title: 'Edit Filters',
|
||||
width: size.width,
|
||||
onPressed: () {
|
||||
final pickImagesBloc = context
|
||||
.read<PickImagesBloc>();
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Text(
|
||||
@@ -275,7 +405,10 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
countryName: _selectedCountry,
|
||||
);
|
||||
editPostcardBloc.add(
|
||||
EditPostCard(myPostCard: postCard!),
|
||||
EditPostCard(
|
||||
myPostCard: postCard!,
|
||||
editImage: selectedImage,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -335,7 +468,7 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
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)),
|
||||
|
||||
@@ -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';
|
||||
@@ -227,7 +228,10 @@ class _MyPostCardDraftViewState extends State<MyPostCardDraftView> {
|
||||
)
|
||||
: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w,vertical: 12.h),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
itemCount: state.draftPostCards.length,
|
||||
itemBuilder: (context, index) {
|
||||
final postcard = state.draftPostCards[index];
|
||||
@@ -445,8 +449,16 @@ class _MyPostCardDraftViewState extends State<MyPostCardDraftView> {
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => EditPostcardBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => PickImagesBloc(),
|
||||
),
|
||||
],
|
||||
|
||||
child: EditPostcardView(myPostCard: postcard),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -33,9 +33,7 @@ class _EditYourdetailsState extends State<EditYourdetails> {
|
||||
String? _selectedState;
|
||||
String? _selectedCountry;
|
||||
|
||||
final List<String> countries = [
|
||||
'Australia',
|
||||
];
|
||||
final List<String> countries = ['Australia'];
|
||||
|
||||
final List<String> states = [
|
||||
'New South Wales',
|
||||
@@ -51,8 +49,12 @@ class _EditYourdetailsState extends State<EditYourdetails> {
|
||||
@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<EditYourdetails> {
|
||||
),
|
||||
),
|
||||
items: items.map((String item) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: Text(item),
|
||||
);
|
||||
return DropdownMenuItem<String>(value: item, child: Text(item));
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
validator: (value) {
|
||||
|
||||
@@ -830,7 +830,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user