INFRA: Set Up Project.
This commit is contained in:
312
lib/screen_ui/on_demand_service/favourite_ondemand_screen.dart
Normal file
312
lib/screen_ui/on_demand_service/favourite_ondemand_screen.dart
Normal file
@@ -0,0 +1,312 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/controllers/favourite_ondemmand_controller.dart';
|
||||
import 'package:customer/controllers/theme_controller.dart';
|
||||
import 'package:customer/models/category_model.dart';
|
||||
import 'package:customer/models/provider_serivce_model.dart';
|
||||
import 'package:customer/screen_ui/auth_screens/login_screen.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/on_demand_details_screen.dart';
|
||||
import 'package:customer/service/fire_store_utils.dart';
|
||||
import 'package:customer/themes/app_them_data.dart';
|
||||
import 'package:customer/themes/round_button_fill.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class FavouriteOndemandScreen extends StatelessWidget {
|
||||
const FavouriteOndemandScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return GetX(
|
||||
init: FavouriteOndemmandController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
backgroundColor: isDark ? AppThemeData.surfaceDark : AppThemeData.surface,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.onDemand300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Text("Favourite Services".tr, style: TextStyle(fontFamily: AppThemeData.semiBold, color: isDark ? AppThemeData.grey900 : AppThemeData.grey900, fontSize: 20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: Constant.userModel == null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset("assets/images/login.gif", height: 120),
|
||||
const SizedBox(height: 12),
|
||||
Text("Please Log In to Continue".tr, style: TextStyle(color: isDark ? AppThemeData.grey100 : AppThemeData.grey800, fontSize: 22, fontFamily: AppThemeData.semiBold)),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"You’re not logged in. Please sign in to access your account and explore all features.".tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: isDark ? AppThemeData.grey50 : AppThemeData.grey500, fontSize: 16, fontFamily: AppThemeData.bold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
RoundedButtonFill(
|
||||
title: "Log in".tr,
|
||||
width: 55,
|
||||
height: 5.5,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey50,
|
||||
onPress: () async {
|
||||
Get.offAll(const LoginScreen());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child:
|
||||
controller.lstFav.isEmpty
|
||||
? Constant.showEmptyView(message: "Favourite Service not found.".tr)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
scrollDirection: Axis.vertical,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: controller.lstFav.length,
|
||||
itemBuilder: (context, index) {
|
||||
return FutureBuilder<List<ProviderServiceModel>>(
|
||||
future: FireStoreUtils.getCurrentProviderService(controller.lstFav[index]),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data == null || snapshot.data!.isEmpty) {
|
||||
return const SizedBox(); // or a placeholder widget
|
||||
}
|
||||
|
||||
final provider = snapshot.data!.first; // safer way than [0]
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(() => OnDemandDetailsScreen(), arguments: {'providerModel': provider});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey500 : Colors.grey.shade100, width: 1),
|
||||
color: isDark ? AppThemeData.grey900 : Colors.white,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10), topLeft: Radius.circular(10)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: provider.photos.isNotEmpty ? provider.photos.first : Constant.placeHolderImage,
|
||||
height: MediaQuery.of(context).size.height * 0.16,
|
||||
width: 110,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.title ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () => controller.toggleFavourite(provider),
|
||||
child: Icon(
|
||||
controller.lstFav.where((element) => element.service_id == provider.id).isNotEmpty ? Icons.favorite : Icons.favorite_border,
|
||||
size: 24,
|
||||
color:
|
||||
controller.lstFav.where((element) => element.service_id == provider.id).isNotEmpty
|
||||
? AppThemeData.primary300
|
||||
: (isDark ? Colors.white38 : Colors.black38),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FutureBuilder<CategoryModel?>(
|
||||
future: controller.getCategory(provider.categoryId ?? ""),
|
||||
builder: (ctx, snap) {
|
||||
if (!snap.hasData) return const SizedBox();
|
||||
return Text(snap.data?.title ?? "", style: TextStyle(fontSize: 14, color: isDark ? Colors.white : Colors.black));
|
||||
},
|
||||
),
|
||||
_buildPrice(provider, isDark: isDark),
|
||||
_buildRating(provider),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
FutureBuilder<List<ProviderServiceModel>>(
|
||||
future: FireStoreUtils.getCurrentProviderService(controller.lstFav[index]),
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data != null
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(() => OnDemandDetailsScreen(), arguments: {'providerModel': snapshot.data![0]});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey500 : Colors.grey.shade100, width: 1),
|
||||
color: isDark ? AppThemeData.grey900 : Colors.white,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10), topLeft: Radius.circular(10)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: snapshot.data![0].photos.isNotEmpty ? snapshot.data![0].photos[0] : Constant.placeHolderImage,
|
||||
height: MediaQuery.of(context).size.height * 0.16,
|
||||
width: 110,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
snapshot.data![0].title ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () => controller.toggleFavourite(snapshot.data![0]),
|
||||
child: Icon(
|
||||
controller.lstFav.where((element) => element.service_id == snapshot.data![0].id).isNotEmpty
|
||||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
size: 24,
|
||||
color:
|
||||
controller.lstFav.where((element) => element.service_id == snapshot.data![0].id).isNotEmpty
|
||||
? AppThemeData.primary300
|
||||
: (isDark ? Colors.white38 : Colors.black38),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FutureBuilder<CategoryModel?>(
|
||||
future: controller.getCategory(snapshot.data![0].categoryId ?? ""),
|
||||
builder: (ctx, snap) {
|
||||
if (!snap.hasData) return const SizedBox();
|
||||
return Text(snap.data?.title ?? "", style: TextStyle(fontSize: 14, color: isDark ? Colors.white : Colors.black));
|
||||
},
|
||||
),
|
||||
_buildPrice(snapshot.data![0], isDark: isDark),
|
||||
_buildRating(snapshot.data![0]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrice(ProviderServiceModel provider, {bool isDark = false}) {
|
||||
if (provider.disPrice == "" || provider.disPrice == "0") {
|
||||
return Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price) : '${Constant.amountShow(amount: provider.price ?? "0")}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.disPrice ?? '0') : '${Constant.amountShow(amount: provider.disPrice)}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price) : '${Constant.amountShow(amount: provider.price ?? "0")}/${'hr'.tr}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey, decoration: TextDecoration.lineThrough),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRating(ProviderServiceModel provider) {
|
||||
double rating = 0;
|
||||
if (provider.reviewsCount != null && provider.reviewsCount != 0) {
|
||||
rating = (provider.reviewsSum ?? 0) / (provider.reviewsCount ?? 1);
|
||||
}
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: AppThemeData.warning400, borderRadius: BorderRadius.circular(16)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.star, size: 16, color: Colors.white),
|
||||
const SizedBox(width: 3),
|
||||
Text(rating.toStringAsFixed(1), style: const TextStyle(letterSpacing: 0.5, fontSize: 12, color: Colors.white)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
203
lib/screen_ui/on_demand_service/my_booking_on_demand_screen.dart
Normal file
203
lib/screen_ui/on_demand_service/my_booking_on_demand_screen.dart
Normal file
@@ -0,0 +1,203 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/my_booking_on_demand_controller.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../models/onprovider_order_model.dart';
|
||||
import '../../models/worker_model.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import 'on_demand_order_details_screen.dart';
|
||||
|
||||
class MyBookingOnDemandScreen extends StatelessWidget {
|
||||
const MyBookingOnDemandScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX<MyBookingOnDemandController>(
|
||||
init: MyBookingOnDemandController(),
|
||||
builder: (controller) {
|
||||
return DefaultTabController(
|
||||
length: controller.tabTitles.length,
|
||||
initialIndex: controller.tabTitles.indexOf(controller.selectedTab.value),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
centerTitle: false,
|
||||
title: Padding(padding: const EdgeInsets.only(bottom: 10), child: Text("Booking History".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900))),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(48),
|
||||
child: TabBar(
|
||||
onTap: (index) {
|
||||
controller.selectTab(controller.tabTitles[index]);
|
||||
},
|
||||
indicatorColor: AppThemeData.grey900,
|
||||
labelColor: AppThemeData.grey900,
|
||||
unselectedLabelColor: AppThemeData.grey900,
|
||||
labelStyle: AppThemeData.boldTextStyle(fontSize: 16),
|
||||
unselectedLabelStyle: AppThemeData.mediumTextStyle(fontSize: 16),
|
||||
tabs: controller.tabTitles.map((title) => Tab(child: Center(child: Text(title)))).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: TabBarView(
|
||||
children:
|
||||
controller.tabTitles.map((title) {
|
||||
final orders = controller.getOrdersForTab(title);
|
||||
|
||||
if (orders.isEmpty) {
|
||||
return Center(child: Text("No ride found".tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: orders.length,
|
||||
itemBuilder: (context, index) {
|
||||
OnProviderOrderModel onProviderOrder = orders[index];
|
||||
WorkerModel? worker = controller.getWorker(onProviderOrder.workerId);
|
||||
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.to(() => OnDemandOrderDetailsScreen(), arguments: onProviderOrder);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey500 : Colors.grey.shade100, width: 1),
|
||||
color: isDark ? AppThemeData.grey500 : Colors.white,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: onProviderOrder.provider.photos.first,
|
||||
height: 80,
|
||||
width: 80,
|
||||
imageBuilder:
|
||||
(context, imageProvider) =>
|
||||
Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), image: DecorationImage(image: imageProvider, fit: BoxFit.cover))),
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget:
|
||||
(context, url, error) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.network(Constant.placeHolderImage, fit: BoxFit.cover, cacheHeight: 80, cacheWidth: 80),
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: BoxDecoration(color: AppThemeData.info50, border: Border.all(color: AppThemeData.info300), borderRadius: BorderRadius.circular(12)),
|
||||
child: Text(onProviderOrder.status, style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.info500)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
onProviderOrder.provider.title.toString(),
|
||||
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.only(top: 6), child: buildPriceText(onProviderOrder)),
|
||||
const SizedBox(height: 6),
|
||||
if (onProviderOrder.status != Constant.orderCompleted &&
|
||||
onProviderOrder.status != Constant.orderCancelled &&
|
||||
onProviderOrder.otp != null &&
|
||||
onProviderOrder.otp!.isNotEmpty)
|
||||
Text(
|
||||
"${'OTP :'.tr} ${onProviderOrder.otp}",
|
||||
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// Bottom Details (Date, Provider, Worker)
|
||||
buildBottomDetails(context, onProviderOrder, isDark, worker),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPriceText(OnProviderOrderModel order) {
|
||||
final hasDiscount = order.provider.disPrice != "" && order.provider.disPrice != "0";
|
||||
final price = hasDiscount ? order.provider.disPrice.toString() : order.provider.price.toString();
|
||||
|
||||
return Text(
|
||||
order.provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: price) : "${Constant.amountShow(amount: price)}/${'hr'.tr}",
|
||||
style: AppThemeData.mediumTextStyle(fontSize: 16, color: AppThemeData.primary300),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBottomDetails(BuildContext context, OnProviderOrderModel order, bool isDark, WorkerModel? worker) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey400 : AppThemeData.grey100, width: 1),
|
||||
color: isDark ? AppThemeData.greyDark100 : AppThemeData.grey100,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
detailRow("Date & Time", DateFormat('dd-MMM-yyyy hh:mm a').format(order.scheduleDateTime!.toDate()), isDark),
|
||||
const Divider(thickness: 1),
|
||||
detailRow("Provider", order.provider.authorName.toString(), isDark),
|
||||
|
||||
if (order.provider.priceUnit == "Hourly") ...[
|
||||
if (order.startTime != null) ...[const Divider(thickness: 1), detailRow("Start Time", DateFormat('dd-MMM-yyyy hh:mm a').format(order.startTime!.toDate()), isDark)],
|
||||
if (order.endTime != null) ...[const Divider(thickness: 1), detailRow("End Time", DateFormat('dd-MMM-yyyy hh:mm a').format(order.endTime!.toDate()), isDark)],
|
||||
],
|
||||
|
||||
if (worker != null) ...[const Divider(thickness: 1), detailRow("Worker", worker.fullName().toString(), isDark)],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget detailRow(String label, String value, bool isDark) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label.tr, style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
Text(value.tr, style: AppThemeData.regularTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
557
lib/screen_ui/on_demand_service/on_demand_booking_screen.dart
Normal file
557
lib/screen_ui/on_demand_service/on_demand_booking_screen.dart
Normal file
@@ -0,0 +1,557 @@
|
||||
import 'package:bottom_picker/bottom_picker.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../controllers/on_demand_booking_controller.dart';
|
||||
import '../../models/tax_model.dart';
|
||||
import '../../models/user_model.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import '../../themes/round_button_fill.dart';
|
||||
import '../../themes/show_toast_dialog.dart';
|
||||
import '../../themes/text_field_widget.dart';
|
||||
import '../../widget/osm_map/map_picker_page.dart';
|
||||
import '../../widget/place_picker/location_picker_screen.dart';
|
||||
import '../../widget/place_picker/selected_location_model.dart';
|
||||
import '../location_enable_screens/address_list_screen.dart';
|
||||
|
||||
class OnDemandBookingScreen extends StatelessWidget {
|
||||
const OnDemandBookingScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return GetX(
|
||||
init: OnDemandBookingController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text("Book Service".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Services Section
|
||||
Text("Services".tr, style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey100),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(controller.provider.value?.title ?? '', style: AppThemeData.mediumTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
const SizedBox(height: 5),
|
||||
Text(controller.categoryTitle.value, style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
if (controller.provider.value?.priceUnit == "Fixed") ...[
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(onTap: controller.decrementQuantity, child: Icon(Icons.remove_circle_outline, color: AppThemeData.primary300, size: 30)),
|
||||
const SizedBox(width: 10),
|
||||
Text('${controller.quantity.value}', style: AppThemeData.mediumTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
const SizedBox(width: 10),
|
||||
GestureDetector(onTap: controller.incrementQuantity, child: Icon(Icons.add_circle_outline, color: AppThemeData.primary300, size: 30)),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey.shade300,
|
||||
image: controller.provider.value!.photos.isNotEmpty ? DecorationImage(image: NetworkImage(controller.provider.value?.photos.first), fit: BoxFit.cover) : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey100),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Address".tr, style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
SizedBox(height: 5),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
if (Constant.userModel != null) {
|
||||
Get.to(AddressListScreen())!.then((value) {
|
||||
if (value != null) {
|
||||
ShippingAddress shippingAddress = value;
|
||||
if (Constant.checkZoneCheck(shippingAddress.location!.latitude ?? 0.0, shippingAddress.location!.longitude ?? 0.0)) {
|
||||
controller.selectedAddress.value = shippingAddress;
|
||||
controller.calculatePrice();
|
||||
} else {
|
||||
ShowToastDialog.showToast("Service not available in this area".tr);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Constant.checkPermission(
|
||||
onTap: () async {
|
||||
ShowToastDialog.showLoader("Please wait...".tr);
|
||||
|
||||
ShippingAddress shippingAddress = ShippingAddress();
|
||||
|
||||
try {
|
||||
await Geolocator.requestPermission();
|
||||
await Geolocator.getCurrentPosition();
|
||||
ShowToastDialog.closeLoader();
|
||||
|
||||
if (Constant.selectedMapType == 'osm') {
|
||||
final result = await Get.to(() => MapPickerPage());
|
||||
if (result != null) {
|
||||
final firstPlace = result;
|
||||
final lat = firstPlace.coordinates.latitude;
|
||||
final lng = firstPlace.coordinates.longitude;
|
||||
final address = firstPlace.address;
|
||||
|
||||
shippingAddress.addressAs = "Home";
|
||||
shippingAddress.locality = address.toString();
|
||||
shippingAddress.location = UserLocation(latitude: lat, longitude: lng);
|
||||
|
||||
controller.selectedAddress.value = shippingAddress;
|
||||
Get.back();
|
||||
}
|
||||
} else {
|
||||
Get.to(LocationPickerScreen())!.then((value) async {
|
||||
if (value != null) {
|
||||
SelectedLocationModel selectedLocationModel = value;
|
||||
|
||||
shippingAddress.addressAs = "Home";
|
||||
shippingAddress.location = UserLocation(latitude: selectedLocationModel.latLng!.latitude, longitude: selectedLocationModel.latLng!.longitude);
|
||||
shippingAddress.locality = "Picked from Map";
|
||||
|
||||
controller.selectedAddress.value = shippingAddress;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
await placemarkFromCoordinates(19.228825, 72.854118).then((valuePlaceMaker) {
|
||||
Placemark placeMark = valuePlaceMaker[0];
|
||||
shippingAddress.location = UserLocation(latitude: 19.228825, longitude: 72.854118);
|
||||
String currentLocation =
|
||||
"${placeMark.name}, ${placeMark.subLocality}, ${placeMark.locality}, ${placeMark.administrativeArea}, ${placeMark.postalCode}, ${placeMark.country}";
|
||||
shippingAddress.locality = currentLocation;
|
||||
});
|
||||
|
||||
controller.selectedAddress.value = shippingAddress;
|
||||
ShowToastDialog.closeLoader();
|
||||
}
|
||||
},
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
controller.selectedAddress.value.getFullAddress(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppThemeData.mediumTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
TextFieldWidget(title: "Description".tr, hintText: "Enter Description".tr, controller: controller.descriptionController.value, maxLine: 5),
|
||||
const SizedBox(height: 10),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
BottomPicker.dateTime(
|
||||
onSubmit: (date) {
|
||||
controller.setDateTime(date);
|
||||
},
|
||||
minDateTime: DateTime.now(),
|
||||
buttonAlignment: MainAxisAlignment.center,
|
||||
displaySubmitButton: true,
|
||||
buttonSingleColor: AppThemeData.primary300,
|
||||
buttonPadding: 10,
|
||||
buttonWidth: 70,
|
||||
pickerTitle: Text("", style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
backgroundColor: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
pickerTextStyle: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
closeIconColor: isDark ? Colors.white : Colors.black,
|
||||
).show(context);
|
||||
},
|
||||
child: TextFieldWidget(title: "Booking Date & Slot".tr, hintText: "Choose Date and Time".tr, controller: controller.dateTimeController.value, enable: false),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
controller.provider.value?.priceUnit == "Fixed"
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
controller.couponList.isNotEmpty
|
||||
? SizedBox(
|
||||
height: 85,
|
||||
child: ListView.builder(
|
||||
itemCount: controller.couponList.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
final coupon = controller.couponList[index];
|
||||
return GestureDetector(onTap: () => controller.applyCoupon(coupon), child: buildOfferItem(controller, index, isDark));
|
||||
},
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
buildPromoCode(controller, isDark),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text("Price Detail".tr, style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
),
|
||||
priceTotalRow(controller, isDark),
|
||||
],
|
||||
)
|
||||
: SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: RoundedButtonFill(title: "Confirm".tr, color: AppThemeData.primary300, textColor: AppThemeData.grey50, onPress: () => controller.confirmBooking(context)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildOfferItem(OnDemandBookingController controller, int index, bool isDark) {
|
||||
return Obx(() {
|
||||
final coupon = controller.couponList[index];
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(7, 10, 7, 10),
|
||||
height: 85,
|
||||
child: DottedBorder(
|
||||
options: RoundedRectDottedBorderOptions(strokeWidth: 1, radius: const Radius.circular(10), color: AppThemeData.primary300),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 5, 12, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Image(image: AssetImage('assets/images/offer_icon.png'), height: 25, width: 25),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 3),
|
||||
child: Text(
|
||||
coupon.discountType == "Fix Price" ? "${Constant.amountShow(amount: coupon.discount.toString())} ${'OFF'.tr}" : "${coupon.discount} ${'% Off'.tr}",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 0.7, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(coupon.code ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, letterSpacing: 0.5, color: Colors.orange)),
|
||||
Container(margin: const EdgeInsets.only(left: 15, right: 15, top: 3), width: 1, color: AppThemeData.grey50),
|
||||
Text(
|
||||
"valid till ".tr + controller.getDate(coupon.expiresAt!.toDate().toString()),
|
||||
style: TextStyle(letterSpacing: 0.5, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildPromoCode(OnDemandBookingController controller, bool isDark) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 10, bottom: 13),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey100),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15.0, horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset("assets/images/reedem.png", height: 50, width: 50),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Promo Code".tr, style: AppThemeData.mediumTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900), overflow: TextOverflow.ellipsis),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"Apply promo code".tr,
|
||||
style: AppThemeData.mediumTextStyle(fontSize: 15, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FloatingActionButton(
|
||||
onPressed: () {
|
||||
Get.bottomSheet(promoCodeSheet(controller, isDark), isScrollControlled: true, isDismissible: true, backgroundColor: Colors.transparent, enableDrag: true);
|
||||
},
|
||||
mini: true,
|
||||
backgroundColor: Colors.blueGrey.shade50,
|
||||
elevation: 0,
|
||||
child: const Icon(Icons.add, color: Colors.black54),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget promoCodeSheet(OnDemandBookingController controller, bool isDark) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: Get.height / 4.3, left: 25, right: 25),
|
||||
height: Get.height * 0.88,
|
||||
decoration: BoxDecoration(color: Colors.transparent, border: Border.all(style: BorderStyle.none)),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey100, width: 0.3),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, // ✅ visible color
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50),
|
||||
alignment: Alignment.center,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(padding: const EdgeInsets.only(top: 30), child: const Image(image: AssetImage('assets/images/redeem_coupon.png'), width: 100)),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Text('Redeem Your Coupons'.tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, fontSize: 16)),
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 10, left: 22, right: 22),
|
||||
child: Text("Voucher or Coupon code".tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 20),
|
||||
child: DottedBorder(
|
||||
options: RoundedRectDottedBorderOptions(strokeWidth: 1, radius: const Radius.circular(12), color: AppThemeData.primary300),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
alignment: Alignment.center,
|
||||
child: TextFormField(
|
||||
textAlign: TextAlign.center,
|
||||
style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
controller: controller.couponTextController.value,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: "Write Coupon Code".tr,
|
||||
hintStyle: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey400),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30, bottom: 30, left: 15, right: 15),
|
||||
child: RoundedButtonFill(
|
||||
title: "REDEEM NOW".tr,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey50,
|
||||
onPress: () {
|
||||
final inputCode = controller.couponTextController.value.text.trim().toLowerCase();
|
||||
|
||||
final matchingCoupon = controller.couponList.firstWhereOrNull((c) => c.code?.toLowerCase() == inputCode);
|
||||
|
||||
if (matchingCoupon != null) {
|
||||
controller.applyCoupon(matchingCoupon);
|
||||
Get.back();
|
||||
} else {
|
||||
ShowToastDialog.showToast("Applied coupon not valid.".tr);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget priceTotalRow(OnDemandBookingController controller, bool isDark) {
|
||||
return Obx(() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark400 : AppThemeData.grey100),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 5),
|
||||
rowText("Price".tr, Constant.amountShow(amount: controller.price.value.toString()), isDark),
|
||||
controller.discountAmount.value != 0 ? const Divider() : const SizedBox(),
|
||||
controller.discountAmount.value != 0
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${"Discount".tr} ${controller.discountType.value == 'Percentage' || controller.discountType.value == 'Percent' ? "(${controller.discountLabel.value}%)" : "(${Constant.amountShow(amount: controller.discountLabel.value)})"}",
|
||||
style: TextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
Text(controller.offerCode.value, style: TextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text("(-${Constant.amountShow(amount: controller.discountAmount.value.toString())})", style: const TextStyle(color: Colors.red)),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
const Divider(),
|
||||
rowText("SubTotal".tr, Constant.amountShow(amount: controller.subTotal.value.toString()), isDark),
|
||||
const Divider(),
|
||||
ListView.builder(
|
||||
itemCount: Constant.taxList.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
TaxModel taxModel = Constant.taxList[index];
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${taxModel.title} (${taxModel.type == "fix" ? Constant.amountShow(amount: taxModel.tax) : "${taxModel.tax}%"})",
|
||||
style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
Constant.amountShow(amount: Constant.getTaxValue(amount: controller.subTotal.value.toString(), taxModel: taxModel).toString()),
|
||||
style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
rowText("Total Amount".tr, Constant.amountShow(amount: controller.totalAmount.value.toString()), isDark),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget rowText(String title, String value, bool isDark) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title.tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
Text(value.tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/screen_ui/on_demand_service/on_demand_category_screen.dart
Normal file
101
lib/screen_ui/on_demand_service/on_demand_category_screen.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/view_category_service_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/on_demand_category_controller.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../models/category_model.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
|
||||
class OnDemandCategoryScreen extends StatelessWidget {
|
||||
const OnDemandCategoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX(
|
||||
init: OnDemandCategoryController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Explore services".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
Text(
|
||||
"Explore services tailored for you—quick, easy, and personalized.".tr,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.grey900),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
controller.categories.isEmpty
|
||||
? Center(child: Text("No Categories".tr))
|
||||
: GridView.builder(
|
||||
padding: const EdgeInsets.all(5),
|
||||
itemCount: controller.categories.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
|
||||
itemBuilder: (context, index) {
|
||||
return categoriesCell(context, controller.categories[index], index, isDark);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget categoriesCell(BuildContext context, CategoryModel category, int index, bool isDark) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(() => ViewCategoryServiceListScreen(), arguments: {'categoryId': category.id, 'categoryTitle': category.title});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(borderRadius: BorderRadius.circular(12), child: CachedNetworkImage(imageUrl: category.image ?? "", height: 60, width: 60, fit: BoxFit.cover)),
|
||||
const SizedBox(height: 5),
|
||||
Text(category.title ?? "", style: AppThemeData.semiBoldTextStyle(fontSize: 12, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900), textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/controllers/cab_dashboard_controller.dart';
|
||||
import 'package:customer/controllers/theme_controller.dart';
|
||||
import 'package:customer/themes/app_them_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../controllers/on_demand_dashboard_controller.dart';
|
||||
|
||||
class OnDemandDashboardScreen extends StatelessWidget {
|
||||
const OnDemandDashboardScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
return Obx(() {
|
||||
final isDark = themeController.isDark.value;
|
||||
return GetX(
|
||||
init: OnDemandDashboardController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
body: controller.pageList[controller.selectedIndex.value],
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showUnselectedLabels: true,
|
||||
showSelectedLabels: true,
|
||||
selectedFontSize: 12,
|
||||
selectedLabelStyle: const TextStyle(fontFamily: AppThemeData.bold),
|
||||
unselectedLabelStyle: const TextStyle(fontFamily: AppThemeData.bold),
|
||||
currentIndex: controller.selectedIndex.value,
|
||||
backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
|
||||
selectedItemColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
|
||||
unselectedItemColor: isDark ? AppThemeData.grey300 : AppThemeData.grey600,
|
||||
onTap: (int index) {
|
||||
if (index == 0) {
|
||||
Get.put(CabDashboardController());
|
||||
}
|
||||
controller.selectedIndex.value = index;
|
||||
},
|
||||
items:
|
||||
Constant.walletSetting == false
|
||||
? [
|
||||
navigationBarItem(isDark, index: 0, assetIcon: "assets/icons/ic_home_cab.svg", label: 'Home'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 1, assetIcon: "assets/icons/ic_fav.svg", label: 'Favourites'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 2, assetIcon: "assets/icons/ic_booking_cab.svg", label: 'My Bookings'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 3, assetIcon: "assets/icons/ic_profile.svg", label: 'Profile'.tr, controller: controller),
|
||||
]
|
||||
: [
|
||||
navigationBarItem(isDark, index: 0, assetIcon: "assets/icons/ic_home_cab.svg", label: 'Home'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 1, assetIcon: "assets/icons/ic_fav.svg", label: 'Favourites'.tr, controller: controller),
|
||||
|
||||
navigationBarItem(isDark, index: 2, assetIcon: "assets/icons/ic_booking_cab.svg", label: 'My Bookings'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 3, assetIcon: "assets/icons/ic_wallet_cab.svg", label: 'Wallet'.tr, controller: controller),
|
||||
navigationBarItem(isDark, index: 4, assetIcon: "assets/icons/ic_profile.svg", label: 'Profile'.tr, controller: controller),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
BottomNavigationBarItem navigationBarItem(isDark, {required int index, required String label, required String assetIcon, required OnDemandDashboardController controller}) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: SvgPicture.asset(
|
||||
assetIcon,
|
||||
height: 22,
|
||||
width: 22,
|
||||
color:
|
||||
controller.selectedIndex.value == index
|
||||
? isDark
|
||||
? AppThemeData.primary300
|
||||
: AppThemeData.primary300
|
||||
: isDark
|
||||
? AppThemeData.grey300
|
||||
: AppThemeData.grey600,
|
||||
),
|
||||
),
|
||||
label: label,
|
||||
);
|
||||
}
|
||||
}
|
||||
499
lib/screen_ui/on_demand_service/on_demand_details_screen.dart
Normal file
499
lib/screen_ui/on_demand_service/on_demand_details_screen.dart
Normal file
@@ -0,0 +1,499 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/provider_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../models/provider_serivce_model.dart';
|
||||
import '../../controllers/on_demand_details_controller.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import '../../themes/round_button_fill.dart';
|
||||
import '../auth_screens/login_screen.dart';
|
||||
import 'on_demand_booking_screen.dart';
|
||||
|
||||
class OnDemandDetailsScreen extends StatelessWidget {
|
||||
const OnDemandDetailsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return GetX<OnDemandDetailsController>(
|
||||
init: OnDemandDetailsController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
body: buildSliverScrollView(context, controller, controller.provider, controller.userModel, isDark),
|
||||
bottomNavigationBar:
|
||||
controller.isOpen.value == false
|
||||
? SizedBox()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
RoundedButtonFill(
|
||||
title: "Book Now".tr,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey50,
|
||||
onPress: () async {
|
||||
if (Constant.userModel == null) {
|
||||
Get.offAll(const LoginScreen());
|
||||
} else {
|
||||
print("providerModel ::::::::${controller.provider.title ?? 'No provider'}");
|
||||
print("categoryTitle ::::::: ${controller.categoryTitle.value}");
|
||||
Get.to(() => OnDemandBookingScreen(), arguments: {'providerModel': controller.provider, 'categoryTitle': controller.categoryTitle.value});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
SingleChildScrollView buildSliverScrollView(BuildContext context, OnDemandDetailsController controller, ProviderServiceModel provider, user, isDark) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: provider.photos.isNotEmpty ? provider.photos.first : "",
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.fitWidth),
|
||||
fit: BoxFit.fitWidth,
|
||||
width: width,
|
||||
height: height * 0.45,
|
||||
),
|
||||
Positioned(top: height * 0.05, left: width * 0.03, child: _circleButton(context, icon: Icons.arrow_back, onTap: () => Get.back())),
|
||||
Positioned(
|
||||
top: height * 0.05,
|
||||
right: width * 0.03,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(40), color: controller.isOpen.value ? Colors.green : Colors.red),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Text(controller.isOpen.value ? "Open".tr : "Close".tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white, fontSize: 14)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
child: GetBuilder<OnDemandDetailsController>(
|
||||
builder: (controller) {
|
||||
final provider = controller.provider;
|
||||
final categoryTitle = controller.categoryTitle.value;
|
||||
final subCategoryTitle = controller.subCategoryTitle.value;
|
||||
// final tabString = controller.tabString.value;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.title.toString(),
|
||||
style: TextStyle(fontSize: 20, fontFamily: AppThemeData.regular, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
provider.disPrice == "" || provider.disPrice == "0"
|
||||
? Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price ?? '0') : '${Constant.amountShow(amount: provider.price ?? '0')}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 18, fontFamily: AppThemeData.regular, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.disPrice ?? '0') : '${Constant.amountShow(amount: provider.disPrice ?? '0')}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 18, fontFamily: AppThemeData.regular, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price ?? '0') : '${Constant.amountShow(amount: provider.price ?? '0')}/${'hr'.tr}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.grey, decoration: TextDecoration.lineThrough),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Text(categoryTitle, style: TextStyle(fontSize: 14, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w400, color: isDark ? Colors.white : Colors.black)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.star, size: 16, color: AppThemeData.warning400),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
provider.reviewsCount != 0 ? ((provider.reviewsSum ?? 0.0) / (provider.reviewsCount ?? 0.0)).toStringAsFixed(1) : '0',
|
||||
style: const TextStyle(letterSpacing: 0.5, fontSize: 16, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w500, color: AppThemeData.warning400),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
"(${provider.reviewsCount} ${'Reviews'.tr})",
|
||||
style: TextStyle(letterSpacing: 0.5, fontSize: 16, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w500, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Row(
|
||||
children: [
|
||||
subCategoryTitle.isNotEmpty
|
||||
? Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: AppThemeData.primary300.withOpacity(0.20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Text(subCategoryTitle, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, fontFamily: AppThemeData.regular, color: AppThemeData.primary300)),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.green.withOpacity(0.20)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
isDismissible: true,
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
enableDrag: true,
|
||||
builder: (context) => showTiming(context, controller, isDark),
|
||||
);
|
||||
},
|
||||
child: Text("View Timing".tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.green, letterSpacing: 0.5)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.location_on_outlined, color: isDark ? Colors.white : Colors.black, size: 20),
|
||||
const SizedBox(width: 5),
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.address.toString(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontFamily: AppThemeData.regular, fontWeight: FontWeight.w400, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
_tabBar(controller),
|
||||
Obx(() {
|
||||
if (controller.tabString.value == "About") {
|
||||
return aboutTabViewWidget(controller, controller.provider, isDark);
|
||||
} else if (controller.tabString.value == "Gallery") {
|
||||
return galleryTabViewWidget(controller);
|
||||
} else {
|
||||
return reviewTabViewWidget(controller, isDark);
|
||||
}
|
||||
}),
|
||||
const SizedBox(height: 15),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _circleButton(BuildContext context, {required IconData icon, required VoidCallback onTap}) {
|
||||
return ClipOval(
|
||||
child: Container(color: Colors.black.withOpacity(0.7), child: InkWell(onTap: onTap, child: Padding(padding: const EdgeInsets.all(8.0), child: Icon(icon, size: 30, color: Colors.white)))),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tabBar(OnDemandDetailsController controller) {
|
||||
return Obx(() => Row(children: [_tabItem("About", controller), _tabItem("Gallery", controller), _tabItem("Review", controller)]));
|
||||
}
|
||||
|
||||
Widget _tabItem(String title, OnDemandDetailsController controller) {
|
||||
return GestureDetector(
|
||||
onTap: () => controller.changeTab(title),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(color: controller.tabString.value == title ? AppThemeData.primary300 : Colors.grey.shade200, borderRadius: BorderRadius.circular(10)),
|
||||
child: Text(title.tr, style: TextStyle(fontWeight: FontWeight.bold, color: controller.tabString.value == title ? Colors.white : Colors.black)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget aboutTabViewWidget(OnDemandDetailsController controller, ProviderServiceModel providerModel, bool isDark) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text((providerModel.description ?? '').tr, style: TextStyle(color: isDark ? Colors.white : Colors.black, fontSize: 14, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w500)),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() {
|
||||
final user = controller.userModel.value;
|
||||
if (user == null) return const SizedBox();
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.to(() => ProviderScreen(), arguments: {'providerId': user.id});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey500 : Colors.grey.shade100, width: 1),
|
||||
color: isDark ? AppThemeData.grey500 : Colors.white,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(radius: 30, backgroundImage: NetworkImage(user.profilePictureURL?.isNotEmpty == true ? user.profilePictureURL! : Constant.placeHolderImage)),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(user.fullName(), style: TextStyle(color: isDark ? Colors.white : Colors.black, fontFamily: AppThemeData.regular, fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 5),
|
||||
Text(user.email ?? '', style: TextStyle(color: isDark ? Colors.white : Colors.black, fontFamily: AppThemeData.regular, fontSize: 14)),
|
||||
const SizedBox(height: 10),
|
||||
// Rating Box
|
||||
Container(
|
||||
decoration: BoxDecoration(color: AppThemeData.warning400, borderRadius: BorderRadius.circular(16)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.star, size: 16, color: Colors.white),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
double.parse(user.reviewsCount.toString()) != 0
|
||||
? (double.parse(user.reviewsSum.toString()) / double.parse(user.reviewsCount.toString())).toStringAsFixed(1)
|
||||
: '0',
|
||||
style: const TextStyle(letterSpacing: 0.5, fontSize: 12, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget galleryTabViewWidget(OnDemandDetailsController controller) {
|
||||
final photos = controller.provider.photos;
|
||||
|
||||
if (photos.isEmpty) {
|
||||
return Center(child: Text("No Image Found".tr));
|
||||
}
|
||||
|
||||
return GridView.builder(
|
||||
itemCount: photos.length,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 0, crossAxisSpacing: 8, mainAxisExtent: 180),
|
||||
itemBuilder: (context, index) {
|
||||
final imageUrl = photos[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
height: 60,
|
||||
width: 60,
|
||||
imageBuilder: (context, imageProvider) => Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), image: DecorationImage(image: imageProvider, fit: BoxFit.cover))),
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget reviewTabViewWidget(OnDemandDetailsController controller, bool isDark) {
|
||||
final reviews = controller.ratingService;
|
||||
|
||||
if (reviews.isEmpty) {
|
||||
return SizedBox(height: 200, child: Center(child: Text("No review Found".tr, style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900))));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: reviews.length,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final review = reviews[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: ShapeDecoration(
|
||||
color: isDark ? AppThemeData.grey700 : AppThemeData.grey50,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shadows: const [BoxShadow(color: Color(0x0A000000), blurRadius: 32, offset: Offset(0, 0), spreadRadius: 0)],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(review.uname ?? '', style: TextStyle(fontSize: 16, letterSpacing: 1, fontWeight: FontWeight.w600, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
Text(
|
||||
review.createdAt != null ? DateFormat('dd MMM').format(review.createdAt!.toDate()) : '',
|
||||
style: TextStyle(fontSize: 12, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
RatingBar.builder(
|
||||
initialRating: double.tryParse(review.rating.toString()) ?? 0,
|
||||
direction: Axis.horizontal,
|
||||
itemSize: 20,
|
||||
ignoreGestures: true,
|
||||
itemPadding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
itemBuilder: (context, _) => Icon(Icons.star, color: AppThemeData.primary300),
|
||||
onRatingUpdate: (rate) {},
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 5),
|
||||
Text(review.comment ?? '', style: TextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget showTiming(BuildContext context, OnDemandDetailsController controller, bool isDark) {
|
||||
final provider = controller.provider;
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: isDark ? AppThemeData.grey300 : Colors.white, borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20))),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
||||
child: Text("Service Timing".tr, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, fontFamily: AppThemeData.regular, color: AppThemeData.primary300)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _timeCard(context, "Start Time : ".tr, provider.startTime.toString(), isDark)),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: _timeCard(context, "End Time : ".tr, provider.endTime.toString(), isDark)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
|
||||
child: Text("Service Days".tr, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, fontFamily: AppThemeData.regular, color: AppThemeData.primary300)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Wrap(
|
||||
spacing: 6.0,
|
||||
runSpacing: 6.0,
|
||||
children:
|
||||
provider.days
|
||||
.map(
|
||||
(day) => Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6), side: BorderSide(color: isDark ? const Color(0XFF3c3a2e) : const Color(0XFFC3C5D1), width: 1)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7, horizontal: 20),
|
||||
child: Text(day, style: TextStyle(color: isDark ? const Color(0XFFa5a292) : const Color(0XFF5A5D6D))),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _timeCard(BuildContext context, String title, String value, bool isDark) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6), side: BorderSide(color: isDark ? const Color(0XFF3c3a2e) : const Color(0XFFC3C5D1), width: 1)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7, horizontal: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(title, style: TextStyle(color: isDark ? const Color(0XFFa5a292) : const Color(0XFF5A5D6D))),
|
||||
Text(value, style: TextStyle(color: isDark ? const Color(0XFFa5a292) : const Color(0XFF5A5D6D))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
532
lib/screen_ui/on_demand_service/on_demand_home_screen.dart
Normal file
532
lib/screen_ui/on_demand_service/on_demand_home_screen.dart
Normal file
@@ -0,0 +1,532 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/controllers/theme_controller.dart';
|
||||
import 'package:customer/models/banner_model.dart';
|
||||
import 'package:customer/models/user_model.dart';
|
||||
import 'package:customer/screen_ui/auth_screens/login_screen.dart';
|
||||
import 'package:customer/screen_ui/location_enable_screens/address_list_screen.dart';
|
||||
import 'package:customer/screen_ui/location_enable_screens/location_permission_screen.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/view_all_popular_service_screen.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/view_category_service_screen.dart';
|
||||
import 'package:customer/themes/app_them_data.dart';
|
||||
import 'package:customer/themes/round_button_fill.dart';
|
||||
import 'package:customer/themes/show_toast_dialog.dart';
|
||||
import 'package:customer/utils/network_image_widget.dart';
|
||||
import 'package:customer/widget/osm_map/map_picker_page.dart';
|
||||
import 'package:customer/widget/place_picker/location_picker_screen.dart';
|
||||
import 'package:customer/widget/place_picker/selected_location_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../controllers/on_demand_home_controller.dart';
|
||||
import '../../models/category_model.dart';
|
||||
import '../../models/provider_serivce_model.dart';
|
||||
import 'on_demand_category_screen.dart';
|
||||
import 'on_demand_details_screen.dart';
|
||||
|
||||
class OnDemandHomeScreen extends StatelessWidget {
|
||||
const OnDemandHomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX<OnDemandHomeController>(
|
||||
init: OnDemandHomeController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: const BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: const Center(child: Padding(padding: EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Constant.userModel == null
|
||||
? InkWell(onTap: () => Get.offAll(const LoginScreen()), child: Text("Login".tr, style: AppThemeData.boldTextStyle(color: AppThemeData.grey900, fontSize: 12)))
|
||||
: Text(Constant.userModel!.fullName(), style: AppThemeData.boldTextStyle(color: AppThemeData.grey900, fontSize: 12)),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
if (Constant.userModel != null) {
|
||||
Get.to(AddressListScreen())!.then((value) {
|
||||
if (value != null) {
|
||||
ShippingAddress shippingAddress = value;
|
||||
Constant.selectedLocation = shippingAddress;
|
||||
controller.getData();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Constant.checkPermission(
|
||||
onTap: () async {
|
||||
ShowToastDialog.showLoader("Please wait...".tr);
|
||||
|
||||
// ✅ declare it once here!
|
||||
ShippingAddress shippingAddress = ShippingAddress();
|
||||
|
||||
try {
|
||||
await Geolocator.requestPermission();
|
||||
await Geolocator.getCurrentPosition();
|
||||
ShowToastDialog.closeLoader();
|
||||
|
||||
if (Constant.selectedMapType == 'osm') {
|
||||
final result = await Get.to(() => MapPickerPage());
|
||||
if (result != null) {
|
||||
final firstPlace = result;
|
||||
final lat = firstPlace.coordinates.latitude;
|
||||
final lng = firstPlace.coordinates.longitude;
|
||||
final address = firstPlace.address;
|
||||
|
||||
shippingAddress.addressAs = "Home";
|
||||
shippingAddress.locality = address.toString();
|
||||
shippingAddress.location = UserLocation(latitude: lat, longitude: lng);
|
||||
Constant.selectedLocation = shippingAddress;
|
||||
controller.getData();
|
||||
Get.back();
|
||||
}
|
||||
} else {
|
||||
Get.to(LocationPickerScreen())!.then((value) async {
|
||||
if (value != null) {
|
||||
SelectedLocationModel selectedLocationModel = value;
|
||||
|
||||
shippingAddress.addressAs = "Home";
|
||||
shippingAddress.location = UserLocation(latitude: selectedLocationModel.latLng!.latitude, longitude: selectedLocationModel.latLng!.longitude);
|
||||
shippingAddress.locality = "Picked from Map"; // You can reverse-geocode
|
||||
|
||||
Constant.selectedLocation = shippingAddress;
|
||||
controller.getData();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
await placemarkFromCoordinates(19.228825, 72.854118).then((valuePlaceMaker) {
|
||||
Placemark placeMark = valuePlaceMaker[0];
|
||||
shippingAddress.location = UserLocation(latitude: 19.228825, longitude: 72.854118);
|
||||
String currentLocation =
|
||||
"${placeMark.name}, ${placeMark.subLocality}, ${placeMark.locality}, ${placeMark.administrativeArea}, ${placeMark.postalCode}, ${placeMark.country}";
|
||||
shippingAddress.locality = currentLocation;
|
||||
});
|
||||
|
||||
Constant.selectedLocation = shippingAddress;
|
||||
ShowToastDialog.closeLoader();
|
||||
controller.getData();
|
||||
}
|
||||
},
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text.rich(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: Constant.selectedLocation.getFullAddress(),
|
||||
style: TextStyle(fontFamily: AppThemeData.medium, overflow: TextOverflow.ellipsis, color: AppThemeData.grey900, fontSize: 14),
|
||||
),
|
||||
WidgetSpan(child: SvgPicture.asset("assets/icons/ic_down.svg")),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: Constant.isZoneAvailable == false || controller.providerList.isEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset("assets/images/location.gif", height: 120),
|
||||
const SizedBox(height: 12),
|
||||
Text("No Store Found in Your Area".tr, style: TextStyle(color: isDark ? AppThemeData.grey100 : AppThemeData.grey800, fontSize: 22, fontFamily: AppThemeData.semiBold)),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"Currently, there are no available store in your zone. Try changing your location to find nearby options.".tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: isDark ? AppThemeData.grey50 : AppThemeData.grey500, fontSize: 16, fontFamily: AppThemeData.bold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
RoundedButtonFill(
|
||||
title: "Change Zone".tr,
|
||||
width: 55,
|
||||
height: 5.5,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey50,
|
||||
onPress: () async {
|
||||
Get.offAll(const LocationPermissionScreen());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BannerView(bannerList: controller.bannerTopHome),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.12,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark600 : AppThemeData.greyDark600, width: 1),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
),
|
||||
child:
|
||||
controller.categories.isEmpty
|
||||
? Constant.showEmptyView(message: "No Categories".tr)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.categories.length > 3 ? 3 : controller.categories.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
final category = controller.categories[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.to(() => ViewCategoryServiceListScreen(), arguments: {'categoryId': category.id, 'categoryTitle': category.title});
|
||||
},
|
||||
child: CategoryView(category: category, index: index, isDark: isDark),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (controller.categories.length > 3)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.to(() => const OnDemandCategoryScreen());
|
||||
},
|
||||
child: ClipOval(child: Container(width: 50, height: 50, color: AppThemeData.grey200, child: const Center(child: Icon(Icons.chevron_right)))),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
SizedBox(
|
||||
width: 70,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"View All".tr,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: AppThemeData.semiBoldTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Most Popular services".tr,
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontSize: 18, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.to(() => ViewAllPopularServiceScreen());
|
||||
},
|
||||
child: Text("View all".tr, style: TextStyle(color: AppThemeData.primary300, fontSize: 14, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
controller.providerList.isEmpty
|
||||
? Center(child: Text("No Services Found".tr))
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: controller.providerList.length >= 6 ? 6 : controller.providerList.length,
|
||||
itemBuilder: (_, index) {
|
||||
return ServiceView(provider: controller.providerList[index], controller: controller, isDark: isDark);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BannerView extends StatelessWidget {
|
||||
final List<BannerModel> bannerList;
|
||||
final RxInt currentPage = 0.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
BannerView({super.key, required this.bannerList});
|
||||
|
||||
void onScroll(BuildContext context) {
|
||||
if (scrollController.hasClients && bannerList.isNotEmpty) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final itemWidth = screenWidth * 0.8 + 10; // banner width + spacing
|
||||
final offset = scrollController.offset;
|
||||
final index = (offset / itemWidth).round();
|
||||
|
||||
if (index != currentPage.value && index < bannerList.length) {
|
||||
currentPage.value = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
scrollController.addListener(() {
|
||||
onScroll(context);
|
||||
});
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 150,
|
||||
child: ListView.separated(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: bannerList.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 15),
|
||||
itemBuilder: (context, index) {
|
||||
final banner = bannerList[index];
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: SizedBox(width: MediaQuery.of(context).size.width * 0.8, child: NetworkImageWidget(imageUrl: banner.photo ?? '', fit: BoxFit.cover)),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Obx(() {
|
||||
return Row(
|
||||
children: List.generate(bannerList.length, (index) {
|
||||
bool isSelected = currentPage.value == index;
|
||||
return Expanded(child: Container(height: 4, decoration: BoxDecoration(color: isSelected ? AppThemeData.grey300 : AppThemeData.grey100, borderRadius: BorderRadius.circular(5))));
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryView extends StatelessWidget {
|
||||
final CategoryModel category;
|
||||
final int index;
|
||||
final bool isDark;
|
||||
|
||||
const CategoryView({super.key, required this.category, required this.index, required this.isDark});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
height: 55,
|
||||
width: 55,
|
||||
decoration: BoxDecoration(color: Constant.colorList[index % Constant.colorList.length], borderRadius: BorderRadius.circular(50)),
|
||||
child: ClipOval(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: CachedNetworkImage(imageUrl: category.image.toString(), errorWidget: (_, __, ___) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
SizedBox(
|
||||
width: 70,
|
||||
child: Center(
|
||||
child: Text(category.title ?? "", textAlign: TextAlign.center, maxLines: 1, style: AppThemeData.semiBoldTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceView extends StatelessWidget {
|
||||
final ProviderServiceModel provider;
|
||||
final bool isDark;
|
||||
final OnDemandHomeController? controller;
|
||||
|
||||
const ServiceView({super.key, required this.provider, this.isDark = false, this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.to(() => OnDemandDetailsScreen(), arguments: {'providerModel': provider});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: isDark ? AppThemeData.grey500 : Colors.grey.shade200),
|
||||
color: isDark ? AppThemeData.grey900 : Colors.white,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// --- Left Image ---
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: provider.photos.isNotEmpty ? provider.photos[0] : Constant.placeHolderImage,
|
||||
width: 110,
|
||||
height: MediaQuery.of(context).size.height * 0.16,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
|
||||
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
|
||||
// --- Right Content ---
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title + Favourite icon
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.title ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black),
|
||||
),
|
||||
),
|
||||
if (controller != null)
|
||||
Obx(
|
||||
() => GestureDetector(
|
||||
onTap: () => controller!.toggleFavourite(provider),
|
||||
child: Icon(
|
||||
controller!.lstFav.where((element) => element.service_id == provider.id).isNotEmpty ? Icons.favorite : Icons.favorite_border,
|
||||
size: 24,
|
||||
color: controller!.lstFav.where((element) => element.service_id == provider.id).isNotEmpty ? AppThemeData.primary300 : (isDark ? Colors.white38 : Colors.black38),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// Category
|
||||
if (controller != null)
|
||||
FutureBuilder<CategoryModel?>(
|
||||
future: controller!.getCategory(provider.categoryId ?? ""),
|
||||
builder: (ctx, snap) {
|
||||
if (!snap.hasData) return const SizedBox.shrink();
|
||||
return Text(snap.data?.title ?? "", style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.black54));
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// Price
|
||||
_buildPrice(),
|
||||
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// Rating
|
||||
_buildRating(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrice() {
|
||||
if (provider.disPrice == "" || provider.disPrice == "0") {
|
||||
return Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price) : '${Constant.amountShow(amount: provider.price ?? "0")}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.disPrice ?? '0') : '${Constant.amountShow(amount: provider.disPrice)}/${'hr'.tr}',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: isDark ? Colors.white : AppThemeData.primary300),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
provider.priceUnit == 'Fixed' ? Constant.amountShow(amount: provider.price) : '${Constant.amountShow(amount: provider.price ?? "0")}/hr',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey, decoration: TextDecoration.lineThrough),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRating() {
|
||||
double rating = 0;
|
||||
if (provider.reviewsCount != null && provider.reviewsCount != 0) {
|
||||
rating = (provider.reviewsSum ?? 0) / (provider.reviewsCount ?? 1);
|
||||
}
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: AppThemeData.warning400, borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [const Icon(Icons.star, size: 14, color: Colors.white), const SizedBox(width: 3), Text(rating.toStringAsFixed(1), style: const TextStyle(fontSize: 12, color: Colors.white))],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
1043
lib/screen_ui/on_demand_service/on_demand_order_details_screen.dart
Normal file
1043
lib/screen_ui/on_demand_service/on_demand_order_details_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
263
lib/screen_ui/on_demand_service/on_demand_payment_screen.dart
Normal file
263
lib/screen_ui/on_demand_service/on_demand_payment_screen.dart
Normal file
@@ -0,0 +1,263 @@
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../controllers/0n_demand_payment_controller.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../payment/createRazorPayOrderModel.dart';
|
||||
import '../../payment/rozorpayConroller.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import '../../themes/round_button_fill.dart';
|
||||
import '../../themes/show_toast_dialog.dart';
|
||||
import '../multi_vendor_service/wallet_screen/wallet_screen.dart';
|
||||
|
||||
class OnDemandPaymentScreen extends StatelessWidget {
|
||||
const OnDemandPaymentScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX<OnDemandPaymentController>(
|
||||
init: OnDemandPaymentController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text("Select Payment Method".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
||||
decoration: BoxDecoration(color: isDark ? AppThemeData.greyDark200 : Colors.white),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Preferred Payment".tr, textAlign: TextAlign.start, style: AppThemeData.boldTextStyle(fontSize: 15, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
|
||||
const SizedBox(height: 10),
|
||||
if (controller.walletSettingModel.value.isEnabled == true || controller.cashOnDeliverySettingModel.value.isEnabled == true)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: controller.walletSettingModel.value.isEnabled == true,
|
||||
child: cardDecoration(controller, PaymentGateway.wallet, isDark, "assets/images/ic_wallet.png"),
|
||||
),
|
||||
Visibility(
|
||||
visible: controller.cashOnDeliverySettingModel.value.isEnabled == true,
|
||||
child: cardDecoration(controller, PaymentGateway.cod, isDark, "assets/images/ic_cash.png"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.walletSettingModel.value.isEnabled == true || controller.cashOnDeliverySettingModel.value.isEnabled == true)
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
"Other Payment Options".tr,
|
||||
textAlign: TextAlign.start,
|
||||
style: AppThemeData.boldTextStyle(fontSize: 15, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
border: Border.all(color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Visibility(visible: controller.stripeModel.value.isEnabled == true, child: cardDecoration(controller, PaymentGateway.stripe, isDark, "assets/images/stripe.png")),
|
||||
Visibility(visible: controller.payPalModel.value.isEnabled == true, child: cardDecoration(controller, PaymentGateway.paypal, isDark, "assets/images/paypal.png")),
|
||||
Visibility(
|
||||
visible: controller.payStackModel.value.isEnable == true,
|
||||
child: cardDecoration(controller, PaymentGateway.payStack, isDark, "assets/images/paystack.png"),
|
||||
),
|
||||
Visibility(
|
||||
visible: controller.mercadoPagoModel.value.isEnabled == true,
|
||||
child: cardDecoration(controller, PaymentGateway.mercadoPago, isDark, "assets/images/mercado-pago.png"),
|
||||
),
|
||||
Visibility(
|
||||
visible: controller.flutterWaveModel.value.isEnable == true,
|
||||
child: cardDecoration(controller, PaymentGateway.flutterWave, isDark, "assets/images/flutterwave_logo.png"),
|
||||
),
|
||||
Visibility(visible: controller.payFastModel.value.isEnable == true, child: cardDecoration(controller, PaymentGateway.payFast, isDark, "assets/images/payfast.png")),
|
||||
Visibility(
|
||||
visible: controller.razorPayModel.value.isEnabled == true,
|
||||
child: cardDecoration(controller, PaymentGateway.razorpay, isDark, "assets/images/razorpay.png"),
|
||||
),
|
||||
Visibility(visible: controller.midTransModel.value.enable == true, child: cardDecoration(controller, PaymentGateway.midTrans, isDark, "assets/images/midtrans.png")),
|
||||
Visibility(
|
||||
visible: controller.orangeMoneyModel.value.enable == true,
|
||||
child: cardDecoration(controller, PaymentGateway.orangeMoney, isDark, "assets/images/orange_money.png"),
|
||||
),
|
||||
Visibility(visible: controller.xenditModel.value.enable == true, child: cardDecoration(controller, PaymentGateway.xendit, isDark, "assets/images/xendit.png")),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
RoundedButtonFill(
|
||||
title: "Continue".tr,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey900,
|
||||
onPress: () async {
|
||||
print("getTotalAmount :::::::: ${"${controller.totalAmount.value}"}");
|
||||
if (controller.isOrderPlaced.value == false) {
|
||||
controller.isOrderPlaced.value = true;
|
||||
if (controller.selectedPaymentMethod.value == PaymentGateway.stripe.name) {
|
||||
controller.stripeMakePayment(amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.paypal.name) {
|
||||
controller.paypalPaymentSheet("${controller.totalAmount.value}", context);
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.payStack.name) {
|
||||
controller.payStackPayment("${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.mercadoPago.name) {
|
||||
controller.mercadoPagoMakePayment(context: context, amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.flutterWave.name) {
|
||||
controller.flutterWaveInitiatePayment(context: context, amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.payFast.name) {
|
||||
controller.payFastPayment(context: context, amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.wallet.name) {
|
||||
double totalAmount = double.parse("${controller.totalAmount.value}");
|
||||
double walletAmount = double.tryParse(Constant.userModel?.walletAmount?.toString() ?? "0") ?? 0;
|
||||
|
||||
if (walletAmount == 0) {
|
||||
ShowToastDialog.showToast("Wallet balance is 0. Please recharge wallet.".tr);
|
||||
} else if (walletAmount < totalAmount) {
|
||||
ShowToastDialog.showToast("Insufficient wallet balance. Please add funds.".tr);
|
||||
} else {
|
||||
controller.placeOrder();
|
||||
}
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.cod.name) {
|
||||
controller.placeOrder();
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.wallet.name) {
|
||||
controller.placeOrder();
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.midTrans.name) {
|
||||
controller.midtransMakePayment(context: context, amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.orangeMoney.name) {
|
||||
controller.orangeMakePayment(context: context, amount: "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.xendit.name) {
|
||||
controller.xenditPayment(context, "${controller.totalAmount.value}");
|
||||
} else if (controller.selectedPaymentMethod.value == PaymentGateway.razorpay.name) {
|
||||
RazorPayController().createOrderRazorPay(amount: double.parse("${controller.totalAmount.value}"), razorpayModel: controller.razorPayModel.value).then((value) {
|
||||
if (value == null) {
|
||||
Get.back();
|
||||
ShowToastDialog.showToast("Something went wrong, please contact admin.".tr);
|
||||
} else {
|
||||
CreateRazorPayOrderModel result = value;
|
||||
controller.openCheckout(amount: "${controller.totalAmount.value}", orderId: result.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
controller.isOrderPlaced.value = false;
|
||||
ShowToastDialog.showToast("Please select payment method".tr);
|
||||
}
|
||||
controller.isOrderPlaced.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Obx cardDecoration(controller, PaymentGateway value, isDark, String image) {
|
||||
return Obx(
|
||||
() => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
controller.selectedPaymentMethod.value = value.name;
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: ShapeDecoration(shape: RoundedRectangleBorder(side: const BorderSide(width: 1, color: Color(0xFFE5E7EB)), borderRadius: BorderRadius.circular(8))),
|
||||
child: Padding(padding: EdgeInsets.all(value.name == "payFast" ? 0 : 8.0), child: Image.asset(image)),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
value.name == "wallet"
|
||||
? Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
value.name.capitalizeString(),
|
||||
textAlign: TextAlign.start,
|
||||
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900),
|
||||
),
|
||||
Text(
|
||||
Constant.amountShow(amount: Constant.userModel?.walletAmount == null ? '0.0' : Constant.userModel?.walletAmount.toString()),
|
||||
textAlign: TextAlign.start,
|
||||
style: AppThemeData.semiBoldTextStyle(fontSize: 14, color: isDark ? AppThemeData.primary300 : AppThemeData.primary300),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: Text(
|
||||
value.name.capitalizeString(),
|
||||
textAlign: TextAlign.start,
|
||||
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900),
|
||||
),
|
||||
),
|
||||
const Expanded(child: SizedBox()),
|
||||
Radio(
|
||||
value: value.name,
|
||||
groupValue: controller.selectedPaymentMethod.value,
|
||||
activeColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
|
||||
onChanged: (value) {
|
||||
controller.selectedPaymentMethod.value = value.toString();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
122
lib/screen_ui/on_demand_service/on_demand_review_screen.dart
Normal file
122
lib/screen_ui/on_demand_service/on_demand_review_screen.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/on_demand_review_controller.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import '../../themes/round_button_fill.dart';
|
||||
import '../../themes/text_field_widget.dart';
|
||||
import '../../utils/network_image_widget.dart';
|
||||
|
||||
class OnDemandReviewScreen extends StatelessWidget {
|
||||
const OnDemandReviewScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetBuilder<OnDemandReviewController>(
|
||||
init: OnDemandReviewController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(controller.ratingModel.value != null ? "Update Review".tr : "Add Review".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Obx(
|
||||
() =>
|
||||
(controller.reviewFor.value == "Worker" && controller.workerModel.value == null) || (controller.reviewFor.value == "Provider" && controller.provider.value == null)
|
||||
? Constant.loader()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 50.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 42, bottom: 20),
|
||||
child: Card(
|
||||
elevation: 2,
|
||||
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 65),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Rate for'.tr, style: AppThemeData.mediumTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
|
||||
Text(
|
||||
controller.reviewFor.value == "Provider" ? controller.order.value!.provider.authorName ?? "" : controller.workerModel.value!.fullName(),
|
||||
style: AppThemeData.mediumTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: RatingBar.builder(
|
||||
initialRating: controller.ratings.value,
|
||||
minRating: 1,
|
||||
direction: Axis.horizontal,
|
||||
allowHalfRating: true,
|
||||
itemCount: 5,
|
||||
itemPadding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
itemBuilder: (context, _) => const Icon(Icons.star, color: Colors.amber),
|
||||
unratedColor: isDark ? AppThemeData.greyDark400 : AppThemeData.grey400,
|
||||
onRatingUpdate: (rating) {
|
||||
controller.ratings.value = rating;
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(20.0), child: TextFieldWidget(hintText: "Type comment....".tr, controller: controller.comment, maxLine: 5)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: RoundedButtonFill(
|
||||
title: controller.ratingModel.value != null ? "Update Review".tr : "Add Review".tr,
|
||||
color: AppThemeData.primary300,
|
||||
textColor: AppThemeData.grey50,
|
||||
onPress: controller.submitReview,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: NetworkImageWidget(
|
||||
imageUrl: controller.reviewFor.value == "Provider" ? controller.order.value?.provider.authorProfilePic ?? '' : controller.workerModel.value?.profilePictureURL ?? '',
|
||||
fit: BoxFit.cover,
|
||||
height: 100,
|
||||
width: 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
127
lib/screen_ui/on_demand_service/provider_inbox_screen.dart
Normal file
127
lib/screen_ui/on_demand_service/provider_inbox_screen.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/models/inbox_model.dart';
|
||||
import 'package:customer/models/user_model.dart';
|
||||
import 'package:customer/screen_ui/multi_vendor_service/chat_screens/chat_screen.dart';
|
||||
import 'package:customer/themes/app_them_data.dart';
|
||||
import 'package:customer/themes/responsive.dart';
|
||||
import 'package:customer/utils/network_image_widget.dart';
|
||||
import 'package:customer/widget/firebase_pagination/src/fireStore_pagination.dart';
|
||||
import 'package:customer/widget/firebase_pagination/src/models/view_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../controllers/theme_controller.dart';
|
||||
import '../../../service/fire_store_utils.dart';
|
||||
import '../../../themes/show_toast_dialog.dart';
|
||||
|
||||
class ProviderInboxScreen extends StatelessWidget {
|
||||
const ProviderInboxScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: isDark ? AppThemeData.surfaceDark : AppThemeData.surface,
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text("Provider Inbox".tr, textAlign: TextAlign.start, style: TextStyle(fontFamily: AppThemeData.medium, fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900)),
|
||||
),
|
||||
body: FirestorePagination(
|
||||
//item builder type is compulsory.
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, documentSnapshots, index) {
|
||||
final data = documentSnapshots[index].data() as Map<String, dynamic>?;
|
||||
InboxModel inboxModel = InboxModel.fromJson(data!);
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
ShowToastDialog.showLoader("Please wait...".tr);
|
||||
|
||||
UserModel? customer = await FireStoreUtils.getUserProfile(inboxModel.customerId.toString());
|
||||
UserModel? restaurantUser = await FireStoreUtils.getUserProfile(inboxModel.restaurantId.toString());
|
||||
ShowToastDialog.closeLoader();
|
||||
|
||||
Get.to(
|
||||
const ChatScreen(),
|
||||
arguments: {
|
||||
"customerName": customer!.fullName(),
|
||||
"restaurantName": restaurantUser!.fullName(),
|
||||
"orderId": inboxModel.orderId,
|
||||
"restaurantId": restaurantUser.id,
|
||||
"customerId": customer.id,
|
||||
"customerProfileImage": customer.profilePictureURL,
|
||||
"restaurantProfileImage": restaurantUser.profilePictureURL,
|
||||
"token": restaurantUser.fcmToken,
|
||||
"chatType": inboxModel.chatType,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5),
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(color: isDark ? AppThemeData.grey900 : AppThemeData.grey50, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
child: NetworkImageWidget(
|
||||
imageUrl: inboxModel.restaurantProfileImage.toString(),
|
||||
fit: BoxFit.cover,
|
||||
height: Responsive.height(6, context),
|
||||
width: Responsive.width(12, context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${inboxModel.restaurantName}",
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.semiBold, fontSize: 16, color: isDark ? AppThemeData.grey100 : AppThemeData.grey800),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
Constant.timestampToDate(inboxModel.createdAt!),
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.regular, fontSize: 16, color: isDark ? AppThemeData.grey400 : AppThemeData.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"${inboxModel.lastMessage}",
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.medium, fontSize: 14, color: isDark ? AppThemeData.grey200 : AppThemeData.grey700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
shrinkWrap: true,
|
||||
onEmpty: Constant.showEmptyView(message: "No Conversion found".tr),
|
||||
// orderBy is compulsory to enable pagination
|
||||
query: FirebaseFirestore.instance.collection('chat_provider').where("customerId", isEqualTo: FireStoreUtils.getCurrentUid()).orderBy('createdAt', descending: true),
|
||||
//Change types customerId
|
||||
viewType: ViewType.list,
|
||||
initialLoader: Constant.loader(),
|
||||
// to fetch real-time data
|
||||
isLive: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
116
lib/screen_ui/on_demand_service/provider_screen.dart
Normal file
116
lib/screen_ui/on_demand_service/provider_screen.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/screen_ui/on_demand_service/on_demand_home_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../controllers/provider_controller.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../models/provider_serivce_model.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
|
||||
class ProviderScreen extends StatelessWidget {
|
||||
const ProviderScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX<ProviderController>(
|
||||
init: ProviderController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(automaticallyImplyLeading: true),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Center(child: Constant.loader())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 50),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child:
|
||||
(controller.userModel.value?.profilePictureURL ?? "").isNotEmpty
|
||||
? CircleAvatar(backgroundImage: NetworkImage(controller.userModel.value?.profilePictureURL ?? ''), radius: 50.0)
|
||||
: CircleAvatar(backgroundImage: NetworkImage(Constant.placeHolderImage), radius: 50.0),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
controller.userModel.value?.fullName() ?? '',
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontFamily: AppThemeData.regular, fontSize: 20, fontWeight: FontWeight.w900),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset("assets/icons/ic_mail.svg", color: isDark ? Colors.white : Colors.black),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
controller.userModel.value?.email ?? '',
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontFamily: AppThemeData.regular, fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset("assets/icons/ic_mobile.svg", color: isDark ? Colors.white : Colors.black),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
controller.userModel.value?.phoneNumber ?? '',
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontFamily: AppThemeData.regular, fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: AppThemeData.warning400, borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.star, size: 16, color: Colors.white),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
_getRating(controller),
|
||||
style: const TextStyle(letterSpacing: 0.5, fontSize: 12, fontFamily: AppThemeData.regular, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
controller.providerList.isEmpty
|
||||
? Center(child: Text("No Services Found".tr))
|
||||
: Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.providerList.length,
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (context, index) {
|
||||
ProviderServiceModel data = controller.providerList[index];
|
||||
return ServiceView(provider: data, isDark: isDark, controller: controller.onDemandHomeController.value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _getRating(ProviderController controller) {
|
||||
final reviewsCount = double.tryParse(controller.userModel.value?.reviewsCount?.toString() ?? "0") ?? 0;
|
||||
final reviewsSum = double.tryParse(controller.userModel.value?.reviewsSum?.toString() ?? "0") ?? 0;
|
||||
|
||||
if (reviewsCount == 0) return "0";
|
||||
final avg = reviewsSum / reviewsCount;
|
||||
return avg.toStringAsFixed(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:customer/screen_ui/on_demand_service/on_demand_home_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../controllers/view_all_popular_service_controller.dart';
|
||||
import '../../models/provider_serivce_model.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
import '../../themes/text_field_widget.dart';
|
||||
|
||||
class ViewAllPopularServiceScreen extends StatelessWidget {
|
||||
const ViewAllPopularServiceScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return GetX<ViewAllPopularServiceController>(
|
||||
init: ViewAllPopularServiceController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text("All Services".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
child: Column(
|
||||
children: [
|
||||
TextFieldWidget(hintText: "Search Service".tr, controller: controller.searchTextFiledController.value, onchange: (value) => controller.getFilterData(value.toString())),
|
||||
const SizedBox(height: 15),
|
||||
controller.providerList.isEmpty
|
||||
? Expanded(child: Center(child: Constant.showEmptyView(message: "No service Found".tr)))
|
||||
: Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: controller.providerList.length,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemBuilder: (context, index) {
|
||||
ProviderServiceModel data = controller.providerList[index];
|
||||
return ServiceView(provider: data, isDark: isDark, controller: controller.onDemandHomeController.value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/constant.dart';
|
||||
import '../../controllers/theme_controller.dart';
|
||||
import '../../controllers/view_category_service_controller.dart';
|
||||
import '../../models/provider_serivce_model.dart';
|
||||
import '../../screen_ui/on_demand_service/on_demand_home_screen.dart';
|
||||
import '../../themes/app_them_data.dart';
|
||||
|
||||
class ViewCategoryServiceListScreen extends StatelessWidget {
|
||||
const ViewCategoryServiceListScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
|
||||
return GetX<ViewCategoryServiceController>(
|
||||
init: ViewCategoryServiceController(),
|
||||
builder: (controller) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: AppThemeData.primary300,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: 42,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: AppThemeData.grey50),
|
||||
child: Center(child: Padding(padding: const EdgeInsets.only(left: 5), child: Icon(Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20))),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(controller.categoryTitle.value, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body:
|
||||
controller.isLoading.value
|
||||
? Constant.loader()
|
||||
: controller.providerList.isEmpty
|
||||
? Constant.showEmptyView(message: "No Service Found".tr)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: ListView.builder(
|
||||
itemCount: controller.providerList.length,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (context, index) {
|
||||
ProviderServiceModel providerModel = controller.providerList[index];
|
||||
return ServiceView(isDark: isDark, provider: providerModel, controller: controller.onDemandHomeController.value);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
128
lib/screen_ui/on_demand_service/worker_inbox_screen.dart
Normal file
128
lib/screen_ui/on_demand_service/worker_inbox_screen.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:customer/constant/constant.dart';
|
||||
import 'package:customer/models/inbox_model.dart';
|
||||
import 'package:customer/models/user_model.dart';
|
||||
import 'package:customer/screen_ui/multi_vendor_service/chat_screens/chat_screen.dart';
|
||||
import 'package:customer/themes/app_them_data.dart';
|
||||
import 'package:customer/themes/responsive.dart';
|
||||
import 'package:customer/utils/network_image_widget.dart';
|
||||
import 'package:customer/widget/firebase_pagination/src/fireStore_pagination.dart';
|
||||
import 'package:customer/widget/firebase_pagination/src/models/view_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../controllers/theme_controller.dart';
|
||||
import '../../../service/fire_store_utils.dart';
|
||||
import '../../../themes/show_toast_dialog.dart';
|
||||
|
||||
class WorkerInboxScreen extends StatelessWidget {
|
||||
const WorkerInboxScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeController = Get.find<ThemeController>();
|
||||
final isDark = themeController.isDark.value;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: isDark ? AppThemeData.surfaceDark : AppThemeData.surface,
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text("Worker Inbox".tr, textAlign: TextAlign.start, style: TextStyle(fontFamily: AppThemeData.medium, fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900)),
|
||||
),
|
||||
body: FirestorePagination(
|
||||
//item builder type is compulsory.
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, documentSnapshots, index) {
|
||||
final data = documentSnapshots[index].data() as Map<String, dynamic>?;
|
||||
InboxModel inboxModel = InboxModel.fromJson(data!);
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
ShowToastDialog.showLoader("Please wait...".tr);
|
||||
|
||||
UserModel? customer = await FireStoreUtils.getUserProfile(inboxModel.customerId.toString());
|
||||
UserModel? restaurantUser = await FireStoreUtils.getUserProfile(inboxModel.restaurantId.toString());
|
||||
ShowToastDialog.closeLoader();
|
||||
|
||||
print("customerId: ${inboxModel.customerId}, restaurantId: ${inboxModel.restaurantId}");
|
||||
Get.to(
|
||||
const ChatScreen(),
|
||||
arguments: {
|
||||
"customerName": customer!.fullName(),
|
||||
"restaurantName": restaurantUser?.fullName() ?? '',
|
||||
"orderId": inboxModel.orderId,
|
||||
"restaurantId": restaurantUser?.id ?? '',
|
||||
"customerId": customer.id,
|
||||
"customerProfileImage": customer.profilePictureURL,
|
||||
"restaurantProfileImage": restaurantUser?.profilePictureURL,
|
||||
"token": restaurantUser?.fcmToken,
|
||||
"chatType": inboxModel.chatType,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5),
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(color: isDark ? AppThemeData.grey900 : AppThemeData.grey50, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
child: NetworkImageWidget(
|
||||
imageUrl: inboxModel.restaurantProfileImage.toString(),
|
||||
fit: BoxFit.cover,
|
||||
height: Responsive.height(6, context),
|
||||
width: Responsive.width(12, context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${inboxModel.restaurantName}",
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.semiBold, fontSize: 16, color: isDark ? AppThemeData.grey100 : AppThemeData.grey800),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
Constant.timestampToDate(inboxModel.createdAt!),
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.regular, fontSize: 16, color: isDark ? AppThemeData.grey400 : AppThemeData.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
"${inboxModel.lastMessage}",
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontFamily: AppThemeData.medium, fontSize: 14, color: isDark ? AppThemeData.grey200 : AppThemeData.grey700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
shrinkWrap: true,
|
||||
onEmpty: Constant.showEmptyView(message: "No Conversion found".tr),
|
||||
// orderBy is compulsory to enable pagination
|
||||
query: FirebaseFirestore.instance.collection('chat_worker').where("customerId", isEqualTo: FireStoreUtils.getCurrentUid()).orderBy('createdAt', descending: true),
|
||||
//Change types customerId
|
||||
viewType: ViewType.list,
|
||||
initialLoader: Constant.loader(),
|
||||
// to fetch real-time data
|
||||
isLive: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user