Compare commits
3 Commits
b08e2699e9
...
40f0ed3a52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40f0ed3a52 | ||
|
|
53264619a8 | ||
|
|
68c3f28d76 |
Binary file not shown.
|
Before Width: | Height: | Size: 749 KiB After Width: | Height: | Size: 1.2 MiB |
@@ -186,7 +186,7 @@ class AppRouter {
|
||||
final attractionID = settings.arguments as int;
|
||||
return MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return PassAttractionDetailsView(attractionId: attractionID);
|
||||
return AttractionDetailsView(attractionId: attractionID);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -201,9 +201,7 @@ class AppRouter {
|
||||
final bookingId = settings.arguments as int; // or String
|
||||
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => CheckoutView(
|
||||
bookingId: bookingId,
|
||||
),
|
||||
builder: (_) => CheckoutView(bookingId: bookingId),
|
||||
);
|
||||
|
||||
|
||||
@@ -239,9 +237,7 @@ class AppRouter {
|
||||
|
||||
return MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return AddDetailsView(
|
||||
bookingId: bookingId,
|
||||
);
|
||||
return AddDetailsView(bookingId: bookingId);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ class RouteConstants {
|
||||
static const String magicItineraryEmptyScreen = '/magicItineraryEmptyScreen';
|
||||
static const String itineraryCreationStart = '/itineraryCreationStart';
|
||||
static const String itineraryCreation = '/itineraryCreation';
|
||||
static const String magicItineraryFilledScreen = "/magicItineraryFilledScreen";
|
||||
static const String magicItineraryFilledScreen =
|
||||
"/magicItineraryFilledScreen";
|
||||
|
||||
/**************************** ESIM Page *****************************************/
|
||||
|
||||
@@ -42,8 +43,8 @@ class RouteConstants {
|
||||
|
||||
/**************************** By Pass Page Page *****************************************/
|
||||
|
||||
static const String buyPass ='/buyPass';
|
||||
static const String checkout ='/checkout';
|
||||
static const String buyPass = '/buyPass';
|
||||
static const String checkout = '/checkout';
|
||||
static const String searchOffer = '/searchOffer';
|
||||
static const String searchPassOffer = '/searchPassOffer';
|
||||
static const String createAcct = '/createAcct';
|
||||
@@ -59,4 +60,5 @@ class RouteConstants {
|
||||
static const String qrPage = '/qrPage';
|
||||
static const String makeBooking = '/makeBooking';
|
||||
static const String bookingSuccessful = '/bookingSuccessful';
|
||||
static const String editPostCard = '/editPostCard';
|
||||
}
|
||||
|
||||
@@ -23,8 +23,13 @@ class CurrentLocationSelection extends StatefulWidget {
|
||||
class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
LatLng? _currentLatLng;
|
||||
bool loading = false;
|
||||
|
||||
Future<void> _getCurrentLocation() async {
|
||||
try {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
LocationPermission permission = await Geolocator.requestPermission();
|
||||
|
||||
if (permission == LocationPermission.denied ||
|
||||
@@ -47,6 +52,18 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
|
||||
});
|
||||
|
||||
await _getAddressFromLatLng(lat, lng);
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getAddressFromLatLng(double lat, double lng) async {
|
||||
@@ -57,7 +74,6 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
|
||||
final place = placemarks.first;
|
||||
|
||||
final address = [
|
||||
place.name,
|
||||
place.street,
|
||||
place.subLocality,
|
||||
place.locality,
|
||||
@@ -133,7 +149,13 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
|
||||
child: SizedBox(
|
||||
height: 250.h,
|
||||
width: double.infinity,
|
||||
child: FlutterMap(
|
||||
child: loading == true
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFFF95F62),
|
||||
),
|
||||
)
|
||||
: FlutterMap(
|
||||
options: MapOptions(
|
||||
initialCenter: _currentLatLng!,
|
||||
initialZoom: 15,
|
||||
@@ -143,7 +165,8 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
|
||||
urlTemplate:
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
subdomains: const ['a', 'b', 'c'],
|
||||
userAgentPackageName: 'com.citycards.customer',
|
||||
userAgentPackageName:
|
||||
'com.citycards.customer',
|
||||
),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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://testingapi.citycards.betadelivery.com";// Test API
|
||||
static const baseUrl = "https://uatapi.citycard.betadelivery.com";// Production Lvl API
|
||||
|
||||
static const refreshToken = "$baseUrl/auth/refresh";
|
||||
|
||||
@@ -25,8 +25,11 @@ class ApiUrls {
|
||||
static const passDetails = "$baseUrl/mobile/passes";
|
||||
static const myPassesCart = "$baseUrl/mobile/passes/cart/passes";
|
||||
|
||||
static const editPostcard = "$baseUrl/mobile/postcards";
|
||||
|
||||
static const myItineraries = "$baseUrl/mobile/itinerary/all-initineraries";
|
||||
static const getItineraryCities = "$baseUrl/mobile/itinerary/cities-with-icons";
|
||||
static const getItineraryCities =
|
||||
"$baseUrl/mobile/itinerary/cities-with-icons";
|
||||
|
||||
//Post Apis
|
||||
static const createAccount = "$baseUrl/mobile/user/register";
|
||||
|
||||
@@ -185,6 +185,27 @@ class NetworkApiService {
|
||||
}
|
||||
}
|
||||
|
||||
// ================= DELETE =================
|
||||
Future<Response> deleteApi({
|
||||
required String url,
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.delete(
|
||||
url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
throw _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================= REFRESH TOKEN =================
|
||||
Future<bool> _refreshToken() async {
|
||||
try {
|
||||
|
||||
26
lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart
Normal file
26
lib/postcard/blocs/edit_postcard/edit_postcard_bloc.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:citycards_customer/postcard/models/my_postcard_model.dart';
|
||||
import 'package:citycards_customer/postcard/repository/my_postcard_repository.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'edit_postcard_event.dart';
|
||||
part 'edit_postcard_state.dart';
|
||||
|
||||
class EditPostcardBloc extends Bloc<EditPostcardEvent, EditPostcardState> {
|
||||
EditPostcardBloc() : super(EditPostcardInitial()) {
|
||||
on<EditPostCard>((event, emit) async {
|
||||
try {
|
||||
emit(EditPostcardLoading());
|
||||
await MyPostCardsRepository().editMyPostCards(
|
||||
postcard: event.myPostCard,
|
||||
);
|
||||
log("Edit PostCard Successfully");
|
||||
emit(EditPostcardSuccessfull());
|
||||
} catch (e) {
|
||||
emit(EditPostcardError(error: "Failed to edit postcard"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
13
lib/postcard/blocs/edit_postcard/edit_postcard_event.dart
Normal file
13
lib/postcard/blocs/edit_postcard/edit_postcard_event.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
part of 'edit_postcard_bloc.dart';
|
||||
|
||||
class EditPostcardEvent extends Equatable {
|
||||
const EditPostcardEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class EditPostCard extends EditPostcardEvent {
|
||||
final MyPostCard myPostCard;
|
||||
const EditPostCard({required this.myPostCard});
|
||||
}
|
||||
19
lib/postcard/blocs/edit_postcard/edit_postcard_state.dart
Normal file
19
lib/postcard/blocs/edit_postcard/edit_postcard_state.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
part of 'edit_postcard_bloc.dart';
|
||||
|
||||
class EditPostcardState extends Equatable {
|
||||
const EditPostcardState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class EditPostcardInitial extends EditPostcardState {}
|
||||
|
||||
class EditPostcardLoading extends EditPostcardState {}
|
||||
|
||||
class EditPostcardSuccessfull extends EditPostcardState {}
|
||||
|
||||
class EditPostcardError extends EditPostcardState {
|
||||
final String error;
|
||||
const EditPostcardError({required this.error});
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:citycards_customer/localPreference/local_preference.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:developer' as developer;
|
||||
import '../../models/my_postcard_model.dart';
|
||||
import '../../repository/my_postcard_repository.dart';
|
||||
import 'my_postcard_event.dart';
|
||||
import 'my_postcard_state.dart';
|
||||
@@ -8,12 +11,14 @@ import 'my_postcard_state.dart';
|
||||
class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
final MyPostCardsRepository repository;
|
||||
|
||||
MyPostCardBloc({required this.repository}) : super(const MyPostCardInitial()) {
|
||||
MyPostCardBloc({required this.repository})
|
||||
: super(const MyPostCardInitial()) {
|
||||
on<CheckLoginStatus>(_onCheckLoginStatus);
|
||||
on<FetchDraftPostCards>(_onFetchDraftPostCards);
|
||||
on<FetchOrderPostCards>(_onFetchOrderPostCards);
|
||||
on<RefreshDraftPostCards>(_onRefreshDraftPostCards);
|
||||
on<RefreshOrderPostCards>(_onRefreshOrderPostCards);
|
||||
on<DeleteDraftPostCards>(_onDeletePostCard);
|
||||
}
|
||||
|
||||
/// Handle checking login status
|
||||
@@ -29,20 +34,28 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
developer.log('📊 Login status: $isLogin', name: 'MyPostCardBloc');
|
||||
|
||||
if (isLogin) {
|
||||
developer.log('✅ User is logged in - initializing state', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'✅ User is logged in - initializing state',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
// User is logged in, initialize with empty lists and loading states
|
||||
emit(const MyPostCardLoaded(
|
||||
emit(
|
||||
const MyPostCardLoaded(
|
||||
draftPostCards: [],
|
||||
orderPostCards: [],
|
||||
isDraftLoading: true,
|
||||
isOrderLoading: true,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
// Fetch both drafts and orders
|
||||
add(const FetchDraftPostCards());
|
||||
add(const FetchOrderPostCards());
|
||||
} else {
|
||||
developer.log('❌ User is NOT logged in - emitting MyPostCardNotLoggedIn', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'❌ User is NOT logged in - emitting MyPostCardNotLoggedIn',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
// User is not logged in
|
||||
emit(const MyPostCardNotLoggedIn());
|
||||
}
|
||||
@@ -69,23 +82,33 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
try {
|
||||
final draftPostCards = await repository.fetchMyPostCards(type: 'draft');
|
||||
developer.log('✅ Draft postcards fetched: ${draftPostCards.length} items', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'✅ Draft postcards fetched: ${draftPostCards.length} items',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
// Update with fetched drafts
|
||||
emit((state as MyPostCardLoaded).copyWith(
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(
|
||||
draftPostCards: draftPostCards,
|
||||
isDraftLoading: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
} else {
|
||||
developer.log('⚠️ State is not MyPostCardLoaded, creating new state', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'⚠️ State is not MyPostCardLoaded, creating new state',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
// Fallback: create new loaded state (shouldn't normally happen)
|
||||
emit(MyPostCardLoaded(
|
||||
emit(
|
||||
MyPostCardLoaded(
|
||||
draftPostCards: draftPostCards,
|
||||
orderPostCards: const [],
|
||||
isDraftLoading: false,
|
||||
isOrderLoading: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log('❌ Error fetching drafts: $error', name: 'MyPostCardBloc');
|
||||
@@ -95,10 +118,29 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
}
|
||||
|
||||
// Emit error state
|
||||
emit(MyPostCardError(
|
||||
errorMessage: error.toString(),
|
||||
errorType: 'draft',
|
||||
));
|
||||
emit(MyPostCardError(errorMessage: error.toString(), errorType: 'draft'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onDeletePostCard(
|
||||
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);
|
||||
emit(
|
||||
currentState.copyWith(draftPostCards: items, isDeleteLoading: false),
|
||||
);
|
||||
} catch (e) {
|
||||
log("Erro - $e");
|
||||
emit(currentState.copyWith(isDeleteLoading: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,23 +160,33 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
|
||||
try {
|
||||
final orderPostCards = await repository.fetchMyPostCards(type: 'orders');
|
||||
developer.log('✅ Order postcards fetched: ${orderPostCards.length} items', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'✅ Order postcards fetched: ${orderPostCards.length} items',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
// Update with fetched orders
|
||||
emit((state as MyPostCardLoaded).copyWith(
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(
|
||||
orderPostCards: orderPostCards,
|
||||
isOrderLoading: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
} else {
|
||||
developer.log('⚠️ State is not MyPostCardLoaded, creating new state', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'⚠️ State is not MyPostCardLoaded, creating new state',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
// Fallback: create new loaded state (shouldn't normally happen)
|
||||
emit(MyPostCardLoaded(
|
||||
emit(
|
||||
MyPostCardLoaded(
|
||||
draftPostCards: const [],
|
||||
orderPostCards: orderPostCards,
|
||||
isDraftLoading: false,
|
||||
isOrderLoading: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log('❌ Error fetching orders: $error', name: 'MyPostCardBloc');
|
||||
@@ -144,10 +196,7 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
}
|
||||
|
||||
// Emit error state
|
||||
emit(MyPostCardError(
|
||||
errorMessage: error.toString(),
|
||||
errorType: 'order',
|
||||
));
|
||||
emit(MyPostCardError(errorMessage: error.toString(), errorType: 'order'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,19 +208,22 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
developer.log('🔄 Refreshing draft postcards...', name: 'MyPostCardBloc');
|
||||
try {
|
||||
final draftPostCards = await repository.fetchMyPostCards(type: 'draft');
|
||||
developer.log('✅ Draft postcards refreshed: ${draftPostCards.length} items', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'✅ Draft postcards refreshed: ${draftPostCards.length} items',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
emit((state as MyPostCardLoaded).copyWith(
|
||||
draftPostCards: draftPostCards,
|
||||
));
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(draftPostCards: draftPostCards),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log('❌ Error refreshing drafts: $error', name: 'MyPostCardBloc');
|
||||
emit(MyPostCardError(
|
||||
errorMessage: error.toString(),
|
||||
errorType: 'draft',
|
||||
));
|
||||
developer.log(
|
||||
'❌ Error refreshing drafts: $error',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
emit(MyPostCardError(errorMessage: error.toString(), errorType: 'draft'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,19 +235,22 @@ class MyPostCardBloc extends Bloc<MyPostCardEvent, MyPostCardState> {
|
||||
developer.log('🔄 Refreshing order postcards...', name: 'MyPostCardBloc');
|
||||
try {
|
||||
final orderPostCards = await repository.fetchMyPostCards(type: 'orders');
|
||||
developer.log('✅ Order postcards refreshed: ${orderPostCards.length} items', name: 'MyPostCardBloc');
|
||||
developer.log(
|
||||
'✅ Order postcards refreshed: ${orderPostCards.length} items',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
|
||||
if (state is MyPostCardLoaded) {
|
||||
emit((state as MyPostCardLoaded).copyWith(
|
||||
orderPostCards: orderPostCards,
|
||||
));
|
||||
emit(
|
||||
(state as MyPostCardLoaded).copyWith(orderPostCards: orderPostCards),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
developer.log('❌ Error refreshing orders: $error', name: 'MyPostCardBloc');
|
||||
emit(MyPostCardError(
|
||||
errorMessage: error.toString(),
|
||||
errorType: 'order',
|
||||
));
|
||||
developer.log(
|
||||
'❌ Error refreshing orders: $error',
|
||||
name: 'MyPostCardBloc',
|
||||
);
|
||||
emit(MyPostCardError(errorMessage: error.toString(), errorType: 'order'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ class FetchDraftPostCards extends MyPostCardEvent {
|
||||
const FetchDraftPostCards();
|
||||
}
|
||||
|
||||
class DeleteDraftPostCards extends MyPostCardEvent {
|
||||
final int id;
|
||||
const DeleteDraftPostCards({required this.id});
|
||||
}
|
||||
|
||||
/// Event to fetch order postcards
|
||||
class FetchOrderPostCards extends MyPostCardEvent {
|
||||
const FetchOrderPostCards();
|
||||
|
||||
@@ -29,12 +29,14 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
final List<MyPostCard> orderPostCards;
|
||||
final bool isDraftLoading;
|
||||
final bool isOrderLoading;
|
||||
final bool isDeleteLoading;
|
||||
|
||||
const MyPostCardLoaded({
|
||||
required this.draftPostCards,
|
||||
required this.orderPostCards,
|
||||
this.isDraftLoading = false,
|
||||
this.isOrderLoading = false,
|
||||
this.isDeleteLoading = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -43,6 +45,7 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
orderPostCards,
|
||||
isDraftLoading,
|
||||
isOrderLoading,
|
||||
isDeleteLoading,
|
||||
];
|
||||
|
||||
/// Helper method to create a copy with updated values
|
||||
@@ -51,12 +54,14 @@ class MyPostCardLoaded extends MyPostCardState {
|
||||
List<MyPostCard>? orderPostCards,
|
||||
bool? isDraftLoading,
|
||||
bool? isOrderLoading,
|
||||
bool? isDeleteLoading,
|
||||
}) {
|
||||
return MyPostCardLoaded(
|
||||
draftPostCards: draftPostCards ?? this.draftPostCards,
|
||||
orderPostCards: orderPostCards ?? this.orderPostCards,
|
||||
isDraftLoading: isDraftLoading ?? this.isDraftLoading,
|
||||
isOrderLoading: isOrderLoading ?? this.isOrderLoading,
|
||||
isDeleteLoading: isDeleteLoading ?? this.isDeleteLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,10 +71,7 @@ class MyPostCardError extends MyPostCardState {
|
||||
final String errorMessage;
|
||||
final String errorType; // 'draft' or 'order'
|
||||
|
||||
const MyPostCardError({
|
||||
required this.errorMessage,
|
||||
required this.errorType,
|
||||
});
|
||||
const MyPostCardError({required this.errorMessage, required this.errorType});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [errorMessage, errorType];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:developer';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../repository/postcard_checkout_repository.dart';
|
||||
import 'postcard_checkout_event.dart';
|
||||
@@ -14,33 +15,44 @@ class PostcardCheckoutBloc
|
||||
on<UpdateCheckoutDataEvent>(_onUpdateCheckoutData);
|
||||
on<SaveAsDraftEvent>(_onSaveAsDraft);
|
||||
on<SubmitPostcardEvent>(_onSubmitPostcard);
|
||||
on<ConfirmPaymentEvent>(_onConfirmPayment); // 🆕 NEW
|
||||
on<ConfirmPaymentEvent>(_onConfirmPayment);
|
||||
}
|
||||
|
||||
void _onUpdateAddress(
|
||||
UpdateAddressEvent event, Emitter<PostcardCheckoutState> emit) {
|
||||
emit(state.copyWith(
|
||||
UpdateAddressEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
countryName: event.countryName,
|
||||
cityName: event.cityName,
|
||||
stateName: event.stateName,
|
||||
zipCode: event.zipCode,
|
||||
address1: event.address1,
|
||||
address2: event.address2,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onUpdateContent(
|
||||
UpdatePostcardContentEvent event, Emitter<PostcardCheckoutState> emit) {
|
||||
emit(state.copyWith(
|
||||
UpdatePostcardContentEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
pcTitle: event.pcTitle,
|
||||
pcContent: event.pcContent,
|
||||
pcImageFile: event.pcImageFile,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onUpdateCheckoutData(
|
||||
UpdateCheckoutDataEvent event, Emitter<PostcardCheckoutState> emit) {
|
||||
emit(state.copyWith(
|
||||
UpdateCheckoutDataEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
countryName: event.countryName,
|
||||
cityName: event.cityName,
|
||||
stateName: event.stateName,
|
||||
@@ -61,15 +73,18 @@ class PostcardCheckoutBloc
|
||||
totalTaxAmount: event.totalTaxAmount,
|
||||
totalAmount: event.totalAmount,
|
||||
postcardId: event.postcardId,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSaveAsDraft(
|
||||
SaveAsDraftEvent event, Emitter<PostcardCheckoutState> emit) async {
|
||||
SaveAsDraftEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true, error: null, isSuccess: false));
|
||||
|
||||
try {
|
||||
// ⭐ Validate pcId exists
|
||||
// Validate pcId exists
|
||||
if (state.postcardId == null) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
@@ -79,9 +94,21 @@ class PostcardCheckoutBloc
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that image file exists before submitting
|
||||
if (state.pcImageFile == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
error: 'Please select a postcard image',
|
||||
isSuccess: false,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await repository.createPostCard(
|
||||
pcId: state.postcardId!,
|
||||
isDraft: true, // ⭐ Save as draft
|
||||
isDraft: true,
|
||||
);
|
||||
|
||||
// Extract order ID from response if available
|
||||
@@ -89,27 +116,33 @@ class PostcardCheckoutBloc
|
||||
response['order_id']?.toString() ??
|
||||
response['id']?.toString();
|
||||
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
isDraft: true,
|
||||
orderId: orderId,
|
||||
));
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
isSuccess: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSubmitPostcard(
|
||||
SubmitPostcardEvent event, Emitter<PostcardCheckoutState> emit) async {
|
||||
SubmitPostcardEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true, error: null, isSuccess: false));
|
||||
|
||||
try {
|
||||
// ⭐ Validate pcId exists
|
||||
// Validate pcId exists
|
||||
if (state.postcardId == null) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
@@ -119,65 +152,92 @@ class PostcardCheckoutBloc
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that image file exists before submitting
|
||||
if (state.pcImageFile == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
error: 'Please select a postcard image',
|
||||
isSuccess: false,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await repository.createPostCard(
|
||||
pcId: state.postcardId!,
|
||||
isDraft: false, // ⭐ Initiate payment
|
||||
isDraft: false,
|
||||
);
|
||||
|
||||
// Parse response from backend
|
||||
final postcardId = response['postcardId'] as int?;
|
||||
final clientSecret = response['clientSecret'] as String?;
|
||||
final status = response['status'] as String?;
|
||||
|
||||
// Extract order ID from response
|
||||
final orderId = response['orderId']?.toString() ??
|
||||
response['order_id']?.toString() ??
|
||||
response['id']?.toString();
|
||||
|
||||
// Validate clientSecret is present
|
||||
if (clientSecret == null || clientSecret.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
error: 'Payment initialization failed - no client secret received',
|
||||
isSuccess: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
// Emit success with clientSecret for payment processing
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
isSuccess: true,
|
||||
isDraft: false,
|
||||
postcardId: postcardId ?? state.postcardId,
|
||||
clientSecret: clientSecret,
|
||||
orderId: orderId,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
),
|
||||
);
|
||||
} catch (e, stack) {
|
||||
log("Payment Error: ${e.toString()}");
|
||||
log("Payment Stack: ${stack.toString()}");
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
isSuccess: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 🆕 Confirm payment after Stripe payment completes
|
||||
/// This should be called after Stripe payment succeeds or fails
|
||||
/// Confirm payment after Stripe payment completes
|
||||
Future<void> _onConfirmPayment(
|
||||
ConfirmPaymentEvent event, Emitter<PostcardCheckoutState> emit) async {
|
||||
|
||||
ConfirmPaymentEvent event,
|
||||
Emitter<PostcardCheckoutState> emit,
|
||||
) async {
|
||||
// Validate postcardId exists
|
||||
if (state.postcardId == null) {
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
confirmationError: 'Cannot confirm payment - postcard ID is missing',
|
||||
isConfirmingPayment: false,
|
||||
isPaymentConfirmed: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isConfirmingPayment: true,
|
||||
confirmationError: null,
|
||||
isPaymentConfirmed: false,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await repository.confirmPayment(
|
||||
@@ -187,17 +247,21 @@ class PostcardCheckoutBloc
|
||||
);
|
||||
|
||||
// Payment confirmation successful
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isConfirmingPayment: false,
|
||||
isPaymentConfirmed: true,
|
||||
confirmationError: null,
|
||||
));
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
emit(
|
||||
state.copyWith(
|
||||
isConfirmingPayment: false,
|
||||
isPaymentConfirmed: false,
|
||||
confirmationError: e.toString(),
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,4 +170,81 @@ class MyPostCard {
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
MyPostCard copyWith({
|
||||
int? id,
|
||||
int? userXid,
|
||||
String? pcTitle,
|
||||
String? pcNumber,
|
||||
String? cityName,
|
||||
DateTime? pcDatetime,
|
||||
String? pcContent,
|
||||
String? pcImagePath,
|
||||
bool? isForSelf,
|
||||
String? fullname,
|
||||
String? emailAddress,
|
||||
String? isdCode,
|
||||
String? mobileNumber,
|
||||
String? address1,
|
||||
String? address2,
|
||||
String? zipCode,
|
||||
String? stateName,
|
||||
String? countryName,
|
||||
String? orderStatus,
|
||||
double? baseAmount,
|
||||
int? couponXid,
|
||||
double? couponDiscountPercent,
|
||||
double? couponDiscountAmount,
|
||||
double? totalTaxAmount,
|
||||
double? totalAmount,
|
||||
bool? isPaid,
|
||||
String? paymentMode,
|
||||
String? paymentId,
|
||||
String? paymentStatus,
|
||||
String? paymentIntentId,
|
||||
bool? isDraft,
|
||||
DateTime? deliveredOn,
|
||||
bool? isActive,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return MyPostCard(
|
||||
id: id ?? this.id,
|
||||
userXid: userXid ?? this.userXid,
|
||||
pcTitle: pcTitle ?? this.pcTitle,
|
||||
pcNumber: pcNumber ?? this.pcNumber,
|
||||
cityName: cityName ?? this.cityName,
|
||||
pcDatetime: pcDatetime ?? this.pcDatetime,
|
||||
pcContent: pcContent ?? this.pcContent,
|
||||
pcImagePath: pcImagePath ?? this.pcImagePath,
|
||||
isForSelf: isForSelf ?? this.isForSelf,
|
||||
fullname: fullname ?? this.fullname,
|
||||
emailAddress: emailAddress ?? this.emailAddress,
|
||||
isdCode: isdCode ?? this.isdCode,
|
||||
mobileNumber: mobileNumber ?? this.mobileNumber,
|
||||
address1: address1 ?? this.address1,
|
||||
address2: address2 ?? this.address2,
|
||||
zipCode: zipCode ?? this.zipCode,
|
||||
stateName: stateName ?? this.stateName,
|
||||
countryName: countryName ?? this.countryName,
|
||||
orderStatus: orderStatus ?? this.orderStatus,
|
||||
baseAmount: baseAmount ?? this.baseAmount,
|
||||
couponXid: couponXid ?? this.couponXid,
|
||||
couponDiscountPercent:
|
||||
couponDiscountPercent ?? this.couponDiscountPercent,
|
||||
couponDiscountAmount: couponDiscountAmount ?? this.couponDiscountAmount,
|
||||
totalTaxAmount: totalTaxAmount ?? this.totalTaxAmount,
|
||||
totalAmount: totalAmount ?? this.totalAmount,
|
||||
isPaid: isPaid ?? this.isPaid,
|
||||
paymentMode: paymentMode ?? this.paymentMode,
|
||||
paymentId: paymentId ?? this.paymentId,
|
||||
paymentStatus: paymentStatus ?? this.paymentStatus,
|
||||
paymentIntentId: paymentIntentId ?? this.paymentIntentId,
|
||||
isDraft: isDraft ?? this.isDraft,
|
||||
deliveredOn: deliveredOn ?? this.deliveredOn,
|
||||
isActive: isActive ?? this.isActive,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../networkApiServices/network_api_services.dart';
|
||||
import '../../networkApiServices/api_urls.dart';
|
||||
import '../models/my_postcard_model.dart';
|
||||
@@ -13,8 +17,61 @@ class MyPostCardsRepository {
|
||||
url: '${ApiUrls.myPostCards}?type=$type',
|
||||
);
|
||||
|
||||
return (response.data as List)
|
||||
.map((e) => MyPostCard.fromJson(e))
|
||||
.toList();
|
||||
return (response.data as List).map((e) => MyPostCard.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<void> editMyPostCards({required MyPostCard postcard}) async {
|
||||
try {
|
||||
final formData = FormData();
|
||||
|
||||
formData.fields.addAll([
|
||||
MapEntry('countryName', postcard.countryName),
|
||||
MapEntry('cityName', postcard.cityName),
|
||||
MapEntry('stateName', postcard.stateName),
|
||||
MapEntry('zipCode', postcard.zipCode),
|
||||
MapEntry('pcTitle', postcard.pcTitle),
|
||||
MapEntry('pcContent', postcard.pcContent),
|
||||
MapEntry('pcNumber', postcard.pcNumber),
|
||||
MapEntry('pcDatetime', postcard.pcDatetime.toString()),
|
||||
MapEntry('fullname', postcard.fullname),
|
||||
MapEntry('isdCode', postcard.isdCode),
|
||||
]);
|
||||
|
||||
if (postcard.address1.isNotEmpty) {
|
||||
formData.fields.add(MapEntry('address1', postcard.address1));
|
||||
}
|
||||
|
||||
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,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
await _apiService.putApi(
|
||||
url: '${ApiUrls.editPostcard}/${postcard.id}',
|
||||
data: formData,
|
||||
);
|
||||
return;
|
||||
} catch (e, stack) {
|
||||
log("Edit PostCard Error - $e");
|
||||
log("Edit PostCard Error - $stack");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMyPostCards(int id) async {
|
||||
try {
|
||||
await _apiService.deleteApi(url: '${ApiUrls.editPostcard}/$id');
|
||||
return;
|
||||
} catch (e, stack) {
|
||||
log("Delete PostCard Error - $e");
|
||||
log("Delete PostCard Error - $stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
377
lib/postcard/views/edit_postcard_view.dart
Normal file
377
lib/postcard/views/edit_postcard_view.dart
Normal file
@@ -0,0 +1,377 @@
|
||||
import 'package:citycards_customer/postcard/blocs/edit_postcard/edit_postcard_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';
|
||||
import 'package:flutter/widgets.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/custom_text.dart';
|
||||
import '../../networkApiServices/api_urls.dart';
|
||||
import '../widgets/edit_post_card/edit_message.dart';
|
||||
import '../widgets/edit_post_card/your_details.dart';
|
||||
|
||||
class EditPostcardView extends StatefulWidget {
|
||||
final MyPostCard myPostCard;
|
||||
const EditPostcardView({super.key, required this.myPostCard});
|
||||
|
||||
@override
|
||||
State<EditPostcardView> createState() => _EditPostcardViewState();
|
||||
}
|
||||
|
||||
class _EditPostcardViewState extends State<EditPostcardView> {
|
||||
MyPostCard? postCard;
|
||||
final EditPostcardBloc editPostcardBloc = EditPostcardBloc();
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final _fullNameController = TextEditingController();
|
||||
final _addressController = TextEditingController();
|
||||
final _cityController = TextEditingController();
|
||||
final _zipCodeController = TextEditingController();
|
||||
|
||||
String? _selectedCountry;
|
||||
String? _selectedState;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fullNameController.dispose();
|
||||
_addressController.dispose();
|
||||
_cityController.dispose();
|
||||
_zipCodeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
setState(() {
|
||||
postCard = widget.myPostCard;
|
||||
_fullNameController.text = widget.myPostCard.fullname;
|
||||
_addressController.text = widget.myPostCard.address1;
|
||||
_cityController.text = widget.myPostCard.cityName;
|
||||
_zipCodeController.text = widget.myPostCard.zipCode;
|
||||
_selectedCountry = widget.myPostCard.countryName;
|
||||
_selectedState = widget.myPostCard.stateName;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
backgroundColor: Colors.white,
|
||||
body: BlocConsumer<EditPostcardBloc, EditPostcardState>(
|
||||
bloc: editPostcardBloc,
|
||||
listener: (ctxx, state) async {
|
||||
if (state is EditPostcardSuccessfull) {
|
||||
if (Navigator.canPop(ctxx)) {
|
||||
Navigator.pop(ctxx, true);
|
||||
}
|
||||
} else if (state is EditPostcardError) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(state.error)));
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CommonAppBar(
|
||||
isWhiteLogo: false,
|
||||
isProfilePage: false,
|
||||
showCart: true,
|
||||
showDivider: true,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Icon(Icons.arrow_back),
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
CustomText(text: "Edit Postcard", size: 12.sp),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Text(
|
||||
"Upload Image",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF212121),
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
"Edit your own unique postcards by uploading images that capture your unforgettable moments.",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF000000).withValues(alpha: 0.6),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
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}',
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
imageButton(
|
||||
title: 'Upload Again',
|
||||
icon: Icons.refresh,
|
||||
width: size.width,
|
||||
),
|
||||
imageButton(
|
||||
title: 'Edit Filters',
|
||||
width: size.width,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Text(
|
||||
"Edit message",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF212121),
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
"Edit your own unique postcards to cherish your unforgettable moments.",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF000000).withValues(alpha: 0.6),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
EditMessage(
|
||||
text: postCard!.pcContent,
|
||||
onChange: (message, font) {
|
||||
postCard = postCard!.copyWith(
|
||||
pcContent: getFormattedMessage(message, font),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: EditYourdetails(
|
||||
fullNameController: _fullNameController,
|
||||
addressController: _addressController,
|
||||
cityController: _cityController,
|
||||
zipCodeController: _zipCodeController,
|
||||
selectedCountry: _selectedCountry ?? "",
|
||||
selectedState: _selectedState ?? "",
|
||||
formKey: _formKey,
|
||||
selectState: (String p1) {
|
||||
setState(() {
|
||||
_selectedState = p1;
|
||||
});
|
||||
},
|
||||
selectCountry: (String p1) {
|
||||
setState(() {
|
||||
_selectedCountry = p1;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Next Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
postCard = postCard!.copyWith(
|
||||
fullname: _fullNameController.text,
|
||||
address1: _addressController.text,
|
||||
cityName: _cityController.text,
|
||||
zipCode: _zipCodeController.text,
|
||||
stateName: _selectedState,
|
||||
countryName: _selectedCountry,
|
||||
);
|
||||
editPostcardBloc.add(
|
||||
EditPostCard(myPostCard: postCard!),
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Next",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: state is EditPostcardSuccessfull
|
||||
? Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0XFFF95F62),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget imageButton({
|
||||
Function()? onPressed,
|
||||
required String title,
|
||||
IconData? icon,
|
||||
required double width,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {},
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
|
||||
side: const BorderSide(color: Color(0xffF95F62)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Color(0xffF95F62),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
),
|
||||
SizedBox(width: icon != null ? 8 : 0),
|
||||
icon != null ? Icon(icon, color: Color(0xffF95F62)) : SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getFormattedMessage(String message, String selectedFont) {
|
||||
if (message.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (selectedFont.isEmpty) {
|
||||
// Default font (Poppins)
|
||||
return '<span style="font-family: Poppins;">$message</span>';
|
||||
}
|
||||
|
||||
return '<span style="font-family: $selectedFont;">$message</span>';
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:citycards_customer/postcard/blocs/edit_postcard/edit_postcard_bloc.dart';
|
||||
import 'package:citycards_customer/postcard/views/edit_postcard_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../core/route_constants.dart';
|
||||
@@ -22,9 +27,7 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
// Show loading indicator if drafts are loading
|
||||
if (state.isDraftLoading && state.draftPostCards.isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xffF95F62),
|
||||
),
|
||||
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,9 +82,13 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Show the list of drafts
|
||||
return RefreshIndicator(
|
||||
return Stack(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<MyPostCardBloc>().add(const RefreshDraftPostCards());
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshDraftPostCards(),
|
||||
);
|
||||
},
|
||||
color: const Color(0xffF95F62),
|
||||
child: ListView.builder(
|
||||
@@ -92,6 +99,26 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,11 +157,16 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<MyPostCardBloc>().add(const FetchDraftPostCards());
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const FetchDraftPostCards(),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
@@ -160,15 +192,57 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
|
||||
Widget _buildDraftCard(BuildContext context, MyPostCard postcard) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF95F62).withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xffF1F5F7)),
|
||||
color: const Color(0xffF95F62),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border(left: BorderSide(width: 6, color: Color(0XFFF93232))),
|
||||
),
|
||||
child: Row(
|
||||
margin: EdgeInsets.only(bottom: 15),
|
||||
child: Slidable(
|
||||
key: UniqueKey(),
|
||||
startActionPane: ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
|
||||
// dismissible: DismissiblePane(onDismissed: () {}),
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (ctx) {
|
||||
context.read<MyPostCardBloc>().add(
|
||||
DeleteDraftPostCards(id: postcard.id),
|
||||
);
|
||||
},
|
||||
flex: 3,
|
||||
backgroundColor: Color(0XFFF93232),
|
||||
foregroundColor: Colors.white,
|
||||
icon: Icons.delete,
|
||||
label: 'Delete',
|
||||
autoClose: true,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(color: const Color(0XFFFFF5F5)),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
/// NUMBER
|
||||
Text(
|
||||
"#${postcard.pcNumber}",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
/// LEFT IMAGE
|
||||
ClipRRect(
|
||||
@@ -210,81 +284,121 @@ class MyPostCardDraftView extends StatelessWidget {
|
||||
|
||||
/// RIGHT CONTENT
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
/// NUMBER
|
||||
Text(
|
||||
"#${postcard.pcNumber}",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
/// TITLE
|
||||
Text(
|
||||
child: Text(
|
||||
postcard.pcTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
/// ICONS – BOTTOM RIGHT (UNDER TITLE)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// delete
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => EditPostcardBloc(),
|
||||
child: EditPostcardView(myPostCard: postcard),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
// ignore: use_build_context_synchronously
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const RefreshDraftPostCards(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/delete_icon.png',
|
||||
width: 20,
|
||||
height: 20,
|
||||
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(
|
||||
0xfff95f62,
|
||||
).withValues(alpha: 0.1),
|
||||
elevation: 0,
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
side: BorderSide(color: Color(0XFFFDCDCE)),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// edit
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/edit_icon.png',
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit_outlined,
|
||||
size: 22,
|
||||
color: Color(0XFFF95F62),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// send
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/icons/send_icon.png',
|
||||
width: 20,
|
||||
height: 20,
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
"Edit",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFFF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(
|
||||
0xfff95f62,
|
||||
).withValues(alpha: 0.1),
|
||||
elevation: 0,
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
side: BorderSide(color: Color(0XFFFDCDCE)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: -45,
|
||||
child: Icon(
|
||||
Icons.send_outlined,
|
||||
size: 22,
|
||||
color: Color(0XFFF95F62),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
"Send",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFFF95F62),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,7 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
// Show loading indicator if orders are loading
|
||||
if (state.isOrderLoading && state.orderPostCards.isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xffF95F62),
|
||||
),
|
||||
child: CircularProgressIndicator(color: Color(0xffF95F62)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,11 +129,16 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<MyPostCardBloc>().add(const FetchOrderPostCards());
|
||||
context.read<MyPostCardBloc>().add(
|
||||
const FetchOrderPostCards(),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 12.h),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
@@ -160,41 +163,71 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildOrderCard(BuildContext context, MyPostCard postcard) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Postcard Number above the card
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
||||
child: Text(
|
||||
"#${postcard.pcNumber}",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15.sp,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Order Card
|
||||
Container(
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF95F62).withValues(alpha:0.08),
|
||||
color: const Color(0xffF95F62).withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xffF1F5F7)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"#${postcard.pcNumber}",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Spacer(),
|
||||
Text(
|
||||
"Status:",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10.sp,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(13, 7, 13, 7),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(
|
||||
postcard.orderStatus,
|
||||
).withOpacity(0.16),
|
||||
border: Border.all(
|
||||
color: const Color(0xffF1F5F7),
|
||||
color: _getStatusBorderColor(postcard.orderStatus),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
_getStatusText(postcard.orderStatus),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8.54.sp,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Postcard Image
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image(
|
||||
image: NetworkImage('${ApiUrls.baseUrl}${postcard.pcImagePath}'),
|
||||
image: NetworkImage(
|
||||
'${ApiUrls.baseUrl}${postcard.pcImagePath}',
|
||||
),
|
||||
height: 70.h,
|
||||
width: 70.w,
|
||||
fit: BoxFit.cover,
|
||||
@@ -233,16 +266,10 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
|
||||
// Postcard Details
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 60.h,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
child: Text(
|
||||
postcard.pcTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -252,51 +279,33 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
fontSize: 16.sp,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"5 Post cards",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(13, 7, 13, 7),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(postcard.orderStatus).withOpacity(0.16),
|
||||
border: Border.all(
|
||||
color: _getStatusBorderColor(postcard.orderStatus),
|
||||
margin: EdgeInsets.only(top: 10),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xfff95f62).withValues(alpha: 0.1),
|
||||
elevation: 0,
|
||||
padding: EdgeInsets.symmetric(vertical: 16.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: Color(0XFFFDCDCE)),
|
||||
),
|
||||
child: Text(
|
||||
_getStatusText(postcard.orderStatus),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8.54.sp,
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MyPostcardPreviewView(
|
||||
postcard: postcard,
|
||||
),
|
||||
builder: (context) =>
|
||||
MyPostcardPreviewView(postcard: postcard),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.remove_red_eye_outlined,
|
||||
@@ -315,16 +324,9 @@ class MyPostCardOrdersView extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
257
lib/postcard/widgets/edit_post_card/edit_message.dart
Normal file
257
lib/postcard/widgets/edit_post_card/edit_message.dart
Normal file
@@ -0,0 +1,257 @@
|
||||
import 'package:citycards_customer/postcard/views/write_message_step_page_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
|
||||
class EditMessage extends StatefulWidget {
|
||||
final String text;
|
||||
final Function(String, String) onChange;
|
||||
const EditMessage({super.key, required this.text, required this.onChange});
|
||||
|
||||
@override
|
||||
State<EditMessage> createState() => _EditMessageState();
|
||||
}
|
||||
|
||||
class _EditMessageState extends State<EditMessage> {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
final fonts = [
|
||||
{"name": "Default", "font": GoogleFonts.poppins(), "cleanName": "Poppins"},
|
||||
{
|
||||
"name": "Patrick Hand",
|
||||
"font": GoogleFonts.patrickHand(),
|
||||
"cleanName": "Patrick Hand",
|
||||
},
|
||||
{
|
||||
"name": "Indie Flower",
|
||||
"font": GoogleFonts.indieFlower(),
|
||||
"cleanName": "Indie Flower",
|
||||
},
|
||||
{
|
||||
"name": "Gloria Hallelujah",
|
||||
"font": GoogleFonts.gloriaHallelujah(),
|
||||
"cleanName": "Gloria Hallelujah",
|
||||
},
|
||||
];
|
||||
|
||||
String selectedFont = "Poppins";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final parsedMessage = _parseHtmlMessage(widget.text);
|
||||
final messageText = parsedMessage['text'] ?? '';
|
||||
final fontFamily = parsedMessage['fontFamily'] ?? '';
|
||||
setState(() {
|
||||
_controller.text = messageText;
|
||||
selectedFont = fontFamily;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF5F5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: LinedPaperPainter(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
maxLines: 8,
|
||||
maxLength: 400,
|
||||
cursorColor: const Color(0xffF95F62),
|
||||
style: _getTextFieldStyle(selectedFont, fonts),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: "Add Your Message Here",
|
||||
hintStyle: TextStyle(
|
||||
color: const Color(0xff999999),
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
counterText: "",
|
||||
),
|
||||
onChanged: (val) {
|
||||
widget.onChange(val, selectedFont);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 6, right: 8),
|
||||
child: Text(
|
||||
"${_controller.text.length}/400",
|
||||
style: TextStyle(fontSize: 12.sp, color: const Color(0xff999999)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: fonts.map((font) {
|
||||
final TextStyle fontStyle = font['font'] as TextStyle;
|
||||
final String fontName = font["name"] as String;
|
||||
final String cleanName = font["cleanName"] as String;
|
||||
|
||||
final isSelected = selectedFont == cleanName;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedFont = cleanName;
|
||||
});
|
||||
widget.onChange(_controller.text, selectedFont);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
width: 100.w,
|
||||
height: 100.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: DottedBorderPainter(
|
||||
color: isSelected
|
||||
? const Color(0xffF95F62)
|
||||
: const Color(0xffE0E0E0),
|
||||
strokeWidth: 1.5,
|
||||
dashWidth: 4,
|
||||
dashSpace: 3,
|
||||
borderRadius: 12,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Aa",
|
||||
style: fontStyle.copyWith(
|
||||
fontSize: 24.sp,
|
||||
color: const Color(0xff1A1A1A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
fontName,
|
||||
textAlign: TextAlign.center,
|
||||
style: fontStyle.copyWith(
|
||||
fontSize: 11.sp,
|
||||
color: isSelected
|
||||
? const Color(0xffF95F62)
|
||||
: const Color(0xff2D3134),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle _getTextFieldStyle(
|
||||
String? selectedFont,
|
||||
List<Map<String, dynamic>> fonts,
|
||||
) {
|
||||
if (selectedFont == null || selectedFont.isEmpty) {
|
||||
return GoogleFonts.poppins(fontSize: 14.sp, color: Colors.black);
|
||||
}
|
||||
|
||||
// Find matching font by cleanName
|
||||
for (var font in fonts) {
|
||||
if (font['cleanName'] == selectedFont) {
|
||||
final TextStyle fontStyle = font['font'] as TextStyle;
|
||||
return fontStyle.copyWith(fontSize: 14.sp, color: Colors.black);
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback to Poppins
|
||||
return GoogleFonts.poppins(fontSize: 14.sp, color: Colors.black);
|
||||
}
|
||||
|
||||
Map<String, String> _parseHtmlMessage(String htmlMessage) {
|
||||
if (htmlMessage.isEmpty) {
|
||||
return {'text': '', 'fontFamily': ''};
|
||||
}
|
||||
|
||||
// Check if message contains HTML tags
|
||||
if (!htmlMessage.contains('<span') && !htmlMessage.contains('style=')) {
|
||||
// Plain text message - no font specified
|
||||
return {'text': htmlMessage, 'fontFamily': ''};
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse HTML
|
||||
final document = html_parser.parse(htmlMessage);
|
||||
final spanElement = document.querySelector('span');
|
||||
|
||||
if (spanElement != null) {
|
||||
// Extract text content
|
||||
final text = spanElement.text;
|
||||
|
||||
// Extract font-family from style attribute
|
||||
final style = spanElement.attributes['style'] ?? '';
|
||||
final fontFamilyMatch = RegExp(
|
||||
r'font-family:\s*([^;]+)',
|
||||
).firstMatch(style);
|
||||
final fontFamily = fontFamilyMatch?.group(1)?.trim() ?? '';
|
||||
|
||||
return {'text': text, 'fontFamily': fontFamily};
|
||||
}
|
||||
|
||||
// Fallback: return plain text
|
||||
return {'text': document.body?.text ?? htmlMessage, 'fontFamily': ''};
|
||||
} catch (e) {
|
||||
// If parsing fails, return original message
|
||||
return {'text': htmlMessage, 'fontFamily': ''};
|
||||
}
|
||||
}
|
||||
|
||||
// Get TextStyle with any Google Font
|
||||
TextStyle _getFontStyle(String fontFamily, double fontSize, double height) {
|
||||
// If no font family specified, use default Caveat
|
||||
if (fontFamily.isEmpty) {
|
||||
return GoogleFonts.caveat(fontSize: fontSize, height: height);
|
||||
}
|
||||
|
||||
try {
|
||||
// Normalize font name: remove extra spaces, handle common variations
|
||||
final normalizedFont = fontFamily.trim().replaceAll(
|
||||
RegExp(r'\s+'),
|
||||
' ',
|
||||
); // Replace multiple spaces with single space
|
||||
|
||||
// Try to get the font from Google Fonts
|
||||
// GoogleFonts.getFont() can load ANY Google Font dynamically
|
||||
return GoogleFonts.getFont(
|
||||
normalizedFont,
|
||||
fontSize: fontSize,
|
||||
height: height,
|
||||
);
|
||||
} catch (e) {
|
||||
// If font not found in Google Fonts, fallback to default
|
||||
debugPrint(
|
||||
'⚠️ Font "$fontFamily" not found in Google Fonts. Using default Caveat font.',
|
||||
);
|
||||
return GoogleFonts.caveat(fontSize: fontSize, height: height);
|
||||
}
|
||||
}
|
||||
}
|
||||
277
lib/postcard/widgets/edit_post_card/your_details.dart
Normal file
277
lib/postcard/widgets/edit_post_card/your_details.dart
Normal file
@@ -0,0 +1,277 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class EditYourdetails extends StatefulWidget {
|
||||
final TextEditingController fullNameController;
|
||||
final TextEditingController addressController;
|
||||
final TextEditingController cityController;
|
||||
final TextEditingController zipCodeController;
|
||||
final String selectedCountry;
|
||||
final String selectedState;
|
||||
final GlobalKey<FormState> formKey;
|
||||
final Function(String) selectState;
|
||||
final Function(String) selectCountry;
|
||||
const EditYourdetails({
|
||||
super.key,
|
||||
required this.fullNameController,
|
||||
required this.addressController,
|
||||
required this.cityController,
|
||||
required this.zipCodeController,
|
||||
required this.selectedCountry,
|
||||
required this.selectedState,
|
||||
required this.formKey,
|
||||
required this.selectState,
|
||||
required this.selectCountry,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EditYourdetails> createState() => _EditYourdetailsState();
|
||||
}
|
||||
|
||||
class _EditYourdetailsState extends State<EditYourdetails> {
|
||||
String? _selectedState;
|
||||
String? _selectedCountry;
|
||||
|
||||
final List<String> countries = [
|
||||
'Australia',
|
||||
];
|
||||
|
||||
final List<String> states = [
|
||||
'New South Wales',
|
||||
'Victoria',
|
||||
'Queensland',
|
||||
'South Australia',
|
||||
'Western Australia',
|
||||
'Tasmania',
|
||||
'Northern Territory',
|
||||
'Australian Capital Territory',
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
setState(() {
|
||||
_selectedState = widget.selectedState;
|
||||
_selectedCountry = widget.selectedCountry;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Recipient Details",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF212121),
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
Text(
|
||||
"Enter the address of the person who will receive this postcard",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0XFF000000).withValues(alpha: 0.6),
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildInputField(
|
||||
label: "Recipient",
|
||||
hint: "Enter the recipient's name",
|
||||
controller: widget.fullNameController,
|
||||
),
|
||||
_buildInputField(
|
||||
label: "Address",
|
||||
hint: "Enter the recipient's Address",
|
||||
controller: widget.addressController,
|
||||
),
|
||||
_buildInputField(
|
||||
label: "City",
|
||||
hint: "Enter the name of your city",
|
||||
controller: widget.cityController,
|
||||
),
|
||||
_buildDropdownField(
|
||||
label: "Country",
|
||||
hint: "Select your country",
|
||||
value: _selectedCountry,
|
||||
items: countries,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_selectedCountry = val;
|
||||
});
|
||||
widget.selectCountry(val!);
|
||||
},
|
||||
),
|
||||
_buildDropdownField(
|
||||
label: "State",
|
||||
hint: "Select your state",
|
||||
value: _selectedState,
|
||||
items: states,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_selectedState = val;
|
||||
});
|
||||
widget.selectState(val!);
|
||||
},
|
||||
),
|
||||
_buildInputField(
|
||||
label: "Zip Code",
|
||||
hint: "Enter the Zip Code you reside in",
|
||||
controller: widget.zipCodeController,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputField({
|
||||
required String label,
|
||||
required String hint,
|
||||
required TextEditingController controller,
|
||||
IconData? icon,
|
||||
TextInputType? keyboardType,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xff1A1A1A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: keyboardType,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: GoogleFonts.poppins(
|
||||
color: const Color(0xff999999),
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
suffixIcon: icon != null
|
||||
? Icon(icon, color: Colors.black, size: 20)
|
||||
: null,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 12,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.red),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.red),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter $label';
|
||||
}
|
||||
if (label == "Email ID" && !value.contains('@')) {
|
||||
return 'Please enter a valid email';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownField({
|
||||
required String label,
|
||||
required String hint,
|
||||
required String? value,
|
||||
required List<String> items,
|
||||
required Function(String?) onChanged,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xff1A1A1A),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
DropdownButtonFormField<String>(
|
||||
value: value,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 12,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.red),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(color: Colors.red),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Color(0xffFDCDCE),
|
||||
),
|
||||
hint: Text(
|
||||
hint,
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xff999999),
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
items: items.map((String item) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: Text(item),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please select $label';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -371,6 +371,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.9.3"
|
||||
flutter_slidable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_slidable
|
||||
sha256: ea369262929d3cc6ebf9d8a00c196127966f117fe433a5e5cb47fb08008ca203
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.3"
|
||||
flutter_stripe:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -61,6 +61,7 @@ dependencies:
|
||||
cached_network_image: ^3.4.1
|
||||
bloc: ^9.2.0
|
||||
csc_picker_plus: ^0.0.3
|
||||
flutter_slidable: ^4.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user