Compare commits
3 Commits
48fd7037ea
...
shreeyash
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdfb9c74ca | ||
| 0abdd2b796 | |||
| dd1991da09 |
210
lib/common_packages/custom_snackbar.dart
Normal file
210
lib/common_packages/custom_snackbar.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class CustomSnackbar {
|
||||
static void show(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
Color? backgroundColor,
|
||||
Color? textColor,
|
||||
IconData? icon,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
bool useOverlay = false,
|
||||
}) {
|
||||
if (useOverlay) {
|
||||
_showOverlaySnackbar(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: backgroundColor ?? Colors.black87,
|
||||
textColor: textColor ?? Colors.white,
|
||||
icon: icon,
|
||||
duration: duration,
|
||||
);
|
||||
} else {
|
||||
_showRegularSnackbar(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: backgroundColor ?? Colors.black87,
|
||||
textColor: textColor ?? Colors.white,
|
||||
icon: icon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void _showRegularSnackbar(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
IconData? icon,
|
||||
}) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(
|
||||
icon,
|
||||
color: textColor,
|
||||
size: 20.sp,
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void _showOverlaySnackbar(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
IconData? icon,
|
||||
required Duration duration,
|
||||
}) {
|
||||
final overlay = Overlay.of(context);
|
||||
final overlayEntry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
top: MediaQuery.of(context).padding.top + 10,
|
||||
left: 20.w,
|
||||
right: 20.w,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, -20 * (1 - value)),
|
||||
child: Opacity(
|
||||
opacity: value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(
|
||||
icon,
|
||||
color: textColor,
|
||||
size: 20.sp,
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
overlay.insert(overlayEntry);
|
||||
Future.delayed(duration, () {
|
||||
overlayEntry.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Helper methods for common use cases
|
||||
static void showSuccess(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
bool useOverlay = false,
|
||||
}) {
|
||||
show(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: Colors.green,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.check_circle,
|
||||
useOverlay: useOverlay,
|
||||
);
|
||||
}
|
||||
|
||||
static void showError(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
bool useOverlay = false,
|
||||
}) {
|
||||
show(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.error,
|
||||
useOverlay: useOverlay,
|
||||
);
|
||||
}
|
||||
|
||||
static void showWarning(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
bool useOverlay = false,
|
||||
}) {
|
||||
show(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: Colors.orange,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.warning,
|
||||
useOverlay: useOverlay,
|
||||
);
|
||||
}
|
||||
|
||||
static void showInfo(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
bool useOverlay = false,
|
||||
}) {
|
||||
show(
|
||||
context,
|
||||
message: message,
|
||||
backgroundColor: Colors.blue,
|
||||
textColor: Colors.white,
|
||||
icon: Icons.info,
|
||||
useOverlay: useOverlay,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:citycards_customer/common_packages/custom_text.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CustomTextField extends StatelessWidget {
|
||||
final String label;
|
||||
@@ -8,11 +9,15 @@ class CustomTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final int? maxLines;
|
||||
final bool enabled;
|
||||
final String? Function(String?)? validator; // ✅ NEW: Validator function
|
||||
final TextInputType? keyboardType; // ✅ NEW: Keyboard type
|
||||
final bool obscureText; // ✅ NEW: For password fields
|
||||
final Widget? suffixIcon; // ✅ NEW: For icons like visibility toggle
|
||||
final void Function(String)? onChanged; // ✅ NEW: OnChanged callback
|
||||
final String? Function(String?)? validator;
|
||||
final TextInputType? keyboardType;
|
||||
final bool obscureText;
|
||||
final Widget? suffixIcon;
|
||||
final void Function(String)? onChanged;
|
||||
|
||||
// ✅ NEW
|
||||
final int? maxLength; // e.g. 10
|
||||
final bool numbersOnly; // allow only digits
|
||||
|
||||
const CustomTextField({
|
||||
super.key,
|
||||
@@ -26,6 +31,10 @@ class CustomTextField extends StatelessWidget {
|
||||
this.obscureText = false,
|
||||
this.suffixIcon,
|
||||
this.onChanged,
|
||||
|
||||
// ✅ NEW
|
||||
this.maxLength, // default = null (infinite)
|
||||
this.numbersOnly = false, // default = false
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -42,16 +51,27 @@ class CustomTextField extends StatelessWidget {
|
||||
SizedBox(height: 6.h),
|
||||
SizedBox(
|
||||
height: maxLines == 1 ? 42.h : null,
|
||||
child: TextFormField( // ✅ Changed from TextField to TextFormField
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
maxLines: obscureText ? 1 : maxLines, // ✅ Password fields always single line
|
||||
maxLines: obscureText ? 1 : maxLines,
|
||||
enabled: enabled,
|
||||
validator: validator, // ✅ Added validator
|
||||
keyboardType: keyboardType, // ✅ Added keyboard type
|
||||
obscureText: obscureText, // ✅ Added obscure text
|
||||
onChanged: onChanged, // ✅ Added onChanged
|
||||
validator: validator,
|
||||
keyboardType: keyboardType,
|
||||
obscureText: obscureText,
|
||||
onChanged: onChanged,
|
||||
|
||||
// ✅ NEW
|
||||
maxLength: maxLength,
|
||||
inputFormatters: [
|
||||
if (numbersOnly)
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
if (maxLength != null)
|
||||
LengthLimitingTextInputFormatter(maxLength),
|
||||
],
|
||||
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
counterText: "", // ✅ hides 0/10 counter
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color: const Color(0xFF8E8E8E),
|
||||
@@ -62,9 +82,9 @@ class CustomTextField extends StatelessWidget {
|
||||
: Colors.grey.shade200,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 24.w,
|
||||
vertical: maxLines != null && maxLines! > 1 ? 12.h : 0, // ✅ Better padding for multiline
|
||||
vertical: maxLines != null && maxLines! > 1 ? 12.h : 0,
|
||||
),
|
||||
suffixIcon: suffixIcon, // ✅ Added suffix icon
|
||||
suffixIcon: suffixIcon,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
borderSide: BorderSide(
|
||||
@@ -79,28 +99,21 @@ class CustomTextField extends StatelessWidget {
|
||||
width: 1.w,
|
||||
),
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
borderSide: BorderSide(
|
||||
color: Colors.grey.shade400,
|
||||
width: .4.w,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder( // ✅ NEW: Error state border
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
borderSide: BorderSide(
|
||||
color: Colors.red,
|
||||
width: 1.w,
|
||||
),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder( // ✅ NEW: Focused error state
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
borderSide: BorderSide(
|
||||
color: Colors.red,
|
||||
width: 1.5.w,
|
||||
),
|
||||
),
|
||||
errorStyle: TextStyle( // ✅ NEW: Error text style
|
||||
errorStyle: TextStyle(
|
||||
fontSize: 11.sp,
|
||||
color: Colors.red,
|
||||
),
|
||||
|
||||
@@ -194,6 +194,8 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
||||
label: "Phone Number",
|
||||
hint: "Enter your phone number",
|
||||
controller: phoneController,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 10,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -358,6 +360,7 @@ class _CreateAccountViewState extends State<CreateAccountView> {
|
||||
hint: "Enter postal / zip code",
|
||||
controller: postalController,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 6,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:citycards_customer/login/view/verify_otp_bottomsheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import '../../common_packages/custom_snackbar.dart';
|
||||
import '../bloc/login/login_bloc.dart';
|
||||
import '../bloc/login/login_state.dart';
|
||||
import '../bloc/login/login_event.dart';
|
||||
@@ -52,12 +53,10 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
||||
),
|
||||
);
|
||||
} else if (state is LoginError) {
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errorMessage),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
CustomSnackbar.showError(
|
||||
context,
|
||||
message: state.errorMessage,
|
||||
useOverlay: true, // Use overlay to show above bottom sheet
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -117,11 +116,10 @@ class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
|
||||
|
||||
final email = _emailController.text.trim();
|
||||
if (email.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please enter your email'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
CustomSnackbar.showError(
|
||||
context,
|
||||
message: "Please enter your email",
|
||||
useOverlay: true, // Use overlay to show above bottom sheet
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import '../../common_packages/custom_snackbar.dart';
|
||||
import '../../core/route_constants.dart';
|
||||
import '../../localPreference/local_preference.dart';
|
||||
import '../../postcard/blocs/myPostCards/my_postcard_event.dart';
|
||||
@@ -77,12 +78,10 @@ class _VerifyOtpBottomsheetState extends State<VerifyOtpBottomsheet> {
|
||||
),
|
||||
);
|
||||
} else if (state is VerifyOtpError) {
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errorMessage),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
CustomSnackbar.showError(
|
||||
context,
|
||||
message: state.errorMessage,
|
||||
useOverlay: true, // Use overlay to show above bottom sheet
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -12,20 +12,24 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
final MyPostCardsRepository repository;
|
||||
|
||||
MyPostCardBloc({required this.repository})
|
||||
: super(const MyPostCardInitial()) {
|
||||
: super(const MyPostCardInitial()) {
|
||||
on<CheckLoginStatus>(_onCheckLoginStatus);
|
||||
on<FetchDraftPostCards>(_onFetchDraftPostCards);
|
||||
on<FetchOrderPostCards>(_onFetchOrderPostCards);
|
||||
on<RefreshDraftPostCards>(_onRefreshDraftPostCards);
|
||||
on<RefreshOrderPostCards>(_onRefreshOrderPostCards);
|
||||
on<DeleteDraftPostCards>(_onDeletePostCard);
|
||||
on<SearchDraftPostCards>(_onSearchDraftPostCards);
|
||||
on<SearchOrderPostCards>(_onSearchOrderPostCards);
|
||||
on<ClearDraftSearch>(_onClearDraftSearch);
|
||||
on<ClearOrderSearch>(_onClearOrderSearch);
|
||||
}
|
||||
|
||||
/// Handle checking login status
|
||||
Future<void> _onCheckLoginStatus(
|
||||
CheckLoginStatus event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
CheckLoginStatus event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('🔍 Checking login status...', name: 'MyPostCardBloc');
|
||||
emit(const MyPostCardCheckingLogin());
|
||||
|
||||
@@ -68,9 +72,9 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
/// Handle fetching draft postcards
|
||||
Future<void> _onFetchDraftPostCards(
|
||||
FetchDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
FetchDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('📥 Fetching draft postcards...', name: 'MyPostCardBloc');
|
||||
// Get current state
|
||||
final currentState = state;
|
||||
@@ -88,10 +92,11 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
// Update with fetched drafts
|
||||
// Update with fetched drafts and store in allDraftPostCards
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(
|
||||
draftPostCards: draftPostCards,
|
||||
allDraftPostCards: draftPostCards, // Store original list
|
||||
isDraftLoading: false,
|
||||
),
|
||||
);
|
||||
@@ -105,6 +110,7 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
MyPostCardLoaded(
|
||||
draftPostCards: draftPostCards,
|
||||
orderPostCards: const [],
|
||||
allDraftPostCards: draftPostCards, // Store original list
|
||||
isDraftLoading: false,
|
||||
isOrderLoading: false,
|
||||
),
|
||||
@@ -123,19 +129,28 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
}
|
||||
|
||||
Future<void> _onDeletePostCard(
|
||||
DeleteDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
DeleteDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
if (state is MyPostCardLoaded) {
|
||||
MyPostCardLoaded currentState = state as MyPostCardLoaded;
|
||||
try {
|
||||
emit(currentState.copyWith(isDeleteLoading: true));
|
||||
await MyPostCardsRepository().deleteMyPostCards(event.id);
|
||||
|
||||
List<MyPostCard> items = currentState.draftPostCards;
|
||||
items.removeWhere((e) => e.id == event.id);
|
||||
// Remove from both filtered and all lists
|
||||
List<MyPostCard> filteredItems = List.from(currentState.draftPostCards);
|
||||
List<MyPostCard> allItems = List.from(currentState.allDraftPostCards);
|
||||
|
||||
filteredItems.removeWhere((e) => e.id == event.id);
|
||||
allItems.removeWhere((e) => e.id == event.id);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(draftPostCards: items, isDeleteLoading: false),
|
||||
currentState.copyWith(
|
||||
draftPostCards: filteredItems,
|
||||
allDraftPostCards: allItems,
|
||||
isDeleteLoading: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
log("Erro - $e");
|
||||
@@ -146,9 +161,9 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
/// Handle fetching order postcards
|
||||
Future<void> _onFetchOrderPostCards(
|
||||
FetchOrderPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
FetchOrderPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('📥 Fetching order postcards...', name: 'MyPostCardBloc');
|
||||
// Get current state
|
||||
final currentState = state;
|
||||
@@ -166,10 +181,11 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
// Update with fetched orders
|
||||
// Update with fetched orders and store in allOrderPostCards
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(
|
||||
orderPostCards: orderPostCards,
|
||||
allOrderPostCards: orderPostCards, // Store original list
|
||||
isOrderLoading: false,
|
||||
),
|
||||
);
|
||||
@@ -183,6 +199,7 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
MyPostCardLoaded(
|
||||
draftPostCards: const [],
|
||||
orderPostCards: orderPostCards,
|
||||
allOrderPostCards: orderPostCards, // Store original list
|
||||
isDraftLoading: false,
|
||||
isOrderLoading: false,
|
||||
),
|
||||
@@ -202,9 +219,9 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
/// Handle refreshing draft postcards
|
||||
Future<void> _onRefreshDraftPostCards(
|
||||
RefreshDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
RefreshDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('🔄 Refreshing draft postcards...', name: 'MyPostCardBloc');
|
||||
try {
|
||||
final draftPostCards = await repository.fetchMyPostCards(type: 'draft');
|
||||
@@ -214,9 +231,27 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(draftPostCards: draftPostCards),
|
||||
);
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
// If there's an active search, apply it to the new data
|
||||
if (currentState.draftSearchQuery.isNotEmpty) {
|
||||
final filteredDrafts = _filterPostCards(
|
||||
draftPostCards,
|
||||
currentState.draftSearchQuery,
|
||||
);
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
draftPostCards: filteredDrafts,
|
||||
allDraftPostCards: draftPostCards,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
draftPostCards: draftPostCards,
|
||||
allDraftPostCards: draftPostCards,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log(
|
||||
@@ -229,9 +264,9 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
/// Handle refreshing order postcards
|
||||
Future<void> _onRefreshOrderPostCards(
|
||||
RefreshOrderPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
RefreshOrderPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('🔄 Refreshing order postcards...', name: 'MyPostCardBloc');
|
||||
try {
|
||||
final orderPostCards = await repository.fetchMyPostCards(type: 'orders');
|
||||
@@ -241,9 +276,27 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(orderPostCards: orderPostCards),
|
||||
);
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
// If there's an active search, apply it to the new data
|
||||
if (currentState.orderSearchQuery.isNotEmpty) {
|
||||
final filteredOrders = _filterPostCards(
|
||||
orderPostCards,
|
||||
currentState.orderSearchQuery,
|
||||
);
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
orderPostCards: filteredOrders,
|
||||
allOrderPostCards: orderPostCards,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
orderPostCards: orderPostCards,
|
||||
allOrderPostCards: orderPostCards,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log(
|
||||
@@ -253,4 +306,153 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
emit(MyPostCardError(errorMessage: error.toString(), errorType: 'order'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle searching draft postcards
|
||||
Future<void> _onSearchDraftPostCards(
|
||||
SearchDraftPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log(
|
||||
'🔍 Searching draft postcards with query: "${event.query}"',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
|
||||
if (event.query.isEmpty) {
|
||||
// If query is empty, show all drafts
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
draftPostCards: currentState.allDraftPostCards,
|
||||
draftSearchQuery: '',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Filter the drafts based on the query
|
||||
final filteredDrafts = _filterPostCards(
|
||||
currentState.allDraftPostCards,
|
||||
event.query,
|
||||
);
|
||||
|
||||
developer.log(
|
||||
'✅ Draft search completed: ${filteredDrafts.length} results found',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
draftPostCards: filteredDrafts,
|
||||
draftSearchQuery: event.query,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle searching order postcards
|
||||
Future<void> _onSearchOrderPostCards(
|
||||
SearchOrderPostCards event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log(
|
||||
'🔍 Searching order postcards with query: "${event.query}"',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
|
||||
if (event.query.isEmpty) {
|
||||
// If query is empty, show all orders
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
orderPostCards: currentState.allOrderPostCards,
|
||||
orderSearchQuery: '',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Filter the orders based on the query
|
||||
final filteredOrders = _filterPostCards(
|
||||
currentState.allOrderPostCards,
|
||||
event.query,
|
||||
);
|
||||
|
||||
developer.log(
|
||||
'✅ Order search completed: ${filteredOrders.length} results found',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
orderPostCards: filteredOrders,
|
||||
orderSearchQuery: event.query,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle clearing draft search
|
||||
Future<void> _onClearDraftSearch(
|
||||
ClearDraftSearch event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('🧹 Clearing draft search', name: 'MyPostCardBloc');
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
draftPostCards: currentState.allDraftPostCards,
|
||||
draftSearchQuery: '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle clearing order search
|
||||
Future<void> _onClearOrderSearch(
|
||||
ClearOrderSearch event,
|
||||
Emitter<MyPostCardState> emit,
|
||||
) async {
|
||||
developer.log('🧹 Clearing order search', name: 'MyPostCardBloc');
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
final currentState = state as MyPostCardLoaded;
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
orderPostCards: currentState.allOrderPostCards,
|
||||
orderSearchQuery: '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to filter postcards based on search query
|
||||
/// Searches by title, postcard number, and ID
|
||||
List<MyPostCard> _filterPostCards(
|
||||
List<MyPostCard> postcards,
|
||||
String query,
|
||||
) {
|
||||
final lowerQuery = query.toLowerCase().trim();
|
||||
|
||||
if (lowerQuery.isEmpty) {
|
||||
return postcards;
|
||||
}
|
||||
|
||||
return postcards.where((postcard) {
|
||||
// Search in postcard title (main field)
|
||||
final titleMatch = postcard.pcTitle.toLowerCase().contains(lowerQuery);
|
||||
|
||||
// Search in postcard number
|
||||
final numberMatch = postcard.pcNumber.toString().toLowerCase().contains(lowerQuery);
|
||||
|
||||
// Search in postcard ID
|
||||
final idMatch = postcard.id.toString().contains(lowerQuery);
|
||||
|
||||
// Return true if any field matches
|
||||
return titleMatch || numberMatch || idMatch;
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
@@ -36,3 +36,31 @@ class RefreshDraftPostCards extends MyPostCardEvent {
|
||||
class RefreshOrderPostCards extends MyPostCardEvent {
|
||||
const RefreshOrderPostCards();
|
||||
}
|
||||
|
||||
/// Event to search draft postcards
|
||||
class SearchDraftPostCards extends MyPostCardEvent {
|
||||
final String query;
|
||||
const SearchDraftPostCards({required this.query});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [query];
|
||||
}
|
||||
|
||||
/// Event to search order postcards
|
||||
class SearchOrderPostCards extends MyPostCardEvent {
|
||||
final String query;
|
||||
const SearchOrderPostCards({required this.query});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [query];
|
||||
}
|
||||
|
||||
/// Event to clear draft search
|
||||
class ClearDraftSearch extends MyPostCardEvent {
|
||||
const ClearDraftSearch();
|
||||
}
|
||||
|
||||
/// Event to clear order search
|
||||
class ClearOrderSearch extends MyPostCardEvent {
|
||||
const ClearOrderSearch();
|
||||
}
|
||||
@@ -31,13 +31,24 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
final bool isOrderLoading;
|
||||
final bool isDeleteLoading;
|
||||
|
||||
// Search related properties
|
||||
final List<MyPostCard> allDraftPostCards; // Store original unfiltered drafts
|
||||
final List<MyPostCard> allOrderPostCards; // Store original unfiltered orders
|
||||
final String draftSearchQuery;
|
||||
final String orderSearchQuery;
|
||||
|
||||
const MyPostCardLoaded({
|
||||
required this.draftPostCards,
|
||||
required this.orderPostCards,
|
||||
this.isDraftLoading = false,
|
||||
this.isOrderLoading = false,
|
||||
this.isDeleteLoading = false,
|
||||
});
|
||||
List<MyPostCard>? allDraftPostCards,
|
||||
List<MyPostCard>? allOrderPostCards,
|
||||
this.draftSearchQuery = '',
|
||||
this.orderSearchQuery = '',
|
||||
}) : allDraftPostCards = allDraftPostCards ?? draftPostCards,
|
||||
allOrderPostCards = allOrderPostCards ?? orderPostCards;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -46,6 +57,10 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
isDraftLoading,
|
||||
isOrderLoading,
|
||||
isDeleteLoading,
|
||||
allDraftPostCards,
|
||||
allOrderPostCards,
|
||||
draftSearchQuery,
|
||||
orderSearchQuery,
|
||||
];
|
||||
|
||||
/// Helper method to create a copy with updated values
|
||||
@@ -55,6 +70,10 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
bool? isDraftLoading,
|
||||
bool? isOrderLoading,
|
||||
bool? isDeleteLoading,
|
||||
List<MyPostCard>? allDraftPostCards,
|
||||
List<MyPostCard>? allOrderPostCards,
|
||||
String? draftSearchQuery,
|
||||
String? orderSearchQuery,
|
||||
}) {
|
||||
return MyPostCardLoaded(
|
||||
draftPostCards: draftPostCards ?? this.draftPostCards,
|
||||
@@ -62,6 +81,10 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
isDraftLoading: isDraftLoading ?? this.isDraftLoading,
|
||||
isOrderLoading: isOrderLoading ?? this.isOrderLoading,
|
||||
isDeleteLoading: isDeleteLoading ?? this.isDeleteLoading,
|
||||
allDraftPostCards: allDraftPostCards ?? this.allDraftPostCards,
|
||||
allOrderPostCards: allOrderPostCards ?? this.allOrderPostCards,
|
||||
draftSearchQuery: draftSearchQuery ?? this.draftSearchQuery,
|
||||
orderSearchQuery: orderSearchQuery ?? this.orderSearchQuery,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -75,4 +98,4 @@ class MyPostCardError extends MyPostCardState {
|
||||
|
||||
@override
|
||||
List<Object?> get props => [errorMessage, errorType];
|
||||
}
|
||||
}
|
||||
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,90 +129,213 @@ class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
),
|
||||
),
|
||||
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<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(
|
||||
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<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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
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<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';
|
||||
@@ -15,9 +16,22 @@ import '../blocs/myPostCards/my_postcard_event.dart';
|
||||
import '../blocs/myPostCards/my_postcard_state.dart';
|
||||
import '../models/my_postcard_model.dart';
|
||||
|
||||
class MyPostCardDraftView extends StatelessWidget {
|
||||
class MyPostCardDraftView extends StatefulWidget {
|
||||
const MyPostCardDraftView({super.key});
|
||||
|
||||
@override
|
||||
State<MyPostCardDraftView> createState() => _MyPostCardDraftViewState();
|
||||
}
|
||||
|
||||
class _MyPostCardDraftViewState extends State<MyPostCardDraftView> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MyPostCardBloc, MyPostCardState>(
|
||||
@@ -32,7 +46,7 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Show empty state if no drafts
|
||||
if (state.draftPostCards.isEmpty) {
|
||||
if (state.allDraftPostCards.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
@@ -82,41 +96,169 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Show the list of drafts
|
||||
return Stack(
|
||||
return Column(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshDraftPostCards(),
|
||||
);
|
||||
},
|
||||
color: const Color(0xffF95F62),
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: state.draftPostCards.length,
|
||||
itemBuilder: (context, index) {
|
||||
final postcard = state.draftPostCards[index];
|
||||
return _buildDraftCard(context, postcard);
|
||||
},
|
||||
// Search Field
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search drafts...',
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.black38,
|
||||
),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
color: Colors.black38,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const ClearDraftSearch(),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62).withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62).withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
),
|
||||
onChanged: (query) {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
SearchDraftPostCards(query: query),
|
||||
); // To update clear button visibility
|
||||
},
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: state.isDeleteLoading == true
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0XFFF95F62),
|
||||
),
|
||||
// Search Results Info
|
||||
if (state.draftSearchQuery.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Found ${state.draftPostCards.length} of ${state.allDraftPostCards.length} drafts',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.black54,
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.draftSearchQuery.isNotEmpty) SizedBox(height: 8.h),
|
||||
|
||||
// List with Stack for loading
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshDraftPostCards(),
|
||||
);
|
||||
},
|
||||
color: const Color(0xffF95F62),
|
||||
child: state.draftPostCards.isEmpty
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
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);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: state.isDeleteLoading == true
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0XFFF95F62),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -307,8 +449,16 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
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),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -11,9 +11,22 @@ import '../models/my_postcard_model.dart';
|
||||
import '../../networkApiServices/api_urls.dart';
|
||||
import 'my_postcard_preview_view.dart';
|
||||
|
||||
class MyPostCardOrdersView extends StatelessWidget {
|
||||
class MyPostCardOrdersView extends StatefulWidget {
|
||||
const MyPostCardOrdersView({super.key});
|
||||
|
||||
@override
|
||||
State<MyPostCardOrdersView> createState() => _MyPostCardOrdersViewState();
|
||||
}
|
||||
|
||||
class _MyPostCardOrdersViewState extends State<MyPostCardOrdersView> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MyPostCardBloc, MyPostCardState>(
|
||||
@@ -28,7 +41,7 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Show empty state if no orders
|
||||
if (state.orderPostCards.isEmpty) {
|
||||
if (state.allOrderPostCards.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
@@ -77,20 +90,162 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// Show the list of orders
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<MyPostCardBloc>().add(const RefreshOrderPostCards());
|
||||
},
|
||||
color: const Color(0xffF95F62),
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: state.orderPostCards.length,
|
||||
itemBuilder: (context, index) {
|
||||
final postcard = state.orderPostCards[index];
|
||||
return _buildOrderCard(context, postcard);
|
||||
},
|
||||
),
|
||||
// Show the list of orders with search
|
||||
return Column(
|
||||
children: [
|
||||
// Search Field
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search orders...',
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.black38,
|
||||
),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
color: Colors.black38,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const ClearOrderSearch(),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62).withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62).withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffF95F62),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
),
|
||||
onChanged: (query) {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
SearchOrderPostCards(query: query),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Search Results Info
|
||||
if (state.orderSearchQuery.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Found ${state.orderPostCards.length} of ${state.allOrderPostCards.length} orders',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.orderSearchQuery.isNotEmpty) SizedBox(height: 8.h),
|
||||
|
||||
// List with Stack for loading
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshOrderPostCards(),
|
||||
);
|
||||
},
|
||||
color: const Color(0xffF95F62),
|
||||
child: state.orderPostCards.isEmpty
|
||||
? ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
SizedBox(height: 100.h),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_off,
|
||||
size: 48,
|
||||
color: Colors.black26,
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Text(
|
||||
'No orders found',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
if (state.orderSearchQuery.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32.w,
|
||||
),
|
||||
child: Text(
|
||||
'Try adjusting your search query',
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.black38,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: state.orderPostCards.length,
|
||||
padding: EdgeInsets.only(top: 16.h),
|
||||
itemBuilder: (context, index) {
|
||||
final postcard = state.orderPostCards[index];
|
||||
return _buildOrderCard(context, postcard);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Loading overlay
|
||||
if (state.isOrderLoading)
|
||||
Container(
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xffF95F62),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -384,4 +539,4 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,465 +1,484 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../common_packages/app_bar.dart';
|
||||
import '../../common_packages/back_widget.dart';
|
||||
import '../models/my_postcard_model.dart';
|
||||
import '../../networkApiServices/api_urls.dart';
|
||||
import '../widgets/back_card_widget.dart';
|
||||
import '../widgets/front_card_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../common_packages/app_bar.dart';
|
||||
import '../../common_packages/back_widget.dart';
|
||||
import '../blocs/edit_postcard/edit_postcard_bloc.dart';
|
||||
import '../blocs/myPostCards/my_postcard_bloc.dart';
|
||||
import '../blocs/myPostCards/my_postcard_event.dart';
|
||||
import '../models/my_postcard_model.dart';
|
||||
import '../../networkApiServices/api_urls.dart';
|
||||
import '../widgets/back_card_widget.dart';
|
||||
import '../widgets/front_card_widget.dart';
|
||||
import 'edit_postcard_view.dart';
|
||||
|
||||
class MyPostcardPreviewView extends StatefulWidget {
|
||||
final MyPostCard postcard;
|
||||
class MyPostcardPreviewView extends StatefulWidget {
|
||||
final MyPostCard postcard;
|
||||
|
||||
const MyPostcardPreviewView({
|
||||
super.key,
|
||||
required this.postcard,
|
||||
});
|
||||
const MyPostcardPreviewView({
|
||||
super.key,
|
||||
required this.postcard,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MyPostcardPreviewView> createState() => _MyPostcardPreviewViewState();
|
||||
}
|
||||
@override
|
||||
State<MyPostcardPreviewView> createState() => _MyPostcardPreviewViewState();
|
||||
}
|
||||
|
||||
class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
|
||||
bool showBack = false;
|
||||
class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
|
||||
bool showBack = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CommonAppBar(
|
||||
isWhiteLogo: false,
|
||||
isProfilePage: false,
|
||||
showDivider: true,
|
||||
),
|
||||
backWidget(context, "Preview", Colors.black),
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CommonAppBar(
|
||||
isWhiteLogo: false,
|
||||
isProfilePage: false,
|
||||
showDivider: true,
|
||||
),
|
||||
backWidget(context, "Preview", Colors.black),
|
||||
|
||||
SizedBox(height: 29.h),
|
||||
// Postcard Number with Action Icons
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
child: Row(
|
||||
children: [
|
||||
/// PC Number (takes only available space)
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.postcard.pcNumber,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
SizedBox(height: 29.h),
|
||||
// Postcard Number with Action Icons
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
child: Row(
|
||||
children: [
|
||||
/// PC Number (takes only available space)
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.postcard.pcNumber,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(width: 12.w),
|
||||
SizedBox(width: 12.w),
|
||||
|
||||
/// Action Icons
|
||||
Row(
|
||||
/// Action Icons
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// Delete functionality
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/delete_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final result = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => EditPostcardBloc(),
|
||||
child: EditPostcardView(myPostCard: widget.postcard),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
// ignore: use_build_context_synchronously
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshOrderPostCards(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/edit_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// Send functionality
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/send_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// Flip buttons
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// Delete functionality
|
||||
setState(() {
|
||||
showBack = false;
|
||||
});
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/delete_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_back,
|
||||
color: !showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Text(
|
||||
'Flip',
|
||||
style: GoogleFonts.poppins(
|
||||
color: !showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// Edit functionality
|
||||
setState(() {
|
||||
showBack = true;
|
||||
});
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/edit_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// Send functionality
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/send_icon.png',
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Flip',
|
||||
style: GoogleFonts.poppins(
|
||||
color: showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Icon(
|
||||
Icons.arrow_forward,
|
||||
color: showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// Flip buttons
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
showBack = false;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_back,
|
||||
color: !showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Text(
|
||||
'Flip',
|
||||
style: GoogleFonts.poppins(
|
||||
color: !showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
showBack = true;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Flip',
|
||||
style: GoogleFonts.poppins(
|
||||
color: showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6.w),
|
||||
Icon(
|
||||
Icons.arrow_forward,
|
||||
color: showBack ? Colors.grey[400] : const Color(0xffF95F62),
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 40.h),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: showBack
|
||||
? BackCardWidget(
|
||||
key: const ValueKey('back'),
|
||||
message: widget.postcard.pcContent,
|
||||
city: widget.postcard.cityName,
|
||||
state: widget.postcard.stateName,
|
||||
country: widget.postcard.countryName,
|
||||
address: widget.postcard.address1,
|
||||
name: widget.postcard.fullname,
|
||||
pincode: widget.postcard.zipCode,
|
||||
)
|
||||
: FrontCardWidget(
|
||||
key: const ValueKey('front'),
|
||||
imageUrl:
|
||||
'${ApiUrls.baseUrl}${widget.postcard.pcImagePath}',
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 40.h),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: showBack
|
||||
? BackCardWidget(
|
||||
key: const ValueKey('back'),
|
||||
message: widget.postcard.pcContent,
|
||||
city: widget.postcard.cityName,
|
||||
state: widget.postcard.stateName,
|
||||
country: widget.postcard.countryName,
|
||||
address: widget.postcard.address1,
|
||||
name: widget.postcard.fullname,
|
||||
pincode: widget.postcard.zipCode,
|
||||
)
|
||||
: FrontCardWidget(
|
||||
key: const ValueKey('front'),
|
||||
imageUrl:
|
||||
'${ApiUrls.baseUrl}${widget.postcard.pcImagePath}',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.only(top: 40.h),
|
||||
// child: AnimatedSwitcher(
|
||||
// duration: const Duration(milliseconds: 400),
|
||||
// transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
// return FadeTransition(
|
||||
// opacity: animation,
|
||||
// child: child,
|
||||
// );
|
||||
// },
|
||||
// child: showBack ? _buildBackSide() : _buildFrontSide(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
SizedBox(height: 40.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFrontSide() {
|
||||
return Container(
|
||||
key: const ValueKey('front'),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5, // Standard postcard ratio
|
||||
child: Image.network(
|
||||
'${ApiUrls.baseUrl}${widget.postcard.pcImagePath}',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: const Color(0xffF95F62),
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported,
|
||||
size: 60,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBackSide() {
|
||||
return Container(
|
||||
key: const ValueKey('back'),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xffE2D6C2),
|
||||
Color(0xffFFF5E6),
|
||||
Color(0xffFFF5E6),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: const Color(0xff000000).withOpacity(0.12),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Row(
|
||||
children: [
|
||||
// ================= LEFT SIDE =================
|
||||
Expanded(
|
||||
flex: 55,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Logo
|
||||
Image.asset(
|
||||
'assets/logo/logo_city_cards.png',
|
||||
height: 24.h, // adjust as needed
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
'POSTCARD',
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 6.sp,
|
||||
letterSpacing: 1.4,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 14.h),
|
||||
|
||||
// Message label
|
||||
Text(
|
||||
'MESSAGE PREVIEW',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 6.sp,
|
||||
letterSpacing: 1.4,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
|
||||
// Message text
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
widget.postcard.pcContent,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 10.h),
|
||||
|
||||
// Footer
|
||||
Text(
|
||||
'CityCards.co',
|
||||
style: TextStyle(
|
||||
color: const Color(0xffF95F62),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= DIVIDER =================
|
||||
Container(
|
||||
width: 4,
|
||||
margin: EdgeInsets.symmetric(vertical: 14.h),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.05),
|
||||
Colors.black.withOpacity(0.30),
|
||||
Colors.black.withOpacity(0.05),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= RIGHT SIDE =================
|
||||
Expanded(
|
||||
flex: 45,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
|
||||
// Address with BORDER
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(4.w),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// ADDRESS label
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'ADDRESS',
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 7.5.sp,
|
||||
letterSpacing: 1.6,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 6.h),
|
||||
|
||||
// Address line 1
|
||||
Text(
|
||||
'${widget.postcard.cityName},',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 6.h),
|
||||
|
||||
// State
|
||||
Text(
|
||||
'${widget.postcard.stateName},',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
// Country
|
||||
Text(
|
||||
widget.postcard.countryName,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.only(top: 40.h),
|
||||
// child: AnimatedSwitcher(
|
||||
// duration: const Duration(milliseconds: 400),
|
||||
// transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
// return FadeTransition(
|
||||
// opacity: animation,
|
||||
// child: child,
|
||||
// );
|
||||
// },
|
||||
// child: showBack ? _buildBackSide() : _buildFrontSide(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
SizedBox(height: 40.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFrontSide() {
|
||||
return Container(
|
||||
key: const ValueKey('front'),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5, // Standard postcard ratio
|
||||
child: Image.network(
|
||||
'${ApiUrls.baseUrl}${widget.postcard.pcImagePath}',
|
||||
fit: BoxFit.cover,
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: const Color(0xffF95F62),
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[300],
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.image_not_supported,
|
||||
size: 60,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBackSide() {
|
||||
return Container(
|
||||
key: const ValueKey('back'),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xffE2D6C2),
|
||||
Color(0xffFFF5E6),
|
||||
Color(0xffFFF5E6),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: const Color(0xff000000).withOpacity(0.12),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Row(
|
||||
children: [
|
||||
// ================= LEFT SIDE =================
|
||||
Expanded(
|
||||
flex: 55,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Logo
|
||||
Image.asset(
|
||||
'assets/logo/logo_city_cards.png',
|
||||
height: 24.h, // adjust as needed
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
'POSTCARD',
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 6.sp,
|
||||
letterSpacing: 1.4,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 14.h),
|
||||
|
||||
// Message label
|
||||
Text(
|
||||
'MESSAGE PREVIEW',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 6.sp,
|
||||
letterSpacing: 1.4,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
|
||||
// Message text
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
widget.postcard.pcContent,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 10.h),
|
||||
|
||||
// Footer
|
||||
Text(
|
||||
'CityCards.co',
|
||||
style: TextStyle(
|
||||
color: const Color(0xffF95F62),
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= DIVIDER =================
|
||||
Container(
|
||||
width: 4,
|
||||
margin: EdgeInsets.symmetric(vertical: 14.h),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.05),
|
||||
Colors.black.withOpacity(0.30),
|
||||
Colors.black.withOpacity(0.05),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= RIGHT SIDE =================
|
||||
Expanded(
|
||||
flex: 45,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
|
||||
// Address with BORDER
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(4.w),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// ADDRESS label
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'ADDRESS',
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 7.5.sp,
|
||||
letterSpacing: 1.6,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 6.h),
|
||||
|
||||
// Address line 1
|
||||
Text(
|
||||
'${widget.postcard.cityName},',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 6.h),
|
||||
|
||||
// State
|
||||
Text(
|
||||
'${widget.postcard.stateName},',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
// Country
|
||||
Text(
|
||||
widget.postcard.countryName,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 13.sp,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ class _MyPostCardsViewState extends State<MyPostCardsView> {
|
||||
|
||||
// Handle loaded state
|
||||
if (state is MyPostCardLoaded) {
|
||||
final isDraftsEmpty = state.draftPostCards.isEmpty;
|
||||
final isOrdersEmpty = state.orderPostCards.isEmpty;
|
||||
final isDraftsEmpty = state.allDraftPostCards.isEmpty;
|
||||
final isOrdersEmpty = state.allOrderPostCards.isEmpty;
|
||||
|
||||
developer.log('📊 Loaded - Drafts: ${state.draftPostCards.length}, Orders: ${state.orderPostCards.length}', name: 'MyPostCardsView');
|
||||
developer.log('🔄 Loading - Drafts: ${state.isDraftLoading}, Orders: ${state.isOrderLoading}', name: 'MyPostCardsView');
|
||||
|
||||
@@ -433,6 +433,9 @@ class _PostcardCheckoutPageViewState extends State<PostcardCheckoutPageView> {
|
||||
color: checkoutState.isLoading
|
||||
? Colors.grey
|
||||
: const Color(0xffF95F62),
|
||||
decoration:TextDecoration.underline,
|
||||
decorationColor: const Color(0xffF95F62),
|
||||
decorationThickness: 2 ,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@@ -208,12 +209,15 @@ class _PostcardPurchaseFormPageViewState extends State<PostcardPurchaseFormPageV
|
||||
hint: "eg: Jay@gmail.com",
|
||||
controller: _emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
isEmail: true,
|
||||
),
|
||||
_buildInputField(
|
||||
label: "Phone number",
|
||||
hint: "eg: +91 9999 999 999",
|
||||
hint: "eg: 9999 999 999",
|
||||
controller: _phoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 10,
|
||||
isMobileNumber: true,
|
||||
),
|
||||
_buildInputField(
|
||||
label: "Address",
|
||||
@@ -240,6 +244,7 @@ class _PostcardPurchaseFormPageViewState extends State<PostcardPurchaseFormPageV
|
||||
hint: "Enter the Zip Code you reside in",
|
||||
controller: _zipCodeController,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 6,
|
||||
),
|
||||
_buildDropdownField(
|
||||
label: "Country",
|
||||
@@ -348,6 +353,10 @@ class _PostcardPurchaseFormPageViewState extends State<PostcardPurchaseFormPageV
|
||||
required TextEditingController controller,
|
||||
IconData? icon,
|
||||
TextInputType? keyboardType,
|
||||
int? maxLength,
|
||||
bool isEmail = false,
|
||||
bool isMobileNumber = false, // ✅ NEW
|
||||
int mobileLength = 10, // ✅ NEW (default 10)
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 18),
|
||||
@@ -365,9 +374,17 @@ class _PostcardPurchaseFormPageViewState extends State<PostcardPurchaseFormPageV
|
||||
const SizedBox(height: 6),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: keyboardType,
|
||||
keyboardType: keyboardType ??
|
||||
(isMobileNumber
|
||||
? TextInputType.phone
|
||||
: TextInputType.text),
|
||||
maxLength: maxLength ?? (isMobileNumber ? mobileLength : null),
|
||||
inputFormatters: isMobileNumber
|
||||
? [FilteringTextInputFormatter.digitsOnly]
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
counterText: "",
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
color: const Color(0xff999999),
|
||||
fontSize: 14.sp,
|
||||
@@ -395,12 +412,28 @@ class _PostcardPurchaseFormPageViewState extends State<PostcardPurchaseFormPageV
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Please enter $label';
|
||||
}
|
||||
if (label == "Email ID" && !value.contains('@')) {
|
||||
return 'Please enter a valid email';
|
||||
|
||||
if (isEmail) {
|
||||
final emailRegex = RegExp(
|
||||
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$',
|
||||
);
|
||||
if (!emailRegex.hasMatch(value.trim())) {
|
||||
return 'Please enter a valid email address';
|
||||
}
|
||||
}
|
||||
|
||||
if (isMobileNumber) {
|
||||
if (!RegExp(r'^\d+$').hasMatch(value)) {
|
||||
return 'Only numbers are allowed';
|
||||
}
|
||||
if (value.length != mobileLength) {
|
||||
return 'Mobile number must be $mobileLength digits';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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) {
|
||||
@@ -274,4 +273,4 @@ class _EditYourdetailsState extends State<EditYourdetails> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +496,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
hint: "Enter your phone number",
|
||||
controller: phoneController,
|
||||
enabled: !isLoading,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 10,
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Phone number is required';
|
||||
@@ -679,6 +681,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
hint: "Enter the ZIP code you reside in",
|
||||
controller: zipCodeController,
|
||||
enabled: !isLoading,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 6,
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -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