854 lines
30 KiB
Dart
854 lines
30 KiB
Dart
|
import 'dart:async';
|
||
|
import 'dart:ui';
|
||
|
|
||
|
import 'package:dio/dio.dart';
|
||
|
import 'package:ebroker/Ui/screens/widgets/AnimatedRoutes/blur_page_route.dart';
|
||
|
import 'package:ebroker/Ui/screens/widgets/blurred_dialoge_box.dart';
|
||
|
import 'package:ebroker/data/cubits/project/delete_project_cubit.dart';
|
||
|
import 'package:ebroker/data/cubits/project/fetchMyProjectsListCubit.dart';
|
||
|
import 'package:ebroker/exports/main_export.dart';
|
||
|
import 'package:ebroker/utils/CloudState/cloud_state.dart';
|
||
|
import 'package:ebroker/utils/Extensions/extensions.dart';
|
||
|
import 'package:ebroker/utils/helper_utils.dart';
|
||
|
import 'package:ebroker/utils/responsiveSize.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:flutter/services.dart';
|
||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||
|
import 'package:open_filex/open_filex.dart';
|
||
|
import 'package:url_launcher/url_launcher.dart';
|
||
|
|
||
|
import '../../../../data/helper/widgets.dart';
|
||
|
import '../../../../data/model/project_model.dart';
|
||
|
import '../../../../utils/AppIcon.dart';
|
||
|
import '../../../../utils/VideoPlayer/video_player_widget.dart';
|
||
|
import '../../../../utils/typedefs.dart';
|
||
|
import '../../../../utils/ui_utils.dart';
|
||
|
import '../../proprties/property_details.dart';
|
||
|
import '../../widgets/gallery_view.dart';
|
||
|
|
||
|
class ProjectDetailsScreen extends StatefulWidget {
|
||
|
final ProjectModel project;
|
||
|
static route(RouteSettings settings) {
|
||
|
Map? arguement = settings.arguments as Map?;
|
||
|
return BlurredRouter(
|
||
|
builder: (context) {
|
||
|
return BlocProvider(
|
||
|
create: (context) => DeleteProjectCubit(),
|
||
|
child: ProjectDetailsScreen(
|
||
|
project: arguement?['project'],
|
||
|
),
|
||
|
);
|
||
|
},
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const ProjectDetailsScreen({
|
||
|
super.key,
|
||
|
required this.project,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
CloudState<ProjectDetailsScreen> createState() =>
|
||
|
_ProjectDetailsScreenState();
|
||
|
}
|
||
|
|
||
|
class _ProjectDetailsScreenState extends CloudState<ProjectDetailsScreen> {
|
||
|
final Completer<GoogleMapController> _controller =
|
||
|
Completer<GoogleMapController>();
|
||
|
bool isMyProject = false;
|
||
|
late ProjectModel project;
|
||
|
|
||
|
late final CameraPosition _kInitialPlace = CameraPosition(
|
||
|
target: LatLng(
|
||
|
double.parse(project.latitude!),
|
||
|
double.parse(project.longitude!),
|
||
|
),
|
||
|
zoom: 14.4746,
|
||
|
);
|
||
|
|
||
|
@override
|
||
|
void initState() {
|
||
|
project = widget.project;
|
||
|
isMyProject = checkIsProjectMine();
|
||
|
|
||
|
super.initState();
|
||
|
}
|
||
|
|
||
|
bool checkIsProjectMine() {
|
||
|
return project.addedBy.toString() == HiveUtils.getUserId();
|
||
|
}
|
||
|
|
||
|
bool hasFloors() {
|
||
|
return project.plans!.isNotEmpty;
|
||
|
}
|
||
|
|
||
|
bool hasDocuments() {
|
||
|
return project.documents!.isNotEmpty;
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return Scaffold(
|
||
|
backgroundColor: context.color.backgroundColor,
|
||
|
bottomNavigationBar: BottomAppBar(
|
||
|
color: context.color.secondaryColor,
|
||
|
child: bottomNavigation(context),
|
||
|
),
|
||
|
body: Builder(builder: (context) {
|
||
|
return Padding(
|
||
|
padding: EdgeInsets.zero,
|
||
|
child: BlocListener<DeleteProjectCubit, DeleteProjectState>(
|
||
|
listener: (context, state) {
|
||
|
if (state is DeleteProjectInProgress) {
|
||
|
Widgets.showLoader(context);
|
||
|
}
|
||
|
|
||
|
if (state is DeleteProjectSuccess) {
|
||
|
Widgets.hideLoder(context);
|
||
|
context.read<FetchMyProjectsListCubit>().delete(state.id);
|
||
|
|
||
|
Navigator.pop(
|
||
|
context,
|
||
|
);
|
||
|
}
|
||
|
},
|
||
|
child: CustomScrollView(
|
||
|
physics: const BouncingScrollPhysics(),
|
||
|
slivers: [
|
||
|
SliverAppBar(
|
||
|
backgroundColor: context.color.secondaryColor,
|
||
|
leading: Material(
|
||
|
clipBehavior: Clip.antiAlias,
|
||
|
color: Colors.transparent,
|
||
|
type: MaterialType.circle,
|
||
|
child: InkWell(
|
||
|
onTap: () {
|
||
|
Navigator.pop(context);
|
||
|
},
|
||
|
child: Padding(
|
||
|
padding: const EdgeInsets.all(18.0),
|
||
|
child: UiUtils.getSvg(AppIcons.arrowLeft,
|
||
|
fit: BoxFit.none,
|
||
|
color: context.color.tertiaryColor),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
systemOverlayStyle: const SystemUiOverlayStyle(
|
||
|
statusBarColor: Colors.transparent),
|
||
|
expandedHeight: context.screenHeight * 0.45,
|
||
|
// toolbarHeight: 0,
|
||
|
primary: true,
|
||
|
automaticallyImplyLeading: true,
|
||
|
shape: RoundedRectangleBorder(
|
||
|
borderRadius: BorderRadius.circular(20),
|
||
|
),
|
||
|
// forceMaterialTransparency: true,
|
||
|
floating: false,
|
||
|
pinned: true,
|
||
|
|
||
|
// title: Text("I am title"),
|
||
|
centerTitle: true,
|
||
|
flexibleSpace: FlexibleSpaceBar(
|
||
|
collapseMode: CollapseMode.parallax,
|
||
|
titlePadding: EdgeInsets.zero,
|
||
|
background: ProjectImageCareusel(
|
||
|
images: [
|
||
|
...{project.image!},
|
||
|
...project.gallaryImages!.map((e) => e.name!)
|
||
|
],
|
||
|
),
|
||
|
)),
|
||
|
SliverToBoxAdapter(
|
||
|
child: Padding(
|
||
|
padding: const EdgeInsets.all(14.0),
|
||
|
child: Column(
|
||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
children: [
|
||
|
Row(
|
||
|
children: [
|
||
|
categoryCard(context, project),
|
||
|
const Spacer(),
|
||
|
Text(project.type!.translate(context))
|
||
|
.bold()
|
||
|
.size(context.font.small),
|
||
|
],
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
Text(project.title!)
|
||
|
.size(context.font.larger)
|
||
|
.bold(weight: FontWeight.w400),
|
||
|
const SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
Text(project.description!.trim()).color(
|
||
|
context.color.textColorDark.withOpacity(0.89)),
|
||
|
const SizedBox(
|
||
|
height: 18,
|
||
|
),
|
||
|
|
||
|
ContactDetailsWidget(
|
||
|
url: project.customer?.profile ?? "",
|
||
|
number: project.customer!.mobile!,
|
||
|
name: project.customer!.name!,
|
||
|
email: project.customer!.email!),
|
||
|
const SizedBox(
|
||
|
height: 14,
|
||
|
),
|
||
|
if (project.videoLink != null &&
|
||
|
project.videoLink!.isNotEmpty)
|
||
|
VideoPlayerWideget(
|
||
|
padding: const EdgeInsets.symmetric(vertical: 18),
|
||
|
url: project.videoLink!,
|
||
|
),
|
||
|
if (hasDocuments()) ...[
|
||
|
Text("Documents".translate(context))
|
||
|
.size(context.font.large)
|
||
|
.bold(),
|
||
|
ListView.builder(
|
||
|
shrinkWrap: true,
|
||
|
physics: const NeverScrollableScrollPhysics(),
|
||
|
itemBuilder: (context, index) {
|
||
|
Document? document = project.documents?[index];
|
||
|
|
||
|
return DownloadableDocument(
|
||
|
url: document!.name!,
|
||
|
);
|
||
|
// return ListTile(
|
||
|
// dense: true,
|
||
|
// title: Text(name).size(context.font.large).color(
|
||
|
// context.color.textColorDark.withOpacity(0.9)),
|
||
|
// trailing: IconButton(
|
||
|
// icon: const Icon(Icons.download),
|
||
|
// onPressed: () {},
|
||
|
// ),
|
||
|
// );
|
||
|
},
|
||
|
itemCount: project.documents?.length ?? 0,
|
||
|
),
|
||
|
],
|
||
|
const SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
if (hasFloors()) ...[
|
||
|
Text("Floor Plans".translate(context))
|
||
|
.size(context.font.large)
|
||
|
.bold(),
|
||
|
ListView.builder(
|
||
|
shrinkWrap: true,
|
||
|
physics: const NeverScrollableScrollPhysics(),
|
||
|
itemCount: project.plans?.length ?? 0,
|
||
|
itemBuilder: (context, index) {
|
||
|
Plan floor = project.plans![index];
|
||
|
return CustomExpansionTile(
|
||
|
title: floor.title!,
|
||
|
children: [Image.network(floor.document!)],
|
||
|
);
|
||
|
},
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 18,
|
||
|
),
|
||
|
],
|
||
|
|
||
|
// Container(
|
||
|
// width: context.screenWidth,
|
||
|
// height: 210,
|
||
|
// child: YoutubeExplode,
|
||
|
// decoration: BoxDecoration(
|
||
|
// color: context.color.tertiaryColor,
|
||
|
// borderRadius: BorderRadius.circular(10)),
|
||
|
// ),
|
||
|
|
||
|
Text("projectLocation".translate(context))
|
||
|
.size(context.font.large)
|
||
|
.bold(),
|
||
|
|
||
|
const SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
|
||
|
Padding(
|
||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
|
child: Column(
|
||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
children: [
|
||
|
Row(
|
||
|
children: [
|
||
|
Text("locationLbl".translate(context)).bold(),
|
||
|
Text(project.location!)
|
||
|
],
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 5,
|
||
|
),
|
||
|
Row(
|
||
|
children: [
|
||
|
Text("cityProj".translate(context)).bold(),
|
||
|
Text(project.city!)
|
||
|
],
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 5,
|
||
|
),
|
||
|
Row(
|
||
|
children: [
|
||
|
Text("stateProj".translate(context)).bold(),
|
||
|
Text(project.state!)
|
||
|
],
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 5,
|
||
|
),
|
||
|
Row(
|
||
|
children: [
|
||
|
Text("countryProj".translate(context)).bold(),
|
||
|
Text(project.country!)
|
||
|
],
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
|
||
|
SizedBox(
|
||
|
height: 175,
|
||
|
child: ClipRRect(
|
||
|
borderRadius: BorderRadius.circular(10),
|
||
|
child: Stack(
|
||
|
fit: StackFit.expand,
|
||
|
children: [
|
||
|
Image.asset(
|
||
|
"assets/map.png",
|
||
|
fit: BoxFit.cover,
|
||
|
),
|
||
|
BackdropFilter(
|
||
|
filter: ImageFilter.blur(
|
||
|
sigmaX: 4.0,
|
||
|
sigmaY: 4.0,
|
||
|
),
|
||
|
child: Center(
|
||
|
child: MaterialButton(
|
||
|
onPressed: () {
|
||
|
Navigator.push(context, BlurredRouter(
|
||
|
builder: (context) {
|
||
|
return Scaffold(
|
||
|
extendBodyBehindAppBar: true,
|
||
|
appBar: AppBar(
|
||
|
elevation: 0,
|
||
|
iconTheme: IconThemeData(
|
||
|
color: context
|
||
|
.color.tertiaryColor),
|
||
|
backgroundColor:
|
||
|
Colors.transparent,
|
||
|
),
|
||
|
body: GoogleMapScreen(
|
||
|
latitude: double.parse(
|
||
|
project.latitude!),
|
||
|
longitude: double.parse(
|
||
|
project.longitude!),
|
||
|
kInitialPlace: _kInitialPlace,
|
||
|
controller: _controller,
|
||
|
),
|
||
|
);
|
||
|
},
|
||
|
));
|
||
|
},
|
||
|
shape: RoundedRectangleBorder(
|
||
|
borderRadius:
|
||
|
BorderRadius.circular(5)),
|
||
|
color: context.color.tertiaryColor,
|
||
|
elevation: 0,
|
||
|
child:
|
||
|
Text(("viewMap".translate(context)))
|
||
|
.color(
|
||
|
context.color.buttonColor,
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
const SizedBox(
|
||
|
height: 40,
|
||
|
)
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
)
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
Widget bottomNavigation(BuildContext context) {
|
||
|
if (isMyProject) {
|
||
|
return Padding(
|
||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||
|
child: SizedBox(
|
||
|
height: 65.rh(context),
|
||
|
child: Padding(
|
||
|
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 0),
|
||
|
child: Row(
|
||
|
mainAxisSize: MainAxisSize.min,
|
||
|
children: [
|
||
|
Expanded(
|
||
|
child: UiUtils.buildButton(context,
|
||
|
// padding: const EdgeInsets.symmetric(horizontal: 1),
|
||
|
outerPadding: const EdgeInsets.all(1), onPressed: () {
|
||
|
Navigator.pushNamed(context, Routes.addProjectDetails,
|
||
|
arguments: {
|
||
|
"id": project.id,
|
||
|
"category_id": project.category!.id!,
|
||
|
"project": project.toMap(),
|
||
|
});
|
||
|
},
|
||
|
fontSize: context.font.normal,
|
||
|
width: context.screenWidth / 3,
|
||
|
prefixWidget: Padding(
|
||
|
padding: const EdgeInsets.only(right: 6.0),
|
||
|
child: SvgPicture.asset(AppIcons.edit),
|
||
|
),
|
||
|
buttonTitle: UiUtils.translate(context, "edit")),
|
||
|
),
|
||
|
const SizedBox(
|
||
|
width: 8,
|
||
|
),
|
||
|
Expanded(
|
||
|
child: UiUtils.buildButton(context,
|
||
|
padding: const EdgeInsets.symmetric(horizontal: 1),
|
||
|
outerPadding: const EdgeInsets.all(1),
|
||
|
prefixWidget: Padding(
|
||
|
padding: const EdgeInsets.only(right: 6.0),
|
||
|
child: SvgPicture.asset(
|
||
|
AppIcons.delete,
|
||
|
color: context.color.buttonColor,
|
||
|
width: 14,
|
||
|
height: 14,
|
||
|
),
|
||
|
), onPressed: () async {
|
||
|
UiUtils.showBlurredDialoge(context,
|
||
|
dialoge: BlurredDialogBox(
|
||
|
title: "areYouSure".translate(context),
|
||
|
onAccept: () async {
|
||
|
context
|
||
|
.read<DeleteProjectCubit>()
|
||
|
.delete(project.id!);
|
||
|
},
|
||
|
content: Text(
|
||
|
"projectWillNotRecover".translate(context))));
|
||
|
},
|
||
|
fontSize: context.font.normal,
|
||
|
width: context.screenWidth / 3.2,
|
||
|
buttonTitle: UiUtils.translate(context, "deleteBtnLbl")),
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
return const SizedBox.shrink();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Widget categoryCard(BuildContext context, ProjectModel project) {
|
||
|
return Container(
|
||
|
decoration: BoxDecoration(
|
||
|
// color: context.color.tertiaryColor.withOpacity(0.1),
|
||
|
borderRadius: BorderRadius.circular(5)),
|
||
|
child: Row(
|
||
|
mainAxisSize: MainAxisSize.min,
|
||
|
children: [
|
||
|
Container(
|
||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)),
|
||
|
height: 35,
|
||
|
child: SvgPicture.network(project.category!.image!),
|
||
|
),
|
||
|
const SizedBox(
|
||
|
width: 3,
|
||
|
),
|
||
|
Text(project.category!.category!).size(context.font.large)
|
||
|
],
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
class ProjectImageCareusel extends StatefulWidget {
|
||
|
final List<String> images;
|
||
|
const ProjectImageCareusel({
|
||
|
super.key,
|
||
|
required this.images,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
State<ProjectImageCareusel> createState() => _ProjectImageCareuselState();
|
||
|
}
|
||
|
|
||
|
class _ProjectImageCareuselState extends State<ProjectImageCareusel>
|
||
|
with AutomaticKeepAliveClientMixin {
|
||
|
final ValueNotifier<int> _sliderIndex = ValueNotifier(0);
|
||
|
final PageController _pageController = PageController(
|
||
|
initialPage: 0,
|
||
|
);
|
||
|
late Timer _timer;
|
||
|
@override
|
||
|
void initState() {
|
||
|
_timer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||
|
if (_sliderIndex.value < widget.images.length - 1) {
|
||
|
_sliderIndex.value++;
|
||
|
} else {
|
||
|
_sliderIndex.value = 0;
|
||
|
}
|
||
|
if (_pageController.hasClients) {
|
||
|
_pageController.animateToPage(
|
||
|
_sliderIndex.value,
|
||
|
duration: const Duration(milliseconds: 1000),
|
||
|
curve: Curves.easeIn,
|
||
|
);
|
||
|
}
|
||
|
});
|
||
|
super.initState();
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
void dispose() {
|
||
|
_sliderIndex.dispose();
|
||
|
_timer.cancel();
|
||
|
super.dispose();
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
super.build(context);
|
||
|
return Stack(
|
||
|
children: [
|
||
|
PageView.builder(
|
||
|
itemCount: widget.images.length,
|
||
|
controller: _pageController,
|
||
|
clipBehavior: Clip.antiAlias,
|
||
|
physics: const BouncingScrollPhysics(
|
||
|
decelerationRate: ScrollDecelerationRate.fast,
|
||
|
),
|
||
|
onPageChanged: (index) {
|
||
|
_sliderIndex.value = index;
|
||
|
},
|
||
|
itemBuilder: (context, index) {
|
||
|
List images = widget.images;
|
||
|
return GestureDetector(
|
||
|
onTap: () {
|
||
|
Navigator.push(
|
||
|
context,
|
||
|
BlurredRouter(
|
||
|
builder: (context) => GalleryViewWidget(
|
||
|
images: images,
|
||
|
initalIndex: index,
|
||
|
),
|
||
|
));
|
||
|
},
|
||
|
child: ProjectCateuseItem(
|
||
|
url: widget.images[index],
|
||
|
),
|
||
|
);
|
||
|
}),
|
||
|
Align(
|
||
|
alignment: Alignment.bottomCenter.add(const Alignment(0, -0.05)),
|
||
|
child: ValueListenableBuilder(
|
||
|
valueListenable: _sliderIndex,
|
||
|
builder: (context, val, ch) {
|
||
|
return Row(
|
||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||
|
children: [
|
||
|
...List.generate(
|
||
|
widget.images.length,
|
||
|
(index) => Container(
|
||
|
width: 7,
|
||
|
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||
|
height: 7,
|
||
|
decoration: BoxDecoration(
|
||
|
shape: BoxShape.circle,
|
||
|
color: index == val
|
||
|
? context.color.tertiaryColor
|
||
|
: Colors.white,
|
||
|
),
|
||
|
)),
|
||
|
],
|
||
|
);
|
||
|
}),
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
bool get wantKeepAlive => true;
|
||
|
}
|
||
|
|
||
|
class ProjectCateuseItem extends StatelessWidget {
|
||
|
final String url;
|
||
|
|
||
|
const ProjectCateuseItem({
|
||
|
super.key,
|
||
|
required this.url,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return Stack(
|
||
|
fit: StackFit.expand,
|
||
|
children: [
|
||
|
Image.network(
|
||
|
url,
|
||
|
fit: BoxFit.cover,
|
||
|
),
|
||
|
BackdropFilter(
|
||
|
filter: ImageFilter.blur(sigmaY: 8, sigmaX: 8),
|
||
|
child: Container(
|
||
|
color: Colors.black.withOpacity(0.2),
|
||
|
),
|
||
|
),
|
||
|
Image.network(
|
||
|
url,
|
||
|
fit: BoxFit.cover,
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CustomExpansionTile extends StatefulWidget {
|
||
|
final String title;
|
||
|
final Widgetss children;
|
||
|
const CustomExpansionTile({
|
||
|
super.key,
|
||
|
required this.title,
|
||
|
required this.children,
|
||
|
});
|
||
|
|
||
|
@override
|
||
|
State<CustomExpansionTile> createState() => _CustomExpansionTileState();
|
||
|
}
|
||
|
|
||
|
class _CustomExpansionTileState extends State<CustomExpansionTile> {
|
||
|
bool isExpanded = false;
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return ExpansionTile(
|
||
|
title: Text(widget.title)
|
||
|
.size(context.font.large)
|
||
|
.color(context.color.textColorDark.withOpacity(0.9)),
|
||
|
// dense: true,
|
||
|
collapsedTextColor: context.color.textColorDark,
|
||
|
textColor: context.color.textColorDark,
|
||
|
iconColor: context.color.tertiaryColor,
|
||
|
collapsedIconColor: context.color.tertiaryColor,
|
||
|
trailing: AnimatedCrossFade(
|
||
|
firstChild: const Icon(Icons.add),
|
||
|
secondChild: const Icon(Icons.remove),
|
||
|
duration: const Duration(milliseconds: 250),
|
||
|
crossFadeState:
|
||
|
isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||
|
),
|
||
|
onExpansionChanged: (value) {
|
||
|
isExpanded = value;
|
||
|
setState(() {});
|
||
|
},
|
||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||
|
children: widget.children,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ContactDetailsWidget extends StatelessWidget {
|
||
|
final String url;
|
||
|
final String name;
|
||
|
final String email;
|
||
|
final String number;
|
||
|
|
||
|
const ContactDetailsWidget(
|
||
|
{super.key,
|
||
|
required this.url,
|
||
|
required this.name,
|
||
|
required this.email,
|
||
|
required this.number});
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return Column(
|
||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
children: [
|
||
|
Text("contactUS".translate(context)).size(context.font.large).bold(),
|
||
|
SizedBox(
|
||
|
height: 15,
|
||
|
),
|
||
|
Row(
|
||
|
children: [
|
||
|
GestureDetector(
|
||
|
onTap: () {
|
||
|
UiUtils.showFullScreenImage(context,
|
||
|
provider: NetworkImage(url));
|
||
|
},
|
||
|
child: Container(
|
||
|
width: 70,
|
||
|
height: 70,
|
||
|
clipBehavior: Clip.antiAlias,
|
||
|
decoration: BoxDecoration(
|
||
|
color: Colors.grey.shade200,
|
||
|
borderRadius: BorderRadius.circular(10)),
|
||
|
child: UiUtils.getImage(url, fit: BoxFit.cover)
|
||
|
|
||
|
// CachedNetworkImage(
|
||
|
// imageUrl: widget.propertyData?.customerProfile ?? "",
|
||
|
// fit: BoxFit.cover,
|
||
|
// ),
|
||
|
|
||
|
),
|
||
|
),
|
||
|
const SizedBox(
|
||
|
width: 10,
|
||
|
),
|
||
|
Expanded(
|
||
|
flex: 5,
|
||
|
child: Column(
|
||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
children: [
|
||
|
Text(name)
|
||
|
.size(context.font.large)
|
||
|
.bold()
|
||
|
.setMaxLines(lines: 1),
|
||
|
Text(email).setMaxLines(lines: 1),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
Expanded(
|
||
|
flex: 3,
|
||
|
child: Container(
|
||
|
// color: Colors.red,
|
||
|
child: Row(
|
||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||
|
children: [
|
||
|
IconButton(
|
||
|
onPressed: () async {
|
||
|
await launchUrl(Uri.parse("tel:+$number"));
|
||
|
},
|
||
|
icon: Icon(
|
||
|
Icons.call,
|
||
|
color: context.color.tertiaryColor,
|
||
|
)),
|
||
|
IconButton(
|
||
|
onPressed: () async {
|
||
|
await launchUrl(Uri.parse("mailto:$email"));
|
||
|
},
|
||
|
icon: Icon(
|
||
|
Icons.email,
|
||
|
color: context.color.tertiaryColor,
|
||
|
)),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class DownloadableDocument extends StatefulWidget {
|
||
|
final String url;
|
||
|
const DownloadableDocument({super.key, required this.url});
|
||
|
|
||
|
@override
|
||
|
State<DownloadableDocument> createState() => _DownloadableDocumentState();
|
||
|
}
|
||
|
|
||
|
class _DownloadableDocumentState extends State<DownloadableDocument> {
|
||
|
bool downloaded = false;
|
||
|
Dio dio = Dio();
|
||
|
ValueNotifier<double> percentage = ValueNotifier(0);
|
||
|
|
||
|
@override
|
||
|
void initState() {
|
||
|
super.initState();
|
||
|
}
|
||
|
|
||
|
Future<String?>? path() async {
|
||
|
String? downloadPath = await HelperUtils.getDownloadPath();
|
||
|
return downloadPath;
|
||
|
}
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
String name = widget.url.split("/").last;
|
||
|
return ListTile(
|
||
|
dense: true,
|
||
|
title: Text(name)
|
||
|
.size(context.font.large)
|
||
|
.color(context.color.textColorDark.withOpacity(0.9)),
|
||
|
trailing: ValueListenableBuilder(
|
||
|
valueListenable: percentage,
|
||
|
builder: (context, value, child) {
|
||
|
if (value != 0.0 && value != 1.0) {
|
||
|
return SizedBox(
|
||
|
height: 24,
|
||
|
width: 24,
|
||
|
child: CircularProgressIndicator(
|
||
|
value: value,
|
||
|
color: context.color.tertiaryColor,
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
if (downloaded) {
|
||
|
return IconButton(
|
||
|
padding: EdgeInsets.zero,
|
||
|
alignment: Alignment.centerRight,
|
||
|
splashRadius: 1,
|
||
|
icon: const Icon(Icons.file_open),
|
||
|
onPressed: () async {
|
||
|
String? downloadPath = await path();
|
||
|
|
||
|
await OpenFilex.open("$downloadPath/$name");
|
||
|
});
|
||
|
}
|
||
|
return IconButton(
|
||
|
padding: EdgeInsets.zero,
|
||
|
alignment: Alignment.centerRight,
|
||
|
splashRadius: 1,
|
||
|
icon: const Icon(Icons.download),
|
||
|
onPressed: () async {
|
||
|
String? downloadPath = await path();
|
||
|
bool storagePermission =
|
||
|
await HelperUtils.hasStoragePermissionGiven();
|
||
|
if (storagePermission) {
|
||
|
await dio.download(
|
||
|
widget.url,
|
||
|
"$downloadPath/$name",
|
||
|
onReceiveProgress: (count, total) async {
|
||
|
percentage.value = count / total;
|
||
|
if (percentage.value == 1.0) {
|
||
|
downloaded = true;
|
||
|
setState(() {});
|
||
|
await OpenFilex.open("$downloadPath/$name");
|
||
|
}
|
||
|
},
|
||
|
);
|
||
|
} else {
|
||
|
HelperUtils.showSnackBarMessage(
|
||
|
context, "Storage Permission denied!");
|
||
|
}
|
||
|
},
|
||
|
);
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
}
|