import 'package:flutter/material.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/device/asset_by_id_model.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; class AssetDetailsView extends StatefulWidget { final AssetByIdModel assetModel; final VoidCallback onUpload; const AssetDetailsView({ Key? key, required this.assetModel, required this.onUpload, }) : super(key: key); @override State createState() => _AssetDetailsViewState(); } class _AssetDetailsViewState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; List attachments = []; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 1400), vsync: this, ); attachments = widget.assetModel.assetAttachments?.whereType().toList() ?? []; _animationController.forward(from: 0.0); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Image animation - fade in with scale final imageAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.4, curve: Curves.easeOut), ), ); final imageScaleAnimation = Tween(begin: 0.92, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.4, curve: Curves.easeOutCubic), ), ); // Status label animation final statusAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.15, 0.5, curve: Curves.easeOut), ), ); final statusSlideAnimation = Tween( begin: const Offset(0.0, 0.2), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.15, 0.5, curve: Curves.easeOutCubic), ), ); // Header animation final headerAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.25, 0.6, curve: Curves.easeOut), ), ); final headerSlideAnimation = Tween( begin: const Offset(0.0, 0.2), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.25, 0.6, curve: Curves.easeOutCubic), ), ); // Details row animation final detailsAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.35, 0.75, curve: Curves.easeOut), ), ); final detailsSlideAnimation = Tween( begin: const Offset(0.0, 0.15), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.35, 0.75, curve: Curves.easeOutCubic), ), ); // Dates section animation final datesAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.50, 0.9, curve: Curves.easeOut), ), ); final datesSlideAnimation = Tween( begin: const Offset(0.0, 0.15), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.50, 0.9, curve: Curves.easeOutCubic), ), ); // Description animation (if exists) final descAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.65, 1.0, curve: Curves.easeOut), ), ); final descSlideAnimation = Tween( begin: const Offset(0.0, 0.1), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.65, 1.0, curve: Curves.easeOutCubic), ), ); final attachmentAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.80, 1.0, curve: Curves.easeOut), ), ); final attachmentSlideAnimation = Tween( begin: const Offset(0.0, 0.1), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: const Interval(0.80, 1.0, curve: Curves.easeOutCubic), ), ); return Column( children: [ SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Animated image FadeTransition( opacity: imageAnimation, child: ScaleTransition( scale: imageScaleAnimation, child: AspectRatio( aspectRatio: 159 / 94, child: Container( width: 95, height: 95, decoration: ShapeDecoration( color: AppColor.neutral30, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), image: DecorationImage( fit: BoxFit.cover, image: NetworkImage(widget.assetModel.assetPhoto != null ? URLs.getFileUrl(widget.assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), )), ), ), ), ), 6.height, // Animated content column Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Status label with animation if (widget.assetModel.commissioningStatus != null) FadeTransition( opacity: statusAnimation, child: SlideTransition( position: statusSlideAnimation, child: StatusLabel( label: widget.assetModel.commissioningStatus!.name, textColor: AppColor.getRequestStatusTextColorByName(context, widget.assetModel.commissioningStatus!.name!), backgroundColor: AppColor.getRequestStatusColorByName(context, widget.assetModel.commissioningStatus!.name!), ), ), ), if (widget.assetModel.commissioningStatus != null) 8.height, // Header with animation FadeTransition( opacity: headerAnimation, child: SlideTransition( position: headerSlideAnimation, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, spacing: 2, children: [ InfoHeader16Widget(widget.assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), if (context.userProvider.isEngineer && !(widget.assetModel.hasTrainingImageData ?? true)) const InfoTextLabelWidget(label: '*Upload Images for this asset', color: AppColor.red30), ], ), ), ), 8.height, // Details row with animation FadeTransition( opacity: detailsAnimation, child: SlideTransition( position: detailsSlideAnimation, child: Row( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoTextWidget(label: context.translation.assetNo, value: widget.assetModel.multiAssets!.first.assetNumber, showEmptyValue: true, copyValue: true), InfoTextWidget(label: context.translation.modelName, value: widget.assetModel.modelDefinition!.modelName, showEmptyValue: true), InfoTextWidget(label: context.translation.supplier, value: widget.assetModel.supplier?.suppliername, showEmptyValue: true), InfoTextWidget(label: context.translation.manufacture, value: widget.assetModel.modelDefinition!.manufacturerName, showEmptyValue: true), ], ).expanded, Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoTextWidget(label: context.translation.snNo, value: widget.assetModel.multiAssets!.first.assetSerialNo, showEmptyValue: true), InfoTextWidget(label: context.translation.site, value: widget.assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), InfoTextWidget(label: context.translation.building, value: widget.assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), InfoTextWidget(label: context.translation.floor, value: widget.assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), InfoTextWidget(label: context.translation.md, value: widget.assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), InfoTextWidget(label: context.translation.room, value: widget.assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), ], ).expanded, ], ), ), ), // Divider with animation FadeTransition( opacity: datesAnimation, child: const Divider().defaultStyle(context), ), // Dates section with animation FadeTransition( opacity: datesAnimation, child: SlideTransition( position: datesSlideAnimation, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoTextWidget(label: context.translation.installationDate, value: widget.assetModel.installationDate?.toAssetDetailsFormat, showEmptyValue: true), InfoTextWidget(label: context.translation.nextPmDate, value: widget.assetModel.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), InfoTextWidget(label: context.translation.lastPmDate, value: widget.assetModel.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), ], ), ), ), // Description with animation (if exists) if ((widget.assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ FadeTransition( opacity: descAnimation, child: const Divider().defaultStyle(context), ), FadeTransition( opacity: descAnimation, child: SlideTransition( position: descSlideAnimation, child: InfoTextWidget(label: widget.assetModel.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), ), ), ], if (attachments.isNotEmpty) ...[ FadeTransition( opacity: attachmentAnimation, child: const Divider().defaultStyle(context), ), FadeTransition( opacity: attachmentAnimation, child: SlideTransition( position: attachmentSlideAnimation, child: FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()), ), ), ] ], ), ], ).toShadowContainer(context), ).expanded, if (context.userProvider.isEngineer && !(widget.assetModel.hasTrainingImageData ?? true)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( buttonColor: AppColor.primary10, label: "Upload Images", onPressed: widget.onUpload, ), ), ], ); } }