2 Commits

Author SHA1 Message Date
mystery012728
e91d24becc pull taken of shreeyash and conflict solved 2026-02-09 10:55:36 +05:30
Shreeyash Thorat
09726eb4e6 API Integration 2026-02-06 19:34:34 +05:30
22 changed files with 631 additions and 339 deletions

View File

@@ -15,7 +15,7 @@ import '../repository/create_account_repository.dart';
class CreateAccountView extends StatelessWidget {
final String email;
CreateAccountView({super.key,required this.email});
CreateAccountView({super.key, required this.email});
final TextEditingController firstNameController = TextEditingController();
final TextEditingController lastNameController = TextEditingController();
@@ -29,9 +29,9 @@ class CreateAccountView extends StatelessWidget {
emailController.text.trim().isEmpty ||
phoneController.text.trim().isEmpty ||
addressController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please fill all fields')),
);
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Please fill all fields')));
return;
}
@@ -51,15 +51,14 @@ class CreateAccountView extends StatelessWidget {
Widget build(BuildContext context) {
emailController.text = email;
return BlocProvider(
create: (context) => CreateAccountBloc(
repository: CreateAccountRepository(),
),
create: (context) =>
CreateAccountBloc(repository: CreateAccountRepository()),
child: BlocListener<CreateAccountBloc, CreateAccountState>(
listener: (context, state) async {
listener: (ctx, state) async {
if (state is CreateAccountSuccess) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(state.message)));
await LocalPreference.setLogin(true);
final userId = await LocalPreference.getUserId();
context.read<ProfileBloc>().add(FetchProfileEvent(userId: userId!));

View File

@@ -0,0 +1,20 @@
import 'package:bloc/bloc.dart';
import 'package:citycards_customer/itinerary_creation/repository/itinerary_repository.dart';
import 'package:equatable/equatable.dart';
part 'get_itinerary_event.dart';
part 'get_itinerary_state.dart';
class GetItineraryBloc extends Bloc<GetItineraryEvent, GetItineraryState> {
GetItineraryBloc() : super(GetItineraryInitial()) {
on<GetIiterary>((event, emit) {
try {
emit(GetItineraryLoading());
final data = ItineraryRepository().fetchItinerary();
emit(GetItinerarySuccessfully());
} catch (e) {
emit(GetItineraryFailed(error: "Something went wrong"));
}
});
}
}

View File

@@ -0,0 +1,26 @@
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:citycards_customer/itinerary_creation/models/itinerary_city_model.dart';
import 'package:citycards_customer/itinerary_creation/repository/itinerary_repository.dart';
import 'package:equatable/equatable.dart';
part 'get_itinerary_cities_event.dart';
part 'get_itinerary_cities_state.dart';
class GetItineraryCitiesBloc
extends Bloc<GetItineraryCitiesEvent, GetItineraryCitiesState> {
GetItineraryCitiesBloc() : super(GetItineraryCitiesInitial()) {
on<GetItineraryCities>((event, emit) async {
try {
log("Getting cities");
emit(GetItineraryCitiesLoading());
final data = await ItineraryRepository().fetchItineraryCities();
emit(GetItineraryCitiesSuccessfully(cities: data));
} catch (e) {
log("Fetch Itierary - ${e.toString()}");
emit(GetItineraryCitiesFailed(error: "Something went wrong"));
}
});
}
}

View File

@@ -0,0 +1,10 @@
part of 'get_itinerary_cities_bloc.dart';
abstract class GetItineraryCitiesEvent extends Equatable {
const GetItineraryCitiesEvent();
@override
List<Object> get props => [];
}
class GetItineraryCities extends GetItineraryCitiesEvent {}

View File

@@ -0,0 +1,22 @@
part of 'get_itinerary_cities_bloc.dart';
abstract class GetItineraryCitiesState extends Equatable {
const GetItineraryCitiesState();
@override
List<Object> get props => [];
}
class GetItineraryCitiesInitial extends GetItineraryCitiesState {}
class GetItineraryCitiesLoading extends GetItineraryCitiesState {}
class GetItineraryCitiesSuccessfully extends GetItineraryCitiesState {
final List<ItineraryCityModel> cities;
const GetItineraryCitiesSuccessfully({required this.cities});
}
class GetItineraryCitiesFailed extends GetItineraryCitiesState {
final String error;
const GetItineraryCitiesFailed({required this.error});
}

View File

@@ -0,0 +1,10 @@
part of 'get_itinerary_bloc.dart';
abstract class GetItineraryEvent extends Equatable {
const GetItineraryEvent();
@override
List<Object> get props => [];
}
class GetIiterary extends GetItineraryEvent {}

View File

@@ -0,0 +1,19 @@
part of 'get_itinerary_bloc.dart';
abstract class GetItineraryState extends Equatable {
const GetItineraryState();
@override
List<Object> get props => [];
}
final class GetItineraryInitial extends GetItineraryState {}
class GetItineraryLoading extends GetItineraryState {}
class GetItinerarySuccessfully extends GetItineraryState {}
class GetItineraryFailed extends GetItineraryState {
final String error;
const GetItineraryFailed({required this.error});
}

View File

@@ -1,6 +1,9 @@
import 'package:citycards_customer/itinerary_creation/models/itinerary_city_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import '../models/current_location_model.dart';
abstract class ItineraryDetailEvent {}
class AddDateToItinerary extends ItineraryDetailEvent {
@@ -10,11 +13,17 @@ class AddDateToItinerary extends ItineraryDetailEvent {
}
class AddCityToItinerary extends ItineraryDetailEvent {
final String city;
final ItineraryCityModel city;
AddCityToItinerary(this.city);
}
class AddAddressToItinerary extends ItineraryDetailEvent {
final CurrentLocationModel address;
AddAddressToItinerary(this.address);
}
class AddEnergyToItinerary extends ItineraryDetailEvent {
final String energy;
@@ -65,7 +74,7 @@ class AddShoppingRating extends ItineraryDetailEvent {
class ItineraryDetailState {
final String? selectedDate;
final String? selectedCity;
final ItineraryCityModel? selectedCity;
final String? selectedEnergy;
final String? withKid;
final String? selectedDietary;
@@ -74,6 +83,7 @@ class ItineraryDetailState {
final String? culturalRating;
final String? wildLifeRating;
final String? shoppingRating;
final CurrentLocationModel? baseAdd;
ItineraryDetailState({
this.selectedDate,
@@ -86,19 +96,21 @@ class ItineraryDetailState {
this.culturalRating,
this.wildLifeRating,
this.shoppingRating,
this.baseAdd,
});
ItineraryDetailState copyWith({
String? selectedDate,
String? selectedCity,
ItineraryCityModel? selectedCity,
String? selectedEnergy,
String? withKid,
String? selectedDietary,
String? selectedDietary,
String? museumRating,
String? scenicRating,
String? culturalRating,
String? wildLifeRating,
String? shoppingRating,
CurrentLocationModel? baseAdd,
}) {
return ItineraryDetailState(
selectedDate: selectedDate ?? this.selectedDate,
@@ -111,6 +123,7 @@ class ItineraryDetailState {
culturalRating: culturalRating ?? this.culturalRating,
wildLifeRating: wildLifeRating ?? this.wildLifeRating,
shoppingRating: shoppingRating ?? this.shoppingRating,
baseAdd: baseAdd ?? this.baseAdd,
);
}
}
@@ -121,15 +134,6 @@ class AddItineraryDetailBloc
: super(
ItineraryDetailState(
selectedDate: DateFormat('EEEE, MMMM d, yyyy').format(DateTime.now()),
selectedCity: "Paris",
selectedEnergy: "",
withKid: "",
selectedDietary: "",
museumRating: "",
scenicRating: "",
culturalRating: "",
wildLifeRating: "",
shoppingRating: "",
),
) {
on<AddDateToItinerary>((event, emit) {
@@ -137,10 +141,13 @@ class AddItineraryDetailBloc
});
on<AddCityToItinerary>((event, emit) {
print("Selected city: ${event.city}");
emit(state.copyWith(selectedCity: event.city));
});
on<AddAddressToItinerary>((event, emit) {
emit(state.copyWith(baseAdd: event.address));
});
on<AddEnergyToItinerary>((event, emit) {
emit(state.copyWith(selectedEnergy: event.energy));
});
@@ -150,13 +157,6 @@ class AddItineraryDetailBloc
});
on<AddDietaryToItinerary>((event, emit) {
// final currentSelection = List<String>.from(state.selectedDietary ?? []);
//
// if (currentSelection.contains(event.dietary)) {
// currentSelection.remove(event.dietary);
// } else {
// currentSelection.add(event.dietary);
// }
emit(state.copyWith(selectedDietary: event.dietary));
});

View File

@@ -0,0 +1,6 @@
class CurrentLocationModel {
final String? baseAdd;
final double? lat;
final double? lan;
CurrentLocationModel({this.baseAdd, this.lan, this.lat});
}

View File

@@ -0,0 +1,57 @@
class ItineraryCityModel {
int? id;
String? cityName;
String? urlSlug;
int? iconXid;
Icon? icon;
ItineraryCityModel({
this.id,
this.cityName,
this.urlSlug,
this.iconXid,
this.icon,
});
ItineraryCityModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
cityName = json['cityName'];
urlSlug = json['urlSlug'];
iconXid = json['iconXid'];
icon = json['icon'] != null ? Icon.fromJson(json['icon']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['cityName'] = cityName;
data['urlSlug'] = urlSlug;
data['iconXid'] = iconXid;
if (icon != null) {
data['icon'] = icon!.toJson();
}
return data;
}
}
class Icon {
int? id;
String? iconName;
String? iconSvg;
Icon({this.id, this.iconName, this.iconSvg});
Icon.fromJson(Map<String, dynamic> json) {
id = json['id'];
iconName = json['iconName'];
iconSvg = json['iconSvg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['iconName'] = iconName;
data['iconSvg'] = iconSvg;
return data;
}
}

View File

@@ -0,0 +1,36 @@
import 'dart:developer';
import 'package:citycards_customer/itinerary_creation/models/itinerary_city_model.dart';
import 'package:dio/dio.dart';
import '../../networkApiServices/api_urls.dart';
import '../../networkApiServices/network_api_services.dart';
class ItineraryRepository {
final NetworkApiService _apiService = NetworkApiService();
Future<dynamic> fetchItinerary() async {
final response = await _apiService.getApi(url: ApiUrls.getItinerary);
return response.data;
}
Future<List<ItineraryCityModel>> fetchItineraryCities() async {
try {
final response = await _apiService.getApi(
url: ApiUrls.getItineraryCities,
);
final List<ItineraryCityModel> cities = (response.data as List)
.map((e) => ItineraryCityModel.fromJson(e as Map<String, dynamic>))
.toList();
return cities;
} on DioException catch (e) {
// log("Error logged - ${e.response}");
throw e.response!.data["message"] ?? "Something went wrong";
} catch (e, stack) {
log("Error logged - ${stack.toString()}");
rethrow;
}
}
}

View File

@@ -1,47 +1,30 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
import 'package:citycards_customer/common_packages/custom_search_field.dart';
import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:citycards_customer/common_packages/custom_textfield.dart';
import 'package:citycards_customer/itinerary_creation/bloc/get_itinerary_cities_bloc.dart';
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_detail_bloc.dart';
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart';
import 'package:citycards_customer/networkApiServices/api_urls.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MenuItem {
final int id;
final String label;
final String flag;
class CitySelectionView extends StatefulWidget {
const CitySelectionView({super.key});
MenuItem(this.id, this.label, this.flag);
@override
State<CitySelectionView> createState() => _CitySelectionViewState();
}
List<MenuItem> menuItems = [
MenuItem(1, 'Paris', "🇫🇷"),
MenuItem(2, 'Tokyo', "🇯🇵"),
MenuItem(3, 'New York', "🇺🇸"),
MenuItem(4, 'London', "🇬🇧"),
MenuItem(5, 'Barcelona', "🇪🇸"),
MenuItem(6, 'Dubai', "🇦🇪"),
MenuItem(7, 'Rome', "🇮🇹"),
MenuItem(8, 'Bangkok', "🇹🇭"),
];
class CitySelectionView extends StatelessWidget {
CitySelectionView({super.key});
final List<Map<String, String>> cityList = [
{"flag": "🇫🇷", "city": "Paris"},
{"flag": "🇯🇵", "city": "Tokyo"},
{"flag": "🇺🇸", "city": "New York"},
{"flag": "🇬🇧", "city": "London"},
{"flag": "🇪🇸", "city": "Barcelona"},
{"flag": "🇦🇪", "city": "Dubai"},
{"flag": "🇮🇹", "city": "Rome"},
{"flag": "🇹🇭", "city": "Bangkok"},
];
class _CitySelectionViewState extends State<CitySelectionView> {
final TextEditingController cityController = TextEditingController();
final GetItineraryCitiesBloc getItineraryCitiesBloc =
GetItineraryCitiesBloc();
@override
void initState() {
getItineraryCitiesBloc.add(GetItineraryCities());
super.initState();
}
@override
Widget build(BuildContext context) {
@@ -60,89 +43,6 @@ class CitySelectionView extends StatelessWidget {
),
SizedBox(height: 32.h),
Container(
height: 56.h,
padding: EdgeInsets.only(left: 20.w),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF95F62)),
borderRadius: BorderRadius.circular(28),
),
child: Row(
children: [
Image.asset("assets/icons/location.png", scale: 4),
Expanded(
child: SizedBox(
child:
BlocBuilder<
AddItineraryDetailBloc,
ItineraryDetailState
>(
builder: (context, state) {
final selectedMenuItem = menuItems.firstWhere(
(menu) => menu.label == state.selectedCity,
orElse: () =>
menuItems.first, // fallback if not found
);
return DropdownMenu<MenuItem>(
controller: cityController,
initialSelection: selectedMenuItem,
width: double.infinity,
hintText: "Select City",
requestFocusOnTap: true,
enableFilter: true,
showTrailingIcon: false,
onSelected: (MenuItem? menu) {
context.read<AddItineraryDetailBloc>().add(
AddCityToItinerary(menu!.label),
);
},
inputDecorationTheme: InputDecorationTheme(
contentPadding: EdgeInsets.symmetric(
vertical: 6.h,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(28),
borderSide: const BorderSide(
color: Colors.transparent,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(28),
borderSide: const BorderSide(
color: Colors.transparent,
),
),
),
menuStyle: MenuStyle(
backgroundColor: WidgetStateProperty.all(
Colors.white,
),
maximumSize: WidgetStateProperty.all(
Size.infinite,
),
),
dropdownMenuEntries: menuItems
.map<DropdownMenuEntry<MenuItem>>((
MenuItem menu,
) {
return DropdownMenuEntry<MenuItem>(
value: menu,
label: menu.label,
leadingIcon: CustomText(text: menu.flag),
);
})
.toList(),
);
},
),
),
),
],
),
),
SizedBox(height: 16.h),
Align(
alignment: Alignment.topLeft,
@@ -154,57 +54,86 @@ class CitySelectionView extends StatelessWidget {
),
),
SizedBox(height: 10.h),
SizedBox(
height: 175.h,
child: BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
builder: (context, state) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 16.h,
crossAxisSpacing: 16.w,
),
itemCount: cityList.length,
itemBuilder: (context, index) {
final item = cityList[index];
final isSelected = item['city'] == state.selectedCity;
return GestureDetector(
onTap: () {
context.read<AddItineraryDetailBloc>().add(
AddCityToItinerary(item['city'] ?? ""),
);
},
child: Container(
height: 78.h,
width: 76.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? Color(0xFFF95F62)
: Colors.transparent,
),
BlocBuilder<GetItineraryCitiesBloc, GetItineraryCitiesState>(
bloc: getItineraryCitiesBloc,
builder: (ctx, state1) {
if (state1 is GetItineraryCitiesLoading) {
return Center(child: CircularProgressIndicator());
} else if (state1 is GetItineraryCitiesFailed) {
return Center(child: Text(state1.error));
} else if (state1 is GetItineraryCitiesSuccessfully &&
state1.cities.isEmpty) {
return Center(child: Text("Data not found"));
} else if (state1 is GetItineraryCitiesSuccessfully) {
return SizedBox(
height: 175.h,
child: BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
builder: (context, state) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 16.h,
crossAxisSpacing: 16.w,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomText(text: item['flag'] ?? ""),
SizedBox(height: 4.h),
CustomText(
text: item['city'] ?? "",
size: 12.sp,
color: Color(0xFF364153),
itemCount: state1.cities.length,
itemBuilder: (context, index) {
final item = state1.cities[index];
final isSelected = item == state.selectedCity;
return GestureDetector(
onTap: () {
context.read<AddItineraryDetailBloc>().add(
AddCityToItinerary(item),
);
},
child: Container(
height: 78.h,
width: 76.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? Color(0xFFF95F62)
: Colors.transparent,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CachedNetworkImage(
imageUrl:
"${ApiUrls.baseUrl}${item.icon!.iconSvg!}",
width: 20,
height: 20,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: 20,
height: 20,
color: Colors.grey.shade50,
),
errorWidget: (context, url, error) =>
const Icon(Icons.flag, size: 20),
),
SizedBox(height: 4.h),
CustomText(
text: item.cityName ?? "",
size: 12.sp,
color: Color(0xFF364153),
),
],
),
),
],
),
),
);
},
);
},
);
},
),
);
},
),
}
return Container();
},
),
SizedBox(height: 40.h),
CustomFilledButton(

View File

@@ -1,11 +1,16 @@
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart';
import 'package:citycards_customer/itinerary_creation/models/current_location_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
import '../../bloc/itinerary_detail_bloc.dart';
class CurrentLocationSelection extends StatefulWidget {
const CurrentLocationSelection({super.key});
@@ -21,6 +26,7 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
Future<void> _getCurrentLocation() async {
LocationPermission permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -33,11 +39,40 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
desiredAccuracy: LocationAccuracy.high,
);
final lat = position.latitude;
final lng = position.longitude;
setState(() {
_currentLatLng = LatLng(position.latitude, position.longitude);
_controller.text =
"Lat: ${position.latitude.toStringAsFixed(5)}, Lng: ${position.longitude.toStringAsFixed(5)}";
_currentLatLng = LatLng(lat, lng);
});
await _getAddressFromLatLng(lat, lng);
}
Future<void> _getAddressFromLatLng(double lat, double lng) async {
try {
final placemarks = await placemarkFromCoordinates(lat, lng);
if (placemarks.isNotEmpty) {
final place = placemarks.first;
final address = [
place.name,
place.street,
place.subLocality,
place.locality,
place.administrativeArea,
place.postalCode,
place.country,
].where((e) => e != null && e.isNotEmpty).join(', ');
setState(() {
_controller.text = address;
});
}
} catch (e) {
debugPrint("Reverse geocoding error: $e");
}
}
@override
@@ -98,32 +133,38 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
child: SizedBox(
height: 250.h,
width: double.infinity,
child: Image.asset(
"assets/images/attra_detail_map.png",
fit: BoxFit.cover,
height: 236.h,
child: FlutterMap(
options: MapOptions(
initialCenter: _currentLatLng!,
initialZoom: 15,
),
children: [
TileLayer(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'],
userAgentPackageName: 'com.citycards.customer',
),
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: const Icon(
Icons.location_pin,
color: Colors.red,
size: 40,
),
),
],
),
],
),
// child: GoogleMap(
// initialCameraPosition: CameraPosition(
// target: _currentLatLng!,
// zoom: 15,
// ),
// markers: {
// Marker(
// markerId: const MarkerId("currentLocation"),
// position: _currentLatLng!,
// ),
// },
// myLocationEnabled: true,
// myLocationButtonEnabled: false,
// ),
),
)
: GestureDetector(
onTap: () {
_getCurrentLocation();
},
onTap: _getCurrentLocation,
child: Container(
height: 46.h,
padding: EdgeInsets.symmetric(horizontal: 12.w),
@@ -155,6 +196,15 @@ class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
// --- Continue button ---
CustomFilledButton(
onTap: () {
context.read<AddItineraryDetailBloc>().add(
AddAddressToItinerary(
CurrentLocationModel(
baseAdd: _controller.text,
lan: _currentLatLng?.latitude,
lat: _currentLatLng?.latitude,
),
),
);
context.read<ItineraryStepNavigationBloc>().add(
ItineraryStepNavigationNextEvent(),
);

View File

@@ -27,35 +27,35 @@ class DateSelectionView extends StatelessWidget {
),
SizedBox(height: 32.h),
Container(
height: 90.h,
padding: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(28),
border: Border.all(color: Color(0xFFF95F62), width: 1.1.w),
),
child: Row(
children: [
GestureDetector(
onTap: () {
_pickDate(context);
},
child: Image.asset("assets/icons/calender.png", scale: 4),
),
SizedBox(width: 16.w),
BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
builder: (context, state) {
return CustomText(
text: state.selectedDate ?? "",
size: 14.sp,
color: Color(0xFF101828),
);
},
),
const Spacer(),
Icon(Icons.check_circle, color: Color(0xFFF95F62)),
],
GestureDetector(
onTap: () {
_pickDate(context);
},
child: Container(
height: 90.h,
padding: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(28),
border: Border.all(color: Color(0xFFF95F62), width: 1.1.w),
),
child: Row(
children: [
Image.asset("assets/icons/calender.png", scale: 4),
SizedBox(width: 16.w),
BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
builder: (context, state) {
return CustomText(
text: state.selectedDate ?? "",
size: 14.sp,
color: Color(0xFF101828),
);
},
),
const Spacer(),
Icon(Icons.check_circle, color: Color(0xFFF95F62)),
],
),
),
),
SizedBox(height: 32.h),

View File

@@ -69,7 +69,7 @@ class ItineraryCompletionView extends StatelessWidget {
),
_buildProfileRow(
"City",
state.selectedCity ?? "",
state.selectedCity!.cityName ?? "",
),
_buildProfileRow(
"Energy",

View File

@@ -1,4 +1,4 @@
import 'package:citycards_customer/itinerary_creation/bloc/get_itinerary_cities_bloc.dart';
import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart';
import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_steps/current_location_selection.dart';
import 'package:flutter/material.dart';
@@ -105,7 +105,10 @@ class _ItineraryCreationPageState extends State<ItineraryCreationPage> {
children: [
DateSelectionView(),
CurrentLocationSelection(),
CitySelectionView(),
BlocProvider(
create: (context) => GetItineraryCitiesBloc(),
child: CitySelectionView(),
),
EnergySelectionView(),
KidsSelectionView(),
DietarySelectionView(),

View File

@@ -43,63 +43,63 @@ class _MagicItineraryViewState extends State<MagicItineraryView> {
child: isLoading
? Center(child: CircularProgressIndicator())
: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
child: SingleChildScrollView(
child: Column(
children: [
CommonAppBar(
isWhiteLogo: false,
isProfilePage: false,
showDivider: false,
),
SizedBox(height: 24.h),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
child: SingleChildScrollView(
child: Column(
children: [
CommonAppBar(
isWhiteLogo: false,
isProfilePage: false,
showDivider: false,
),
SizedBox(height: 24.h),
// Show different UI based on login status
if (isLoggedIn) ...[
ItineraryFilledCard(),
SizedBox(height: 32.h),
CustomPaint(
painter: DottedBorderPainter(),
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 24.h),
decoration: BoxDecoration(
color: Color(0xFFF95F62).withOpacity(0.25),
borderRadius: BorderRadius.circular(12.sp),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomText(
text: "Plan your next adventure",
color: Color(0xFF656565),
size: 14.sp,
),
SizedBox(height: 16.h),
CustomFilledButton(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ItineraryCreationStartPage(),
// Show different UI based on login status
if (isLoggedIn) ...[
ItineraryFilledCard(),
SizedBox(height: 32.h),
CustomPaint(
painter: DottedBorderPainter(),
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 24.h),
decoration: BoxDecoration(
color: Color(0xFFF95F62).withOpacity(0.25),
borderRadius: BorderRadius.circular(12.sp),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomText(
text: "Plan your next adventure",
color: Color(0xFF656565),
size: 14.sp,
),
);
},
label: "Create My Itinerary",
showArrow: true,
SizedBox(height: 16.h),
CustomFilledButton(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ItineraryCreationStartPage(),
),
);
},
label: "Create My Itinerary",
showArrow: true,
),
],
),
),
],
),
),
),
] else ...[
EmptyItineraryView(),
],
],
),
] else ...[
EmptyItineraryView(),
],
],
),
),
),
),
),
),
);
}
@@ -151,9 +151,7 @@ class EmptyItineraryView extends StatelessWidget {
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
),
builder: (_) => const LoginEmailBottomsheet(),
);
@@ -247,8 +245,9 @@ class ItineraryFilledCard extends StatelessWidget {
SizedBox(height: 12.h),
InkWell(
onTap: () {
Navigator.of(context)
.pushReplacementNamed(RouteConstants.yourItinerary);
Navigator.of(
context,
).pushReplacementNamed(RouteConstants.yourItinerary);
},
child: Container(
height: 43.h,
@@ -270,4 +269,4 @@ class ItineraryFilledCard extends StatelessWidget {
),
);
}
}
}

View File

@@ -20,6 +20,9 @@ class ApiUrls {
static const myPostCards = "$baseUrl/mobile/postcards/all";
static const coupons = "$baseUrl/mobile/passes/dropdown/card";
static const getItinerary = "$baseUrl/mobile/itinerary/all-initineraries";
static const getItineraryCities =
"$baseUrl/mobile/itinerary/cities-with-icons";
//Post Apis
static const createAccount = "$baseUrl/mobile/user/register";
@@ -28,4 +31,4 @@ class ApiUrls {
static const submitTicket = "$baseUrl/mobile/user/support";
static const createPostCard = "$baseUrl/mobile/postcards";
static const addToCartPasses = "$baseUrl/mobile/passes/add-to-cart";
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../localPreference/local_preference.dart';
@@ -34,14 +36,17 @@ class NetworkApiService {
const maxRetries = 2;
final currentRetry = options.extra['retry'] as int? ?? 0;
final shouldRetry = currentRetry < maxRetries &&
final shouldRetry =
currentRetry < maxRetries &&
(err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.sendTimeout ||
err.type == DioExceptionType.receiveTimeout);
if (shouldRetry) {
if (kDebugMode) {
print('🔁 Retrying request (${currentRetry + 1}) => ${options.uri}');
print(
'🔁 Retrying request (${currentRetry + 1}) => ${options.uri}',
);
}
options.extra['retry'] = currentRetry + 1;
@@ -65,6 +70,7 @@ class NetworkApiService {
QueuedInterceptorsWrapper(
onRequest: (options, handler) async {
final token = await LocalPreference.getAccessToken();
if (token != null && token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
@@ -188,9 +194,7 @@ class NetworkApiService {
final response = await _dio.post(
ApiUrls.refreshToken,
data: {"refreshToken": refreshToken},
options: Options(
headers: {'Authorization': null},
),
options: Options(headers: {'Authorization': null}),
);
await LocalPreference.setAccessToken(response.data['accessToken']);

View File

@@ -5,6 +5,8 @@ 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';
class MyPostcardPreviewView extends StatefulWidget {
final MyPostCard postcard;
@@ -154,9 +156,39 @@ class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
),
),
// Postcard Display
// 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,
// )
// : FrontCardWidget(
// key: const ValueKey('front'),
// imageUrl:
// '${ApiUrls.baseUrl}${widget.postcard.pcImagePath}',
// ),
// ),
// ),
// ),
// ),
Expanded(
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 40.h),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
@@ -180,16 +212,8 @@ class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
Widget _buildFrontSide() {
return Container(
key: const ValueKey('front'),
margin: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
@@ -234,7 +258,6 @@ class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
Widget _buildBackSide() {
return Container(
key: const ValueKey('back'),
margin: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
@@ -250,13 +273,6 @@ class _MyPostcardPreviewViewState extends State<MyPostcardPreviewView> {
color: const Color(0xff000000).withOpacity(0.12),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: AspectRatio(
aspectRatio: 1.5,

View File

@@ -34,13 +34,13 @@ packages:
source: hosted
version: "2.13.0"
bloc:
dependency: transitive
dependency: "direct main"
description:
name: bloc
sha256: e18b8e7825e9921d67a6d256dba0b6015ece8a577eb0a411845c46a352994d78
sha256: a48653a82055a900b88cd35f92429f068c5a8057ae9b136d197b3d56c57efb81
url: "https://pub.dev"
source: hosted
version: "9.0.1"
version: "9.2.0"
boolean_selector:
dependency: transitive
description:
@@ -49,6 +49,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
characters:
dependency: transitive
description:
@@ -262,6 +286,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "9.1.1"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
flutter_glass_morphism:
dependency: "direct main"
description:
@@ -365,6 +397,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.1"
geocoding:
dependency: "direct main"
description:
name: geocoding
sha256: "606be036287842d779d7ec4e2f6c9435fc29bbbd3c6da6589710f981d8852895"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
geocoding_android:
dependency: transitive
description:
name: geocoding_android
sha256: ba810da90d6633cbb82bbab630e5b4a3b7d23503263c00ae7f1ef0316dcae5b9
url: "https://pub.dev"
source: hosted
version: "4.0.1"
geocoding_ios:
dependency: transitive
description:
name: geocoding_ios
sha256: "18ab1c8369e2b0dcb3a8ccc907319334f35ee8cf4cfef4d9c8e23b13c65cb825"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
geocoding_platform_interface:
dependency: transitive
description:
name: geocoding_platform_interface
sha256: "8c2c8226e5c276594c2e18bfe88b19110ed770aeb7c1ab50ede570be8b92229b"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
geolocator:
dependency: "direct main"
description:
@@ -709,6 +773,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
opentype_dart:
dependency: transitive
description:
@@ -837,6 +909,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
sanitize_html:
dependency: transitive
description:

View File

@@ -57,6 +57,9 @@ dependencies:
sqflite: ^2.4.2
flutter_map: ^8.2.2
flutter_stripe: ^12.2.0
geocoding: ^4.0.0
cached_network_image: ^3.4.1
bloc: ^9.2.0
dev_dependencies:
flutter_test: