diff --git a/assets/images/asset_image_upload/side_bottom.svg b/assets/images/asset_image_upload/side_bottom.svg new file mode 100644 index 00000000..7d9ec605 --- /dev/null +++ b/assets/images/asset_image_upload/side_bottom.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_left.svg b/assets/images/asset_image_upload/side_left.svg new file mode 100644 index 00000000..bdf7e6d9 --- /dev/null +++ b/assets/images/asset_image_upload/side_left.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_right.svg b/assets/images/asset_image_upload/side_right.svg new file mode 100644 index 00000000..f8db13a1 --- /dev/null +++ b/assets/images/asset_image_upload/side_right.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_top.svg b/assets/images/asset_image_upload/side_top.svg new file mode 100644 index 00000000..52841a4e --- /dev/null +++ b/assets/images/asset_image_upload/side_top.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart index ea8fcf06..f0c7ab42 100644 --- a/lib/controllers/api_routes/api_manager.dart +++ b/lib/controllers/api_routes/api_manager.dart @@ -19,9 +19,9 @@ import 'package:test_sa/new_views/pages/login_page.dart'; class _CachedResponse { final http.Response response; final DateTime timestamp; - + _CachedResponse(this.response, this.timestamp); - + bool isValid(Duration cacheDuration) { return DateTime.now().difference(timestamp) < cacheDuration; } @@ -41,26 +41,26 @@ class ApiManager { User? user; AssetGroup? assetGroup; - + // OPTIMIZATION: HTTP Connection Pooling - Reuse connections instead of creating new ones // This improves performance by 100-200ms on subsequent API calls static final http.Client _httpClient = http.Client(); - + // OPTIMIZATION: Response Caching - Cache lookup/static data to reduce network calls // This reduces API calls by 60% for frequently accessed but rarely changing data static final Map _cache = {}; static const Duration _defaultCacheDuration = Duration(hours: 1); - + /// Clear all cached responses (call on logout or when needed) void clearCache() { _cache.clear(); } - + /// Clear specific cache entry void clearCacheEntry(String url) { _cache.remove(_generateCacheKey(url)); } - + /// Generate cache key from URL and user context String _generateCacheKey(String url) { // Include user and asset group in cache key to avoid cross-user pollution @@ -68,7 +68,7 @@ class ApiManager { } /// GET request with optional caching support - /// + /// /// [url] - API endpoint URL /// [headers] - Optional additional headers /// [enableToastMessage] - Show toast message from API response @@ -86,12 +86,12 @@ class ApiManager { headers ??= {}; headers.addAll(_headers); Uri url0 = Uri.parse(url); - + // OPTIMIZATION: Check cache first if enabled if (useCache && !forceRefresh) { final cacheKey = _generateCacheKey(url); final cachedResponse = _cache[cacheKey]; - + if (cachedResponse != null && cachedResponse.isValid(cacheDuration ?? _defaultCacheDuration)) { if (kDebugMode) { print('📦 Cache HIT: $url'); @@ -99,10 +99,10 @@ class ApiManager { return cachedResponse.response; } } - + // OPTIMIZATION: Use persistent HTTP client for connection pooling http.Response response = await _httpClient.get(url0, headers: headers); - + // OPTIMIZATION: Cache the response if caching is enabled if (useCache && response.statusCode >= 200 && response.statusCode < 300) { final cacheKey = _generateCacheKey(url); @@ -310,7 +310,7 @@ class ApiManager { Uri url0 = Uri.parse(url); // print(headers); // log(json.encode(body)); - + // OPTIMIZATION: Use persistent HTTP client for connection pooling var request = http.Request('PUT', url0); request.body = json.encode(body); @@ -377,10 +377,9 @@ class ApiManager { } Future multiPart(String url, {Map? headers, required Map body, required List> files}) async { - Map headers0 = const { - 'Content-Type': 'multipart/form-data', - }; + Map headers0 = _headers; + headers0["Content-Type"] = 'multipart/form-data'; headers ??= {}; headers.addAll(headers0); @@ -463,7 +462,7 @@ class ApiManager { Navigator.pop(context); Navigator.of(context).pushNamedAndRemoveUntil(LoginPage.routeName, (routes) => true); } - + /// Dispose HTTP client (call when app is closing if needed) /// Note: In most cases, keep client alive for app lifetime for best performance void dispose() { diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 501df4b8..b7088f02 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,11 +1,11 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "44"; + static const String appReleaseBuildNumber = "47"; - // static const host1 = "https://atomsm.hmg.com"; // production url + static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis @@ -66,6 +66,7 @@ class URLs { static get getFaultDescription => "$_baseUrl/Asset/GetDefectForModelDefinition"; // get static get getAssetById => "$_baseUrl/Asset/GetAssetById?assetId="; // get static get getAssetColumnsMetadata => "$_baseUrl/Asset/columns/metadata"; // get asset export columns metadata + static get saveAssetImages => "$_baseUrl/Asset/SaveAssetImages"; // get static get getModels => "$_baseUrl/ModelDefinition/GetModelDefinitionAsset"; // get ?client=2 static get getAllRequestsAndCount => "$_baseUrl/CallRequest/GetAllRequestsAndCount"; // get static get updateUserContactInformation => "$_baseUrl/Account/UpdateContactInfo"; // get diff --git a/lib/controllers/providers/api/all_requests_provider.dart b/lib/controllers/providers/api/all_requests_provider.dart index a3eeb0d0..0a93116b 100644 --- a/lib/controllers/providers/api/all_requests_provider.dart +++ b/lib/controllers/providers/api/all_requests_provider.dart @@ -742,6 +742,7 @@ class AllRequestsProvider extends ChangeNotifier { List? statusTransaction, List? priority, List? displayData, + bool? isAscending, }) async { if (isMultiFilterLoading == true) return -2; if (_allRequestsAndCount == null || pagination) { @@ -758,6 +759,7 @@ class AllRequestsProvider extends ChangeNotifier { body["pageNumber"] = pageNum++; body["pageSize"] = pageItemNumber; } + if (isAscending != null) body["isAscending"] = isAscending; response = await ApiManager.instance.post(URLs.getAllRequestsAndCount, body: body); stateCode = response.statusCode; diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index 218ebf80..66c95d7b 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -9,6 +9,7 @@ import 'package:test_sa/controllers/api_routes/http_status_manger.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/models/device/asset_by_id_model.dart'; import 'package:test_sa/models/device/asset_search.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/user.dart'; import 'package:test_sa/modules/asset_delivery_module/models/technical_inpection_asset_model.dart'; @@ -152,9 +153,7 @@ class AssetProvider extends ChangeNotifier { print(error); throw (HttpStatusManger.getStatusMessage(status: -1, subtitle: appLocalizations)); } - _stateCode = response.statusCode; if (response.statusCode >= 200 && response.statusCode < 300) { - // client's request was successfully received Map assetData = json.decode(response.body)["data"]; _assetById = AssetByIdModel.fromJson(assetData); return _assetById!; @@ -230,4 +229,32 @@ class AssetProvider extends ChangeNotifier { return []; } } + + Future uploadAssetImages(int assetId, TrainingImageData trainingImageData) async { + late Response response; + try { + List> files = []; + files.add(MultipartFile.fromPath("LeftImage", trainingImageData.leftImage!.name!)); + files.add(MultipartFile.fromPath("RightImage", trainingImageData.rightImage!.name!)); + files.add(MultipartFile.fromPath("UpImage", trainingImageData.upImage!.name!)); + files.add(MultipartFile.fromPath("DownImage", trainingImageData.downImage!.name!)); + + for (var i = 0; i < (trainingImageData.additionalImages?.length ?? 0); i++) { + files.add( + MultipartFile.fromPath('AdditionalImage', trainingImageData.additionalImages![i].name!), + ); + } + response = await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: files); + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + // Map assetData = json.decode(response.body)["data"]; + // _assetById = AssetByIdModel.fromJson(assetData); + // return _assetById!; + return true; + } + return false; + } catch (error) { + return false; + } + } } diff --git a/lib/dashboard_latest/dashboard_provider.dart b/lib/dashboard_latest/dashboard_provider.dart index 77896468..c50e9039 100644 --- a/lib/dashboard_latest/dashboard_provider.dart +++ b/lib/dashboard_latest/dashboard_provider.dart @@ -348,6 +348,7 @@ class DashBoardProvider extends ChangeNotifier { statusTransaction: statusTransaction, priority: priority, displayData: displayData, + isAscending: isAscending, ); } diff --git a/lib/dashboard_latest/dashboard_view.dart b/lib/dashboard_latest/dashboard_view.dart index ca986cc1..6de6e88b 100644 --- a/lib/dashboard_latest/dashboard_view.dart +++ b/lib/dashboard_latest/dashboard_view.dart @@ -12,6 +12,7 @@ import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/controllers/providers/settings/setting_provider.dart'; import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; +import 'package:test_sa/extensions/enum_extensions.dart'; import 'package:test_sa/dashboard_latest/widgets/progress_fragment.dart'; import 'package:test_sa/dashboard_latest/widgets/request_category_fragment_housekeeper.dart'; import 'package:test_sa/dashboard_latest/widgets/requests_fragment.dart'; @@ -103,15 +104,86 @@ class _DashboardViewState extends State { void handleScroll() async { _scrollController = ScrollController(); _scrollController.addListener(() async { - if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_dashBoardProvider.isDetailLoading) { - _dashBoardProvider.pageNum = _dashBoardProvider.pageNum + 1; - if (_dashBoardProvider.currentListIndex == null) { - } else { - _dashBoardProvider.getRequestDetail( + if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { + final allRequestsProvider = Provider.of(context, listen: false); + + // Check which scenario is active and call appropriate pagination API + if (_dashBoardProvider.listIndex.isEmpty) { + // No category selected - paginate AllRequests + if (!allRequestsProvider.isAllLoading && allRequestsProvider.nextPage) { + allRequestsProvider.getAllRequests(context); + } + } else if (_dashBoardProvider.listIndex.length == 1 && _dashBoardProvider.isUnassignedTab(_dashBoardProvider.listIndex[0])) { + // Only UnAssigned tab selected - paginate with getRequestDetail + if (!_dashBoardProvider.isDetailLoading && _dashBoardProvider.nextPage) { + _dashBoardProvider.pageNum = _dashBoardProvider.pageNum + 1; + _dashBoardProvider.getRequestDetail( usersType: user.type!, showLoader: false, - status: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].tag, - tabId: _dashBoardProvider.tabs[_dashBoardProvider.currentListIndex!].id); + status: _dashBoardProvider.tabs[_dashBoardProvider.listIndex[0]].tag, + tabId: _dashBoardProvider.tabs[_dashBoardProvider.listIndex[0]].id, + ); + } + } else { + // Multi-select categories - paginate with getDashboardMultifilterRequests + if (!allRequestsProvider.isMultiFilterLoading && allRequestsProvider.nextPage) { + // Build filter arrays based on selected tabs + List statusTransaction = []; + List priority = []; + List displayData = []; + + for (int index in _dashBoardProvider.listIndex) { + if (index >= 0 && index < _dashBoardProvider.tabs.length) { + CategoryTabs tab = _dashBoardProvider.tabs[index]; + + // High Priority + if (tab.isHighPriority) { + if (!priority.contains(1)) priority.add(1); + } + + // Overdue + if (tab.isOverDue) { + if (!displayData.contains(1)) displayData.add(1); + } + + // Status categories + if (!tab.isHighPriority && !tab.isOverDue && tab.requestType != RequestTypeEnum.unassigned) { + switch (tab.requestType) { + case RequestTypeEnum.open: + if (!statusTransaction.contains(1)) statusTransaction.add(1); + break; + case RequestTypeEnum.inProgress: + if (!statusTransaction.contains(2)) statusTransaction.add(2); + break; + case RequestTypeEnum.completed: + if (!statusTransaction.contains(4)) statusTransaction.add(4); + break; + case RequestTypeEnum.closed: + if (!statusTransaction.contains(4)) statusTransaction.add(4); + break; + case RequestTypeEnum.canceled: + if (!statusTransaction.contains(5)) statusTransaction.add(5); + break; + case RequestTypeEnum.pendingAcknowledgment: + if (!statusTransaction.contains(3)) statusTransaction.add(3); + break; + case RequestTypeEnum.unassigned: + case RequestTypeEnum.highPriority: + case RequestTypeEnum.overdue: + break; + } + } + } + } + + allRequestsProvider.getDashboardMultifilterRequests( + context, + pagination: false, + statusTransaction: statusTransaction, + priority: priority, + displayData: displayData, + ); + } } } }); diff --git a/lib/dashboard_latest/widgets/app_bar_widget.dart b/lib/dashboard_latest/widgets/app_bar_widget.dart index 78ffe1a0..a87ec4cb 100644 --- a/lib/dashboard_latest/widgets/app_bar_widget.dart +++ b/lib/dashboard_latest/widgets/app_bar_widget.dart @@ -223,7 +223,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix [], builder: (_, MenuController controller, Widget? child) { return Container( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: AppColor.assetGroupBackground(context), @@ -234,10 +234,13 @@ class _AppBarWidgetState extends State with TickerProviderStateMix ), child: Row( mainAxisSize: MainAxisSize.min, - spacing: 16, + spacing: 8, children: [ - Text(settingProvider.assetGroup?.name ?? "", style: AppTextStyles.bodyText2.copyWith(color: AppColor.headingTextColor(context))), - Icon(Icons.keyboard_arrow_down, color: AppColor.headingTextColor(context)), + Text( + settingProvider.assetGroup?.name ?? "", + style: AppTextStyles.bodyText2.copyWith(color: AppColor.headingTextColor(context)), + ), + Icon(Icons.keyboard_arrow_down, color: AppColor.headingTextColor(context), size: 20), ], ), ).onPress(() { @@ -252,7 +255,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix }), ), ), - 16.width, + 8.width, SlideTransition( position: _rightSlideAnimation, child: FadeTransition( @@ -260,10 +263,15 @@ class _AppBarWidgetState extends State with TickerProviderStateMix child: Row( children: [ if (context.settingProvider.isUserFlowMedical) ...[ - ("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).paddingOnly(top: 0, end: 0).onPress(() { - Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList())); - }), - 16.width, + IconButton( + icon: ("dashboard/chat").toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "Notifications", + onPressed: () { + Navigator.of(context).push(CupertinoPageRoute(builder: (_) => UnReadChatList())); + }, + ), ], // Notification icon with unread count badge Consumer( @@ -285,7 +293,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix ), ), ], - ).paddingOnly(start: 16, end: 16); + ).paddingOnly(start: 16, end: 08); }), ); } diff --git a/lib/dashboard_latest/widgets/request_category_fragment.dart b/lib/dashboard_latest/widgets/request_category_fragment.dart index c452d306..c6f80cad 100644 --- a/lib/dashboard_latest/widgets/request_category_fragment.dart +++ b/lib/dashboard_latest/widgets/request_category_fragment.dart @@ -83,8 +83,8 @@ class _RequestCategoryFragmentState extends State with Provider.of(context, listen: false).reset(); if (dashboardProvider.listIndex.isEmpty) { - // No category selected - do nothing or could reload default - return; + // No category selected - reload default data with sorting + Provider.of(context, listen: false).getAllRequests(context, isAscending: isAscending); } else if (dashboardProvider.listIndex.length == 1 && dashboardProvider.isUnassignedTab(dashboardProvider.listIndex[0])) { // UnAssigned only - check by requestType, not by index int index = dashboardProvider.listIndex[0]; @@ -117,8 +117,6 @@ class _RequestCategoryFragmentState extends State with final bool isHouseKeeper = user?.type == UsersTypes.houseKeeper; return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - - // !isNurse && dashboardProvider.tabs.isNotEmpty && dashboardProvider.tabs[dashboardProvider.currentListIndex].tag == 0 // ? Column( // crossAxisAlignment: CrossAxisAlignment.start, @@ -193,46 +191,50 @@ class _RequestCategoryFragmentState extends State with // ).paddingOnly(start: 14, end: 12) // : - - isHouseKeeper - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 'All Tasks'.addTranslation.heading4(context), - 'add_icon'.toSvgAsset(width: 24, height: 24,).onPress(() { - Navigator.of(context).pushNamed('/cleaner_search'); - }), - ], - ).paddingOnly(start: 16, end: 16) : - Row( - spacing: 16, - children: [ - Text(context.translation.allWorkOrder, style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context))).expanded, - // todo commenting this code, for FM - // if (!isFMSEngineer) - // 'search'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { - // Navigator.of(context).pushNamed('/cleaner_search'); - // }), - - if (dashboardProvider.listIndex.isNotEmpty) - Text( - 'Clear Filters', - style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), - ).onPress(() { - dashboardProvider.resetRequestDataList(); - dashboardProvider.currentListIndex = null; - Provider.of(context, listen: false) - ..reset() - ..getAllRequests(context); - }), - Transform.flip( - flipY: isAscending, - child: 'dashboard/sort_descending'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { - _toggleSort(dashboardProvider, user?.type); - }), - ) - ], - ).paddingOnly(start: 16, end: 16), + isHouseKeeper + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'All Tasks'.addTranslation.heading4(context), + 'add_icon' + .toSvgAsset( + width: 24, + height: 24, + ) + .onPress(() { + Navigator.of(context).pushNamed('/cleaner_search'); + }), + ], + ).paddingOnly(start: 16, end: 16) + : Row( + spacing: 16, + children: [ + Text(context.translation.allWorkOrder, style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context))).expanded, + // todo commenting this code, for FM + // if (!isFMSEngineer) + // 'search'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { + // Navigator.of(context).pushNamed('/cleaner_search'); + // }), + + if (dashboardProvider.listIndex.isNotEmpty) + Text( + 'Clear Filters', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), + ).onPress(() { + dashboardProvider.resetRequestDataList(); + dashboardProvider.currentListIndex = null; + Provider.of(context, listen: false) + ..reset() + ..getAllRequests(context); + }), + Transform.flip( + flipY: isAscending, + child: 'dashboard/sort_descending'.toSvgAsset(width: 24, height: 24, color: AppColor.headingTextColor(context)).onPress(() { + _toggleSort(dashboardProvider, user?.type); + }), + ) + ], + ).paddingOnly(start: 16, end: 16), ), ), diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index 41f93d7f..8b2a4b23 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import 'package:provider/provider.dart'; @@ -5,6 +6,8 @@ import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/info_dialog.dart'; +import 'package:test_sa/views/full_screen_image_viewer.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; import '../controllers/providers/settings/setting_provider.dart'; import '../l10n/app_localizations.dart'; @@ -24,6 +27,15 @@ extension BuildContextExtension on BuildContext { UserProvider get userProvider => Provider.of(this, listen: false); + void fullScreenImageViewer(Widget child) { + Navigator.of(this).push( + CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => FullScreenImageViewer(child), + ), + ); + } + bool isTablet() { final mediaQueryData = MediaQuery.of(this); final double screenWidth = mediaQueryData.size.width; @@ -49,6 +61,7 @@ extension BuildContextExtension on BuildContext { isScrollControlled: true, isDismissible: true, backgroundColor: Colors.transparent, - builder: (context) => SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title,showCancelButton: showCancelButton), + builder: (context) => + SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title, showCancelButton: showCancelButton), ); } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 2dc5cf00..5273a12a 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -120,12 +120,13 @@ extension WidgetExtensions on Widget { double padding = 12, EdgeInsets? paddingObject, EdgeInsets? margin, + double? width, }) => withShadow ? Container( padding: paddingObject ?? EdgeInsets.all(padding), margin: margin, - width: double.infinity, + width: width ?? double.infinity, decoration: ShapeDecoration( color: backgroundColor ?? AppColor.background(context), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(borderRadius), side: BorderSide(color: borderColor ?? AppColor.borderColor(context))), diff --git a/lib/helper/utils.dart b/lib/helper/utils.dart index 0d706f4e..53496f1f 100644 --- a/lib/helper/utils.dart +++ b/lib/helper/utils.dart @@ -13,7 +13,7 @@ import 'package:nfc_manager/nfc_manager_ios.dart' show MiFareIos, Iso15693Ios; import 'package:shared_preferences/shared_preferences.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; -import 'package:test_sa/views/widgets/dialogs/loading_dialog.dart'; +import 'package:test_sa/views/widgets/dialogs/loading_with_text_dialog.dart'; // ignore_for_file: avoid_annotating_with_dynamic @@ -103,7 +103,7 @@ class Utils { _isLoadingVisible = false; } - static void showUploadingDialog(BuildContext context) { + static void showLoadingWithTextDialog(BuildContext context, {String? message}) { WidgetsBinding.instance.addPostFrameCallback((_) { _isLoadingVisible = true; @@ -111,7 +111,7 @@ class Utils { context: context, barrierColor: Colors.black.withOpacity(0.5), useRootNavigator: false, - builder: (BuildContext context) => const UploadingDialog(), + builder: (BuildContext context) => LoadingWithTextDialog(message: message), ).then((value) { _isLoadingVisible = false; }); diff --git a/lib/models/device/asset.dart b/lib/models/device/asset.dart index 1e41dc31..91c86dc9 100644 --- a/lib/models/device/asset.dart +++ b/lib/models/device/asset.dart @@ -65,6 +65,7 @@ class Asset { this.comment, this.tagCode, this.assetPhoto, + this.hasTrainingImageData, }); Asset.fromJson(dynamic json) { @@ -122,6 +123,7 @@ class Asset { comment = json['comment']; tagCode = json['tagCode']; assetPhoto = json['assetPhoto']; + hasTrainingImageData = json['hasTrainingImageData']; } num? id; // Now nullable @@ -177,6 +179,7 @@ class Asset { String? comment; // Now nullable String? tagCode; // Now nullable String? assetPhoto; + bool? hasTrainingImageData; Asset copyWith({ num? id, @@ -232,6 +235,7 @@ class Asset { String? comment, String? tagCode, String? assetPhoto, + bool? hasTrainingImageData, }) => Asset( id: id ?? this.id, @@ -287,6 +291,7 @@ class Asset { comment: comment ?? this.comment, tagCode: tagCode ?? this.tagCode, assetPhoto: assetPhoto ?? this.assetPhoto, + hasTrainingImageData: hasTrainingImageData ?? this.hasTrainingImageData, ); Map toJson() { @@ -381,6 +386,7 @@ class Asset { map['comment'] = comment; map['tagCode'] = tagCode; map['assetPhoto'] = assetPhoto; + map['hasTrainingImageData'] = hasTrainingImageData; return map; } } diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index c631b767..ab177b23 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -1,4 +1,5 @@ import 'package:test_sa/models/device/technical_guidance_book.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; class AssetByIdModel { @@ -13,12 +14,12 @@ class AssetByIdModel { AssetInfo? oldAsset; AssetReplace? isParent; AssetInfo? parentAsset; - AssetReplace? assetType; + Lookup? assetType; DemoRequest? demoRequest; String? missionCritical; Site? site; - AssetReplace? building; - AssetReplace? floor; + Lookup? building; + Lookup? floor; Department? department; Lookup? room; num? testsDay; @@ -54,14 +55,17 @@ class AssetByIdModel { String? comment; bool? isEnabled; String? tagCode; - List? assetAttachments; + List? assetAttachments; String? retirementTypeName; String? retirementStatusName; String? retirementDate; AssetGroup? assetGroup; String? assetPhoto; + String? modelPhoto; String? nextPMDate; String? lastPMDate; + bool? hasTrainingImageData; + TrainingImageData? trainingImageData; AssetByIdModel( {this.multiAssets, @@ -122,10 +126,15 @@ class AssetByIdModel { this.retirementDate, this.assetGroup, this.assetPhoto, + this.modelPhoto, this.nextPMDate, - this.lastPMDate}); + this.lastPMDate, + this.trainingImageData, + this.hasTrainingImageData}); AssetByIdModel.fromJson(Map json) { + print("assetModel!.fromJson:${json['hasTrainingImageData']}"); + if (json['multiAssets'] != null) { multiAssets = []; json['multiAssets'].forEach((v) { @@ -142,12 +151,12 @@ class AssetByIdModel { oldAsset = json['oldAsset'] != null ? AssetInfo.fromJson(json['oldAsset']) : null; isParent = json['isParent'] != null ? AssetReplace.fromJson(json['isParent']) : null; parentAsset = json['parentAsset'] != null ? AssetInfo.fromJson(json['parentAsset']) : null; - assetType = json['assetType'] != null ? AssetReplace.fromJson(json['assetType']) : null; + assetType = json['assetType'] != null ? Lookup.fromJson(json['assetType']) : null; demoRequest = json['demoRequest'] != null ? DemoRequest.fromJson(json['demoRequest']) : null; missionCritical = json['missionCritical']; site = json['site'] != null ? Site.fromJson(json['site']) : null; - building = json['building'] != null ? AssetReplace.fromJson(json['building']) : null; - floor = json['floor'] != null ? AssetReplace.fromJson(json['floor']) : null; + building = json['building'] != null ? Lookup.fromJson(json['building']) : null; + floor = json['floor'] != null ? Lookup.fromJson(json['floor']) : null; department = json['department'] != null ? Department.fromJson(json['department']) : null; room = json['room'] != null ? Lookup.fromJson(json['room']) : null; testsDay = json['testsDay']; @@ -189,9 +198,9 @@ class AssetByIdModel { isEnabled = json['isEnabled']; tagCode = json['tagCode']; if (json['assetAttachments'] != null) { - assetAttachments = []; + assetAttachments = []; json['assetAttachments'].forEach((v) { - assetAttachments!.add(v); + assetAttachments!.add(AssetAttachment.fromJson(v)); }); } retirementTypeName = json['retirementTypeName']; @@ -199,8 +208,11 @@ class AssetByIdModel { retirementDate = json['retirementDate']; assetGroup = json['assetGroup'] != null ? AssetGroup.fromJson(json['assetGroup']) : null; assetPhoto = json['assetPhoto']; + modelPhoto = json['modelPhoto']; nextPMDate = json['nextPMDate']; lastPMDate = json['lastPMDate']; + hasTrainingImageData = json['hasTrainingImageData']; + trainingImageData = json['trainingImageData'] != null ? TrainingImageData.fromJson(json['trainingImageData']) : null; } Map toJson() { @@ -295,8 +307,11 @@ class AssetByIdModel { data['assetGroup'] = assetGroup?.toJson(); } data['assetPhoto'] = assetPhoto; + data['modelPhoto'] = modelPhoto; data['nextPMDate'] = nextPMDate; data['lastPMDate'] = lastPMDate; + data['hasTrainingImageData'] = hasTrainingImageData; + data['trainingImageData'] = trainingImageData?.toJson(); return data; } } @@ -323,6 +338,31 @@ class MultiAssets { } } +class AssetAttachment { + int? id; + String? attachmentName; + String? attachmentURL; + String? originalName; + + AssetAttachment({this.id, this.attachmentName, this.attachmentURL, this.originalName}); + + AssetAttachment.fromJson(Map json) { + id = json['id']; + attachmentName = json['attachmentName']; + attachmentURL = json['attachmentURL']; + originalName = json['originalName']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['attachmentName'] = attachmentName; + data['attachmentURL'] = attachmentURL; + data['originalName'] = originalName; + return data; + } +} + class ModelDefinition { int? id; String? assetName; @@ -546,18 +586,50 @@ class Floors { class Departments { int? id; String? name; + List? rooms; Departments({this.id, this.name}); Departments.fromJson(Map json) { id = json['id']; name = json['name']; + if (json['departments'] != null) { + rooms = []; + json['rooms'].forEach((v) { + rooms!.add(Rooms.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + if (rooms != null) { + data['rooms'] = rooms!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Rooms { + int? id; + String? name; + int? value; + + Rooms({this.id, this.name, this.value}); + + Rooms.fromJson(Map json) { + id = json['id']; + name = json['name']; + value = json['value']; } Map toJson() { final Map data = {}; data['id'] = id; data['name'] = name; + data['value'] = value; return data; } } @@ -567,14 +639,16 @@ class Department { String? departmentName; String? departmentCode; String? ntCode; + num? operationalHours; - Department({this.id, this.departmentName, this.departmentCode, this.ntCode}); + Department({this.id, this.departmentName, this.departmentCode, this.ntCode, this.operationalHours}); Department.fromJson(Map json) { id = json['id']; departmentName = json['departmentName']; departmentCode = json['departmentCode']; ntCode = json['ntCode']; + operationalHours = json['operationalHours']; } Map toJson() { @@ -583,6 +657,7 @@ class Department { data['departmentName'] = departmentName; data['departmentCode'] = departmentCode; data['ntCode'] = ntCode; + data['operationalHours'] = operationalHours; return data; } } @@ -725,3 +800,37 @@ class DemoRequest { return data; } } + +class TrainingImageData { + GenericAttachmentModel? leftImage; + GenericAttachmentModel? rightImage; + GenericAttachmentModel? upImage; + GenericAttachmentModel? downImage; + List? additionalImages; + + TrainingImageData({this.leftImage, this.rightImage, this.upImage, this.downImage, this.additionalImages = const []}); + + TrainingImageData.fromJson(Map json) { + leftImage = json['leftImage'] == null ? null : GenericAttachmentModel.fromJson(json['leftImage'], convertToLink: true); + rightImage = json['rightImage'] == null ? null : GenericAttachmentModel.fromJson(json['rightImage'], convertToLink: true); + upImage = json['upImage'] == null ? null : GenericAttachmentModel.fromJson(json['upImage'], convertToLink: true); + downImage = json['downImage'] == null ? null : GenericAttachmentModel.fromJson(json['downImage'], convertToLink: true); + additionalImages = []; + if (json['additionalImages'] != null) { + json['additionalImages'].forEach((v) { + additionalImages!.add(GenericAttachmentModel.fromJson(v, convertToLink: true)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['leftImage'] = leftImage?.toJson(); + data['rightImage'] = rightImage?.toJson(); + data['upImage'] = upImage?.toJson(); + data['downImage'] = downImage?.toJson(); + data['additionalImages'] = additionalImages?.map((v) => v.toJson()).toList() ?? []; + + return data; + } +} diff --git a/lib/models/generic_attachment_model.dart b/lib/models/generic_attachment_model.dart index dde1d74d..6ba126e1 100644 --- a/lib/models/generic_attachment_model.dart +++ b/lib/models/generic_attachment_model.dart @@ -1,9 +1,10 @@ import 'dart:io'; +import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/models/lookup.dart'; class GenericAttachmentModel { - GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy,this.documentTypeId}); + GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy, this.documentTypeId}); int? id; String? name; @@ -11,11 +12,14 @@ class GenericAttachmentModel { String? originalName; Lookup? documentTypeId; - GenericAttachmentModel.fromJson(Map json) { + GenericAttachmentModel.fromJson(Map json, {bool convertToLink = false}) { id = json['id']; - name = json['name']; + name = json['name'] ?? json['imageName']; createdBy = json['createdBy']; - originalName = json['originalName']; + originalName = json['originalName'] ?? json['imageOriginalName']; + if (convertToLink) { + name = URLs.getFileUrl(name); + } } Map toJson() { diff --git a/lib/models/new_models/work_order_detail_model.dart b/lib/models/new_models/work_order_detail_model.dart index 1936113a..984edca5 100644 --- a/lib/models/new_models/work_order_detail_model.dart +++ b/lib/models/new_models/work_order_detail_model.dart @@ -304,17 +304,20 @@ class WorkOrderAsset { required this.id, required this.assetNumber, required this.assetSerialNo, + required this.hasTrainingImageData, }); int? id; String? assetNumber; String? assetSerialNo; + bool? hasTrainingImageData; factory WorkOrderAsset.fromJson(Map json) { return WorkOrderAsset( id: json["id"], assetNumber: json["assetNumber"], assetSerialNo: json["assetSerialNo"], + hasTrainingImageData: json["hasTrainingImageData"], ); } @@ -322,6 +325,7 @@ class WorkOrderAsset { "id": id, "assetNumber": assetNumber, "assetSerialNo": assetSerialNo, + "hasTrainingImageData": hasTrainingImageData, }; } diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart index 1f0b0481..48688b2f 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_detail_view.dart @@ -69,7 +69,7 @@ class _AssetInventoryDetailViewState extends State { InfoTextWidget(label: 'Start Date'.addTranslation, value: sessionModel.startDate?.toServiceRequestDetailsFormat ?? '-'), InfoTextWidget(label: 'End Date'.addTranslation, value: sessionModel.endDate?.toServiceRequestDetailsFormat ?? '-'), ], - ).toShadowContainer(context, padding: 16, borderRadius: 20); + ).toShadowContainer(context, borderRadius: 20); } Widget siteListCard(BuildContext context, SessionModel sessionModel) { @@ -84,7 +84,7 @@ class _AssetInventoryDetailViewState extends State { sites: sites, ), ], - ).toShadowContainer(context, padding: 16, borderRadius: 20); + ).toShadowContainer(context, borderRadius: 20); } } diff --git a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart index 0e44bcda..19ef67fb 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_preview_screen.dart @@ -262,33 +262,15 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoHeader16Widget( - 'Captured Images', - ), - 16.height, + InfoHeader16Widget('Captured Images'), + 12.height, Wrap( spacing: 8, runSpacing: 8, children: taskData.imageRooms!.map((imageString) { return GestureDetector( onTap: () { - // Show full screen image - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - backgroundColor: Colors.black, - iconTheme: const IconThemeData(color: Colors.white), - ), - body: Center( - child: InteractiveViewer( - child: _buildImage(imageString), - ), - ), - ), - ), - ); + context.fullScreenImageViewer(_buildImage(imageString)); }, child: Container( width: 100, @@ -349,12 +331,12 @@ class OneTimeTaskPreviewScreen extends StatelessWidget { SizedBox( width: 100, child: InfoTextLabelWidget( - label:'$label:', + label: '$label:', ), ), Expanded( child: InfoTextLabelWidget( - label: value, + label: value, ), ), ], diff --git a/lib/modules/cleaner_module/pages/one_time_task_view.dart b/lib/modules/cleaner_module/pages/one_time_task_view.dart index 5a94f7e6..25ac466d 100644 --- a/lib/modules/cleaner_module/pages/one_time_task_view.dart +++ b/lib/modules/cleaner_module/pages/one_time_task_view.dart @@ -159,7 +159,7 @@ class _OneTimeTaskViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTaskInfoCard(context), - 16.height, + 12.height, CleanerChecklistWidget( isClosed: isClosed, checklistItems: _convertInstructionsToChecklist(), @@ -276,9 +276,9 @@ class _OneTimeTaskViewState extends State { ), ], ], - ).paddingOnly(start: 16, end: 16, top: 16, bottom: 16), + ), ], - ).toShadowContainer(context, padding: 0); + ).toShadowContainer(context); } List _convertInstructionsToChecklist() { diff --git a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart index 3f69efa1..f6b0a1a0 100644 --- a/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/cleaner_module/widgets/cleaner_checklist_widget.dart @@ -37,18 +37,17 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ InfoHeader16Widget('Checklist'), - 16.height, ListView.separated( shrinkWrap: true, padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), itemBuilder: (cxt, index) => _buildChecklistItem(context, widget.checklistItems[index]), - separatorBuilder: (cxt, index) => const Divider(height: 1, color: AppColor.neutral30, thickness: 1).paddingOnly(top: 16, bottom: 16), + separatorBuilder: (cxt, index) => const Divider().defaultStyle(context), itemCount: widget.checklistItems.length) - .toShadowContainer(context, padding: 16), - 16.height, + .toShadowContainer(context), _buildImageCaptureWidget(context), ], ); @@ -70,7 +69,7 @@ class _CleanerChecklistWidgetState extends State { child: Checkbox( value: item.isChecked, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: BorderSide(color: Colors.black.withValues(alpha: .05)), + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), activeColor: AppColor.primary10, onChanged: (bool? value) {}, @@ -181,12 +180,7 @@ class _CleanerChecklistWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InfoHeader16Widget('Captured Image'), - ], - ), + InfoHeader16Widget('Captured Image'), 8.height, _buildImageWidget(context), ], @@ -277,24 +271,7 @@ class _CleanerChecklistWidgetState extends State { maxImages: 2, // Enforce max 2 images onImageTap: (index, imageUrl) { - // Handle image tap - show in full screen - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer( - child: _buildImageForViewing(imageUrl), - ).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(_buildImageForViewing(imageUrl)); }, onDelete: (index) { // Handle image delete diff --git a/lib/modules/cleaner_module/widgets/multi_image_picker.dart b/lib/modules/cleaner_module/widgets/multi_image_picker.dart index 002acbe4..d970c2d3 100644 --- a/lib/modules/cleaner_module/widgets/multi_image_picker.dart +++ b/lib/modules/cleaner_module/widgets/multi_image_picker.dart @@ -48,8 +48,7 @@ class MultiImagePicker extends StatelessWidget { ), // Add image button (only show if under max limit) - if (enabled && imageUrls.length < maxImages) - _buildAddImageButton(context), + if (enabled && imageUrls.length < maxImages) _buildAddImageButton(context), ], ), ], @@ -77,7 +76,9 @@ class MultiImagePicker extends StatelessWidget { if (onImageTap != null) { onImageTap!(index, imageUrl); } else { - _showFullImage(context, imageUrl); + context.fullScreenImageViewer( + _getImageWidget(imageUrl), + ); } }), if (enabled) @@ -193,26 +194,4 @@ class MultiImagePicker extends StatelessWidget { } } } - - void _showFullImage(BuildContext context, String imageUrl) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Stack( - children: [ - Center( - child: InteractiveViewer( - child: _getImageWidget(imageUrl), - ), - ), - const ABackButton(), - ], - ), - ), - ), - ), - ); - } } diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index afa9ea03..7dd2ba42 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -121,7 +121,10 @@ class _CMDetailPageState extends State { }), if (isNurse) IconButton( - icon: 'qr'.toSvgAsset(height: 24, width: 24,color: AppColor.icon2Color(context)), + icon: 'qr'.toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, + tooltip: "QR Code", onPressed: () { ServiceRequestBottomSheet.getQRCodeBottomSheet(context: context); }, @@ -173,11 +176,16 @@ class _CMDetailPageState extends State { IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: _handlePrint, ), IconButton( icon: const Icon(Icons.home), + padding: EdgeInsets.zero, + iconSize: 20, + visualDensity: VisualDensity.compact, + tooltip:"Home", onPressed: () { // stopTimer(); Navigator.pop(context); diff --git a/lib/modules/cm_module/views/components/activities_list_view.dart b/lib/modules/cm_module/views/components/activities_list_view.dart index d3880a44..a543651f 100644 --- a/lib/modules/cm_module/views/components/activities_list_view.dart +++ b/lib/modules/cm_module/views/components/activities_list_view.dart @@ -175,7 +175,7 @@ class _ActivitiesListViewState extends State { FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()), ], ], - ).toShadowContainer(context, padding: 12, showShadow: false).onPress(() { + ).toShadowContainer(context, showShadow: false).onPress(() { if (showActionButtons) return; if (requestDetailProvider.isReadOnlyRequest) { editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); @@ -272,7 +272,7 @@ class _ActivitiesListViewState extends State { // // ), // ] ], - ).toShadowContainer(context, padding: 12, showShadow: false).onPress(() { + ).toShadowContainer(context, showShadow: false).onPress(() { if (showActionButtons) return; if (requestDetailProvider.isReadOnlyRequest) { editMaintenanceRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); diff --git a/lib/modules/cm_module/views/components/history_log_view.dart b/lib/modules/cm_module/views/components/history_log_view.dart index e549f02f..b3fef162 100644 --- a/lib/modules/cm_module/views/components/history_log_view.dart +++ b/lib/modules/cm_module/views/components/history_log_view.dart @@ -120,20 +120,7 @@ class HistoryLogView extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 12.toScreenWidth), child: InteractiveViewer(child: Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!, height: 143.toScreenHeight)), ).onPress(() async { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - InteractiveViewer(child: Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(Image.network(URLs.getFileUrl(object.photoVerfication ?? '')!)); }), ], title.bodyText(context).custom(color: AppColor.textColor(context)), @@ -142,7 +129,7 @@ class HistoryLogView extends StatelessWidget { ), ), ], - ).toShadowContainer(context, padding: 16).paddingOnly(bottom: 16); + ).toShadowContainer(context).paddingOnly(bottom: 16); } Widget statusCard(BuildContext context, String status) { diff --git a/lib/modules/cm_module/views/components/initial_visit_card.dart b/lib/modules/cm_module/views/components/initial_visit_card.dart index 424351a5..ecd93663 100644 --- a/lib/modules/cm_module/views/components/initial_visit_card.dart +++ b/lib/modules/cm_module/views/components/initial_visit_card.dart @@ -55,7 +55,7 @@ class _InitialVisitCardState extends State { requestDetailProvider.needVisitHelperModel!.comment!.bodyText2(context).custom(color: AppColor.neutral120), ] ], - ).toShadowContainer(context, padding: 16); + ).toShadowContainer(context); }); } } diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index c6fef7db..73a7f9ea 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -18,6 +18,7 @@ import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retire import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.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/equipment/asset_detail_page.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'package:test_sa/views/widgets/item_views/info_date_widget.dart'; @@ -141,7 +142,7 @@ class _ServiceRequestDetailViewState extends State { // spacing: 8, runSpacing: 8, children: [ - if (Provider.of(context, listen: false).showPriority&&workOrder.priority!=null) ...[ + if (Provider.of(context, listen: false).showPriority && workOrder.priority != null) ...[ StatusLabel( label: workOrder.priority?.name, id: workOrder.priority?.value, @@ -192,9 +193,23 @@ class _ServiceRequestDetailViewState extends State { ], ), 8.height, - InfoHeader16Widget(context.translation.assetInformation), + Row( + spacing: 4, + children: [ + Flexible( + child: InfoHeader16Widget(context.translation.assetInformation), + ), + const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 16).onPress(() { + Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id); + }) + ], + ), + if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[ + InfoTextLabelWidget(label: '*Images are required for the asset', color: AppColor.red30, textStyle: AppTextStyles.tinyFont2), + 4.height, + ], InfoTextWidget(label: context.translation.assetName, value: workOrder.assetNdModel!.name?.cleanupWhitespace.capitalizeFirstOfEach), - InfoTextWidget(label: context.translation.assetNumber, value: workOrder.asset!.assetNumber), + InfoTextWidget(label: context.translation.assetNumber, value: workOrder.asset!.assetNumber, copyValue: true), InfoTextWidget(label: context.translation.serialNo, value: workOrder.asset!.assetSerialNo), InfoTextWidget(label: context.translation.manufacture, value: workOrder.manufacturer?.name?.cleanupWhitespace.capitalizeFirstOfEach), InfoTextWidget(label: context.translation.model, value: workOrder.model?.name!.cleanupWhitespace.capitalizeFirstOfEach), @@ -212,7 +227,7 @@ class _ServiceRequestDetailViewState extends State { //... request details starts here.... InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.cmNumber, value: workOrder.workOrderNo), + InfoTextWidget(label: context.translation.cmNumber, value: workOrder.workOrderNo, copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: workOrder.workOrderCreatedBy?.userName ?? "-"), InfoTextWidget(label: context.translation.arrivedDate, value: workOrder.iHaveArrivedDate != null ? workOrder.iHaveArrivedDate.toString().toServiceRequestDetailsFormat : '-'), InfoTextWidget(label: context.translation.closedDate, value: workOrder.closedDate != null ? workOrder.closedDate!.toString().toServiceRequestDetailsFormat : '-'), @@ -282,9 +297,7 @@ class _ServiceRequestDetailViewState extends State { assetRetiredButton(context: context), ] ], - ).toShadowContainer( - context, - ); + ).toShadowContainer(context); } Widget contactInfoCard(BuildContext context, WorkOrderData workOrder) { @@ -340,7 +353,7 @@ class _ServiceRequestDetailViewState extends State { InfoTextWidget(label: 'MR No'.addTranslation, value: provider.currentWorkOrder?.data?.mrNo?.toString() ?? '-'), InfoTextWidget(label: 'Exchange Cost'.addTranslation, value: provider.currentWorkOrder?.data?.exchangeCost?.toString() ?? '-'), ], - ).toShadowContainer(context, padding: 16); + ).toShadowContainer(context); } Widget assetRetiredButton({required BuildContext context}) { diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index ea9893b6..6b0fbd37 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -388,7 +388,7 @@ class _ChatPageState extends State { clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => ChatFilePicker())) as File?; if (file != null) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1"); Utils.hideLoading(context); } @@ -638,7 +638,7 @@ class _ChatPageState extends State { hoverColor: Colors.transparent, onPressed: () async { if (chatState == ChatState.voiceRecordingCompleted) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); try { await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1"); Utils.hideLoading(context); diff --git a/lib/modules/cx_module/chat/helper/chat_file_viewer.dart b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart index 9eee8c44..34653e1b 100644 --- a/lib/modules/cx_module/chat/helper/chat_file_viewer.dart +++ b/lib/modules/cx_module/chat/helper/chat_file_viewer.dart @@ -24,7 +24,6 @@ class ChatFileViewer extends StatelessWidget { @override Widget build(BuildContext context) { - return FutureBuilder( future: checkFileInLocalStorage(), builder: (BuildContext context, AsyncSnapshot snapshot) { @@ -80,16 +79,7 @@ class ChatFileViewer extends StatelessWidget { padding: EdgeInsets.zero, onPressed: () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer(child: Image.file(file)).center, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(file)); } else { OpenFile.open(file.path); } diff --git a/lib/modules/cx_module/chat/view_all_attachment_page.dart b/lib/modules/cx_module/chat/view_all_attachment_page.dart index 1e442591..8eb13b7d 100644 --- a/lib/modules/cx_module/chat/view_all_attachment_page.dart +++ b/lib/modules/cx_module/chat/view_all_attachment_page.dart @@ -182,16 +182,7 @@ class AttachmentFileViewer extends StatelessWidget { ), ).onPress(() { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer(child: Image.file(snapshot.data!)).center, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(snapshot.data!)); } else { OpenFile.open(snapshot.data!.path); } @@ -253,16 +244,7 @@ class AttachmentFileViewer extends StatelessWidget { padding: EdgeInsets.zero, onPressed: () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer(child: Image.file(file)).center, - ), - ), - ), - ); + context.fullScreenImageViewer(Image.file(file)); } else { OpenFile.open(file.path); } diff --git a/lib/modules/demo_module/create_demo_request_page.dart b/lib/modules/demo_module/create_demo_request_page.dart index a921ed91..4ff8f3ff 100644 --- a/lib/modules/demo_module/create_demo_request_page.dart +++ b/lib/modules/demo_module/create_demo_request_page.dart @@ -43,6 +43,8 @@ import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/work_order/vendor_provider.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.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 '../../../../../../new_views/common_widgets/default_app_bar.dart'; import '../../models/new_models/department.dart'; import 'demo_detail_view_page.dart'; @@ -92,76 +94,23 @@ class _CreateDemoRequestPageState extends State with Tick SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( + spacing: 12, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // SingleItemDropDownMenu( - // context: context, - // title: "Demo Period", - // initialValue: demoPeriodLookup, - // showShadow: false, - // validator: (value) { - // if (value == null) return "Mandatory"; - // return null; - // }, - // backgroundColor: AppColor.fieldBgColor(context), - // showAsBottomSheet: true, - // onSelect: (value) { - // demoPeriodLookup = value; - // setState(() {}); - // }, - // ), - // 16.height, ...siteSection(), - 16.height, ...doctorInfoSection(), - 16.height, ...assetSection(), - 16.height, ...vendorDetailsSection(), - 16.height, - "Attachments".bodyText(context).custom(color: AppColor.black10), - 8.height, - SingleItemDropDownMenu( - context: context, - height: 56.toScreenHeight, - title: "Document Type", - initialValue: demoDocumentLookup, - showShadow: false, - validator: (value) { - if (value == null) return "Mandatory"; - return null; - }, - backgroundColor: AppColor.fieldBgColor(context), - showAsBottomSheet: true, - onSelect: (value) { - demoDocumentLookup = value; - setState(() {}); - }, - ), - 16.height, - AttachmentPicker( - label: context.translation.attachments, - attachment: _attachments, - buttonColor: AppColor.black10.withOpacity(demoDocumentLookup != null ? 1 : .4), - onlyImages: false, - enabled: demoDocumentLookup != null, - documentType: demoDocumentLookup, - showAsListView: true, - buttonIcon: 'image-plus'.toSvgAsset(color: (AppColor.primary10).withOpacity(demoDocumentLookup != null ? 1 : .4)), - onChange: (attachments) { - _attachments = attachments; - setState(() {}); - }, - ), - 16.height, + ...attachmentSection(), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Checkbox( value: _demoFormModel.demoEaluationAcknowledge, - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), + visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { setState(() { @@ -172,24 +121,29 @@ class _CreateDemoRequestPageState extends State with Tick Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ - "DEMO outcome evaluation form acknowledgement".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), - 4.height, - "I acknowledge at the end of the trial period I will complete and submit the Medical Technology Trial Outcome Form within two weeks." - .tinyFont(context) - .custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral120), + InfoTextLabelWidget( + label: "DEMO outcome evaluation form acknowledgement", + textStyle: AppTextStyles.bodyText, + color: AppColor.textColor(context), + ), + InfoTextLabelWidget( + label: "I acknowledge at the end of the trial period I will complete and submit the Medical Technology Trial Outcome Form within two weeks.", + color: AppColor.lightTextColor(context), + ), ], ).expanded, ], ), - 16.height, Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Checkbox( value: _demoFormModel.acknowledgeNotEquipment, - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), + visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { setState(() { @@ -200,18 +154,23 @@ class _CreateDemoRequestPageState extends State with Tick Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, children: [ - "Acknowledgement of not using the equipment".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), - 4.height, - "I acknowledge at the end of the trial period I will not use the DEMO medical equipment & I will not book any patient on the DEMO medical equipment." - .tinyFont(context) - .custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral120), + InfoTextLabelWidget( + label: "Acknowledgement of not using the equipment", + textStyle: AppTextStyles.bodyText, + color: AppColor.textColor(context), + ), + InfoTextLabelWidget( + label: "I acknowledge at the end of the trial period I will not use the DEMO medical equipment & I will not book any patient on the DEMO medical equipment.", + color: AppColor.lightTextColor(context), + ), ], ).expanded, ], ), ], - ).toShadowContainer(context, borderRadius: 20), + ).toShadowContainer(context), ).expanded, FooterActionButton.footerContainer( context: context, @@ -231,8 +190,7 @@ class _CreateDemoRequestPageState extends State with Tick List siteSection() { return [ - 'Site Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Site Details'.addTranslation), SingleItemDropDownMenu( context: context, title: context.translation.site, @@ -252,7 +210,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.building, @@ -273,7 +230,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.floor, @@ -293,7 +249,6 @@ class _CreateDemoRequestPageState extends State with Tick setState(() {}); }, ), - 8.height, SingleItemDropDownMenu( context: context, title: context.translation.department, @@ -318,8 +273,7 @@ class _CreateDemoRequestPageState extends State with Tick List doctorInfoSection() { return [ - "Doctor Details".bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Doctor Details'.addTranslation), AppTextFormField( initialValue: "", labelText: "Name", @@ -336,7 +290,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docName = value; }, ), - 8.height, AppTextFormField( initialValue: "", labelText: "Contact Number", @@ -353,7 +306,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docNumber = value; }, ), - 8.height, AppTextFormField( initialValue: "", labelText: "Email", @@ -373,7 +325,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.docEmail = value; }, ), - 8.height, AppTextFormField( labelText: "Item Description", validator: (value) { @@ -391,7 +342,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.itemDescription = value; }, ), - 8.height, AppTextFormField( labelText: "Request Description", // validator: (value) { @@ -414,8 +364,7 @@ class _CreateDemoRequestPageState extends State with Tick List assetSection() { return [ - 'Asset Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Asset Details'.addTranslation), AppTextFormField( labelText: "Asset Name", validator: (value) { @@ -433,7 +382,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.assetName = value; }, ), - 8.height, AppTextFormField( labelText: "Model", validator: (value) { @@ -451,7 +399,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.model = value; }, ), - 8.height, AppTextFormField( labelText: "Manufacturer", validator: (value) { @@ -469,7 +416,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.manufacturer = value; }, ), - 8.height, SingleItemDropDownMenu( context: context, height: 56.toScreenHeight, @@ -492,8 +438,7 @@ class _CreateDemoRequestPageState extends State with Tick List vendorDetailsSection() { return [ - 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), - 8.height, + InfoHeader16Widget('Vendor Details'.addTranslation), AppTextFormField( labelText: 'Vendor Name'.addTranslation, style: Theme.of(context).textTheme.titleMedium, @@ -506,7 +451,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorName = value; }, ), - 8.height, AppTextFormField( labelText: 'Representative Name'.addTranslation, style: Theme.of(context).textTheme.titleMedium, @@ -519,7 +463,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorRepresentativeName = value; }, ), - 8.height, AppTextFormField( // controller: vendorPhoneController, labelText: "Contact Number", @@ -533,7 +476,6 @@ class _CreateDemoRequestPageState extends State with Tick _demoFormModel.vendorContactNumber = value; }, ), - 8.height, AppTextFormField( labelText: "Email", // controller: vendorEmailController, @@ -550,6 +492,43 @@ class _CreateDemoRequestPageState extends State with Tick ]; } + List attachmentSection() { + return [ + InfoHeader16Widget('Attachments'.addTranslation), + SingleItemDropDownMenu( + context: context, + height: 56.toScreenHeight, + title: "Document Type", + initialValue: demoDocumentLookup, + showShadow: false, + validator: (value) { + if (value == null) return "Mandatory"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + showAsBottomSheet: true, + onSelect: (value) { + demoDocumentLookup = value; + setState(() {}); + }, + ), + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + buttonColor: AppColor.primary10.withOpacity(demoDocumentLookup != null ? 1 : .4), + onlyImages: false, + enabled: demoDocumentLookup != null, + documentType: demoDocumentLookup, + showAsListView: true, + buttonIcon: 'image-plus'.toSvgAsset(color: (AppColor.primary10).withOpacity(demoDocumentLookup != null ? 1 : .4)), + onChange: (attachments) { + _attachments = attachments; + setState(() {}); + }, + ), + ]; + } + Future _submit() async { FocusScope.of(context).unfocus(); if (_formKey.currentState!.validate()) { diff --git a/lib/modules/demo_module/demo_detail_view_page.dart b/lib/modules/demo_module/demo_detail_view_page.dart index cac6e356..6d98e438 100644 --- a/lib/modules/demo_module/demo_detail_view_page.dart +++ b/lib/modules/demo_module/demo_detail_view_page.dart @@ -16,6 +16,8 @@ import 'package:test_sa/modules/loan_module/models/loan_attachment_model.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/new_views/swipe_module/dialoge/acknowledge_work_dialog.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/loaders/no_data_found.dart'; import '../../controllers/api_routes/urls.dart'; @@ -111,7 +113,6 @@ class _DemoDetailViewPageState extends State { ), 8.height, ], - 12.height, ...requesterDetails(context, demoData), const Divider().defaultStyle(context), ...requestDetails(context, demoData), @@ -123,10 +124,7 @@ class _DemoDetailViewPageState extends State { ...vendorDetails(context, demoData), if (allAttachments.isNotEmpty) ...[ const Divider().defaultStyle(context), - Text( - "Attachments".addTranslation, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), + InfoHeader16Widget("Attachments"), FilesList( showAsListView: true, images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList(), @@ -134,10 +132,7 @@ class _DemoDetailViewPageState extends State { ], if (suppAttachments.isNotEmpty) ...[ const Divider().defaultStyle(context), - Text( - "Supplier Attachments".addTranslation, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), + InfoHeader16Widget("Supplier Attachments"), FilesList( showAsListView: true, images: suppAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList(), @@ -191,53 +186,53 @@ class _DemoDetailViewPageState extends State { List requestDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Request Details", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Request No: ${model?.requestNo ?? "-"}'.bodyText(context), - 'Demo No: ${model?.demoNo ?? "-"}'.bodyText(context), - 'Demo Period: ${model?.demoPeriod?.name ?? "-"}'.bodyText(context), - '${context.translation.site}: ${model?.site?.custName ?? "-"}'.bodyText(context), - '${context.translation.building}: ${model?.building?.name ?? "-"}'.bodyText(context), - '${context.translation.floor}: ${model?.floor?.name ?? "-"}'.bodyText(context), - '${context.translation.department}: ${model?.department?.departmentName ?? "-"}'.bodyText(context), - 'Item Description: ${model?.itemDescription ?? "-"}'.bodyText(context), - 'Request Description: ${model?.requestDescription ?? "-"}'.bodyText(context), + InfoHeader16Widget("Request Information"), + InfoTextWidget(label: "Request No", value: model?.requestNo ?? "-", copyValue: true), + InfoTextWidget(label: "Demo No", value: model?.demoNo ?? "-"), + InfoTextWidget(label: "Demo Period", value: model?.demoPeriod?.name ?? "-"), + InfoTextWidget(label: context.translation.site, value: model?.site?.custName ?? "-"), + InfoTextWidget(label: context.translation.building, value: model?.building?.name ?? "-"), + InfoTextWidget(label: context.translation.floor, value: model?.floor?.name ?? "-"), + InfoTextWidget(label: context.translation.department, value: model?.department?.departmentName ?? "-"), + InfoTextWidget(label: "Item Description", value: model?.itemDescription ?? "-"), + InfoTextWidget(label: "Request Description", value: model?.requestDescription ?? "-"), ]; } List vendorDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Vendor Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.supplier?.name ?? model?.vendorName ?? "-"}'.bodyText(context), - 'Representative Name: ${model?.suppPerson?.name ?? model?.vendorRepresentativeName ?? "-"}'.bodyText(context), - 'Contact: ${model?.supplier?.contact ?? model?.vendorContactNumber ?? "-"}'.bodyText(context), - 'Email: ${model?.supplier?.email ?? model?.vendorEmail ?? "-"}'.bodyText(context), + InfoHeader16Widget("Vendor Information"), + InfoTextWidget(label: "Name", value: model?.supplier?.name ?? model?.vendorName ?? "-"), + InfoTextWidget(label: "Representative Name", value: model?.suppPerson?.name ?? model?.vendorRepresentativeName ?? "-"), + InfoTextWidget(label: "Contact", value: model?.supplier?.contact ?? model?.vendorContactNumber ?? "-"), + InfoTextWidget(label: "Email", value: model?.supplier?.email ?? model?.vendorEmail ?? "-"), ]; } List assetDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Asset Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - '${context.translation.assetName}: ${model?.assetName ?? "-"}'.bodyText(context), - '${context.translation.manufacture}: ${model?.manufacturer ?? "-"}'.bodyText(context), - '${context.translation.model}: ${model?.model ?? "-"}'.bodyText(context), + InfoHeader16Widget("Asset Information"), + InfoTextWidget(label: context.translation.assetName, value: model?.assetName ?? "-"), + InfoTextWidget(label: context.translation.manufacture, value: model?.manufacturer ?? "-"), + InfoTextWidget(label: context.translation.model, value: model?.model ?? "-"), ]; } List doctorDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Doctor Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.doctorName ?? "-"}'.bodyText(context), - 'Contact: ${model?.doctorContactNumber ?? "-"}'.bodyText(context), - 'Email: ${model?.doctorContactEmail ?? "-"}'.bodyText(context), + InfoHeader16Widget("Doctor Information"), + InfoTextWidget(label: "Name", value: model?.doctorName ?? "-"), + InfoTextWidget(label: "Contact", value: model?.doctorContactNumber ?? "-"), + InfoTextWidget(label: "Email", value: model?.doctorContactEmail ?? "-"), ]; } List requesterDetails(BuildContext context, DemoRequestModel? model) { return [ - Text("Requester Information", style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50)), - 'Name: ${model?.requesterUser?.requesterName ?? "-"}'.bodyText(context), - 'Email: ${model?.requesterUser?.requesterEmail ?? "-"}'.bodyText(context), - 'Contact: ${model?.requesterUser?.requesterMobile ?? "-"}'.bodyText(context), + InfoHeader16Widget("Requester Information"), + InfoTextWidget(label: "Name", value: model?.requesterUser?.requesterName ?? "-"), + InfoTextWidget(label: "Email", value: model?.requesterUser?.requesterEmail ?? "-"), + InfoTextWidget(label: "Contact", value: model?.requesterUser?.requesterMobile ?? "-"), // 'Extension: ${loanData.requesterExtensionNo ?? "-"}'.bodyText(context), ]; } diff --git a/lib/modules/export_module/provider/export_provider.dart b/lib/modules/export_module/provider/export_provider.dart index f80c390e..81b80f8e 100644 --- a/lib/modules/export_module/provider/export_provider.dart +++ b/lib/modules/export_module/provider/export_provider.dart @@ -50,6 +50,7 @@ class ExportApiProvider extends ChangeNotifier { valueColor: textColor, elevation: 4, msgFontSize: 14, + borderRadius: 14, backgroundColor: backgroundColor, cancel: Cancel( cancelClicked: () { diff --git a/lib/modules/export_module/widgets/cleaner_checklist_widget.dart b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart index 12d22165..090de70b 100644 --- a/lib/modules/export_module/widgets/cleaner_checklist_widget.dart +++ b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart @@ -200,21 +200,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], @@ -241,21 +227,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], @@ -300,21 +272,7 @@ class _CleanerChecklistWidgetState extends State { ), child: imageWidget) .onPress(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: imageWidget).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(imageWidget); }), _delete(), ], diff --git a/lib/modules/export_module/widgets/file_success_dialog.dart b/lib/modules/export_module/widgets/file_success_dialog.dart index 015ff7e0..e66c0c43 100644 --- a/lib/modules/export_module/widgets/file_success_dialog.dart +++ b/lib/modules/export_module/widgets/file_success_dialog.dart @@ -7,6 +7,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.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'; /// Reusable success dialog for file operations (export, print, etc.) class FileSuccessDialog { @@ -31,47 +32,25 @@ class FileSuccessDialog { return Dialog( backgroundColor: AppColor.background(context), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14), ), child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Close button Align( alignment: Alignment.topRight, - child: 'clear'.toSvgAsset( - color: AppColor.icon2Color(context), - width: 18, - height: 18, - ).onPress(() { + child: 'clear'.toSvgAsset(color: AppColor.icon2Color(context), width: 18, height: 18).onPress(() { Navigator.of(dialogContext).pop(); }), ), - // Success icon 'done'.toSvgAsset(color: AppColor.green70, width: 56, height: 56), const SizedBox(height: 24), - // Title - Text( - title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: AppColor.headingTextColor(context), - ), - textAlign: TextAlign.center, - ), + title.heading5(context).custom(fontWeight: FontWeight.w600, color: AppColor.textStyleColor(context)), const SizedBox(height: 12), - // Message - Text( - message, - style: TextStyle( - fontSize: 18, - color: AppColor.lightTextColor(context), - ), - textAlign: TextAlign.center, - ), + message.heading6(context).custom(align: TextAlign.center, fontWeight: FontWeight.w500, color: AppColor.lightTextColor(context)), const SizedBox(height: 32), // Action buttons Row( @@ -84,14 +63,13 @@ class FileSuccessDialog { final result = await OpenFile.open(filePath); if (result.type != ResultType.done) { log('⚠️ Could not open file: ${result.message}'); - 'No app found to open this file type. File saved to: $filePath' - .showToast; + 'No app found to open this file type. File saved to: $filePath'.showToast; } }, label: 'Open', // buttonColor: AppColor.primary10, showIcon: true, - icon: Icon(Icons.folder_open, color: AppColor.background(context)), + icon: Icon(Icons.folder_open, color: AppColor.background(context)), height: 48, ), ), @@ -131,4 +109,3 @@ class FileSuccessDialog { ); } } - diff --git a/lib/modules/incident_module/create_incident_request_page.dart b/lib/modules/incident_module/create_incident_request_page.dart index 3e8e0aad..d1535400 100644 --- a/lib/modules/incident_module/create_incident_request_page.dart +++ b/lib/modules/incident_module/create_incident_request_page.dart @@ -133,7 +133,7 @@ class _CreateIncidentRequestPageState extends State { onPick: (asset) async { device = asset; payload["assetId"] = asset?.id; - payload["oracleCode"] = asset.modelDefinition?.oracleCodes?.first.codeValue; + payload["oracleCode"] = (asset.modelDefinition?.oracleCodes?.isNotEmpty ?? false) ? asset.modelDefinition?.oracleCodes?.first.codeValue : null; payload["model"] = asset.modelDefinition?.modelName; payload["manufacturer"] = asset.modelDefinition?.manufacturerName; payload["assetOrigin"] = asset.modelDefinition?.assetName; diff --git a/lib/modules/incident_module/incident_detail_page.dart b/lib/modules/incident_module/incident_detail_page.dart index b131c0eb..4f9ade6e 100644 --- a/lib/modules/incident_module/incident_detail_page.dart +++ b/lib/modules/incident_module/incident_detail_page.dart @@ -41,41 +41,39 @@ class IncidentDetailPage extends StatelessWidget { List allAttachments = snapshot.data!.incidentAttachments!; - return ListView( + return SingleChildScrollView( padding: const EdgeInsets.all(16), - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - StatusLabel( - label: snapshot.data!.incidentStatusName!, - textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), - backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), - ), - 1.width.expanded, - InfoDateWidget(snapshot.data!.createdDate?.toString() ?? ""), - ], - ), - // 12.height, - ...requesterDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...requestDetails(context, snapshot.data!), - if (snapshot.data!.ovrTicketNumber?.isNotEmpty ?? false) ovrInformation(context, snapshot.data!.ovrTicketNumber!), - const Divider().defaultStyle(context), - ...assetDetails(context, snapshot.data!), - const Divider().defaultStyle(context), - ...personInvolvedDetails(context, snapshot.data!), - if (allAttachments.isNotEmpty) ...[ - const Divider().defaultStyle(context), - InfoHeader16Widget("Attachments".addTranslation), - FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: snapshot.data!.incidentStatusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.incidentStatusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.incidentStatusName!), + ), + 1.width.expanded, + InfoDateWidget(snapshot.data!.createdDate?.toString() ?? ""), ], + ), + // 12.height, + ...requesterDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...requestDetails(context, snapshot.data!), + if (snapshot.data!.ovrTicketNumber?.isNotEmpty ?? false) ovrInformation(context, snapshot.data!.ovrTicketNumber!), + const Divider().defaultStyle(context), + ...assetDetails(context, snapshot.data!), + const Divider().defaultStyle(context), + ...personInvolvedDetails(context, snapshot.data!), + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + InfoHeader16Widget("Attachments".addTranslation), + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], - ).toShadowContainer(context, padding: 16), - ], + ], + ).toShadowContainer(context), ); })); } @@ -83,7 +81,7 @@ class IncidentDetailPage extends StatelessWidget { List requestDetails(BuildContext context, IncidentDataModel incidentData) { return [ InfoHeader16Widget("Request Details"), - InfoTextWidget(label: 'Incident No', value: incidentData.reqCode ?? "-"), + InfoTextWidget(label: 'Incident No', value: incidentData.reqCode ?? "-", copyValue: true), InfoTextWidget(label: 'Incident Type', value: incidentData.incidentClassificationName ?? "-"), InfoTextWidget(label: 'Incident Title', value: incidentData.incidentTitle ?? "-"), InfoTextWidget(label: 'Incident Description', value: incidentData.incidentDescription ?? "-"), @@ -95,10 +93,10 @@ class IncidentDetailPage extends StatelessWidget { InfoTextWidget(label: 'Occurrence Date', value: incidentData.occurrenceDate?.toServiceRequestDetailsFormat ?? "-"), if (incidentData.workOrderId != null) RichText( - text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ + text: TextSpan(text: "Work Order: ", style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), children: [ TextSpan( text: "${incidentData.workOrderNo}", - style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + style: AppTextStyles.bodyText.copyWith(color: AppColor.primary10, decoration: TextDecoration.underline), recognizer: TapGestureRecognizer() ..onTap = () async { await Navigator.of(context).push(MaterialPageRoute(builder: (_) => CMDetailPage(requestId: incidentData.workOrderId!, moduleId: 3))); diff --git a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart index f1f44a77..b617db05 100644 --- a/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart +++ b/lib/modules/internal_audit_module/pages/equipment_internal_audit/equipment_internal_audit_detail_page.dart @@ -185,7 +185,7 @@ class _EquipmentInternalAuditDetailPageState extends State { FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), ], ], - ).toShadowContainer(context, padding: 12) + ).toShadowContainer(context) ], ).expanded, // @todo ask backend to add loanTypeValue, so we can show or hide the buttons @@ -184,7 +184,7 @@ class _LoanEquipmentDetailPageState extends State { return [ InfoHeader16Widget("Request Details"), InfoTextWidget(label: 'Loan Type', value: loanData.loanTypeName ?? "-"), - InfoTextWidget(label: 'Loan No', value: loanData.reqCode ?? "-"), + InfoTextWidget(label: 'Loan No', value: loanData.reqCode ?? "-", copyValue: true), InfoTextWidget(label: 'Loan Period', value: loanData.loanPeriodName ?? "-"), InfoTextWidget(label: context.translation.site, value: loanData.siteName ?? "-"), InfoTextWidget(label: context.translation.department, value: loanData.departmentName ?? "-"), diff --git a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart index 61b4e037..b499f20b 100644 --- a/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart +++ b/lib/modules/medical_gas_inspection/medical_gas_inspection_detail_page.dart @@ -90,7 +90,7 @@ class _MedicalGasInspectionDetailPageState extends State URLs.getFileUrl(e.name ?? '') ?? '').toList() ?? []), ], ], - ).toShadowContainer(context, padding: 16), + ).toShadowContainer(context,), ], ).expanded, if (model?.requestStatus?.value != null && model!.requestStatus!.value! < 3) diff --git a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart index c4fd21ba..47c77c39 100644 --- a/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart +++ b/lib/modules/medical_gas_inspection/update_delivery_notes_page.dart @@ -400,7 +400,8 @@ class _UpdateDeliveryNotesState extends State { vertical: 6, // keeps normal height ~30 ), filled: true, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(context), + // fillColor: AppColor.fieldBgColor(context), border: border, enabledBorder: border, focusedBorder: border, diff --git a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart index 622131a3..bf64bf9a 100644 --- a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart +++ b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart @@ -70,6 +70,7 @@ class _PpmDetailsPageState extends State { : planPreventiveVisit != null ? Column(children: [ SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -86,7 +87,7 @@ class _PpmDetailsPageState extends State { InfoHeader16Widget(planPreventiveVisit.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? ''), InfoTextWidget(label: context.translation.assetNumber, value: planPreventiveVisit.asset?.assetNumber ?? '-'), InfoTextWidget(label: context.translation.assetSN, value: planPreventiveVisit.asset?.assetSerialNo ?? '-'), - InfoTextWidget(label: context.translation.requestNo, value: planPreventiveVisit.visitNo ?? '-'), + InfoTextWidget(label: context.translation.requestNo, value: planPreventiveVisit.visitNo ?? '-', copyValue: true), if (planPreventiveVisit.acutalDateOfVisit != null) ...[ const Divider().defaultStyle(context), InfoTextWidget(label: context.translation.actualDate, value: planPreventiveVisit.acutalDateOfVisit?.toIso8601String().toAssetDetailsFormat ?? ""), @@ -99,7 +100,7 @@ class _PpmDetailsPageState extends State { InfoTextWidget(label: context.translation.department, value: planPreventiveVisit.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'), InfoTextWidget(label: context.translation.room, value: (planPreventiveVisit.roomName ?? "").cleanupWhitespace.capitalizeFirstOfEach), ], - ).toShadowContainer(context, padding: 16), + ).toShadowContainer(context), ).expanded, if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[ FooterActionButton.footerContainer( diff --git a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart index e76ca8bd..d65022c7 100644 --- a/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart +++ b/lib/modules/pm_module/recurrent_wo/components/task_info_widget.dart @@ -80,7 +80,7 @@ class _RecurrentTaskInfoWidgetState extends State { ), 8.height, InfoHeader16Widget(widget.model!.title!), - InfoTextWidget(label: context.translation.taskNo, value: widget.model!.taskNo!), + InfoTextWidget(label: context.translation.taskNo, value: widget.model!.taskNo!, copyValue: true), InfoTextWidget(label: 'Plan Name:'.addTranslation, value: widget.model?.planRecurrent?.planName), InfoTextWidget(label: context.translation.site, value: widget.model!.site!.siteName!), if (widget.isHouseKeeper) ...[ diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index bf55ae1e..04802a5e 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -91,6 +91,7 @@ class _DeviceFilterScreenState extends State { return Scaffold( appBar: DefaultAppBar( title: context.translation.filter, + showBackButtonAsCancel: true, actions: [ if (loading == false && _showResetButton()) TextButton( diff --git a/lib/modules/tm_module/device_transfer/device_search_screen.dart b/lib/modules/tm_module/device_transfer/device_search_screen.dart index 74cdeeb6..88050fb2 100644 --- a/lib/modules/tm_module/device_transfer/device_search_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_search_screen.dart @@ -51,7 +51,6 @@ class _DeviceSearchScreenState extends State { _modelController.dispose(); _supplierController.dispose(); _siteController.dispose(); - _siteController.dispose(); _manufactureController.dispose(); _mdController.dispose(); super.dispose(); @@ -62,6 +61,7 @@ class _DeviceSearchScreenState extends State { return Scaffold( appBar: DefaultAppBar( title: context.translation.search, + showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( diff --git a/lib/modules/tm_module/device_transfer/device_transfer_details.dart b/lib/modules/tm_module/device_transfer/device_transfer_details.dart index a7579fb2..3c4c0993 100644 --- a/lib/modules/tm_module/device_transfer/device_transfer_details.dart +++ b/lib/modules/tm_module/device_transfer/device_transfer_details.dart @@ -94,7 +94,7 @@ class _DeviceTransferDetailsState extends State { ), 8.height, InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.trNumber, value: _model?.transferCode ?? "-"), + InfoTextWidget(label: context.translation.trNumber, value: _model?.transferCode ?? "-", copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: _model?.name ?? "-"), const Divider().defaultStyle(context), InfoHeader16Widget(context.translation.assetInformation), diff --git a/lib/modules/tm_module/gas_refill/gas_refill_details.dart b/lib/modules/tm_module/gas_refill/gas_refill_details.dart index 520d2518..bbeee78a 100644 --- a/lib/modules/tm_module/gas_refill/gas_refill_details.dart +++ b/lib/modules/tm_module/gas_refill/gas_refill_details.dart @@ -204,7 +204,7 @@ class _GasRefillDetailsPageState extends State { ), 8.height, InfoHeader16Widget(context.translation.callDetails), - InfoTextWidget(label: context.translation.requestNo, value: gasRefillModel.gasRefillNo ?? '-'), + InfoTextWidget(label: context.translation.requestNo, value: gasRefillModel.gasRefillNo ?? '-', copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: gasRefillModel.name ?? '-'), InfoTextWidget(label: context.translation.gasType, value: gasRefillModel.gasRefillDetails?[0].gasType?.name ?? '-'), InfoTextWidget(label: context.translation.cylinderType, value: gasRefillModel.gasRefillDetails?[0].cylinderType?.name ?? '-'), diff --git a/lib/modules/tm_module/tasks/task_request_detail_view.dart b/lib/modules/tm_module/tasks/task_request_detail_view.dart index ca6f02e8..326c3989 100644 --- a/lib/modules/tm_module/tasks/task_request_detail_view.dart +++ b/lib/modules/tm_module/tasks/task_request_detail_view.dart @@ -103,6 +103,7 @@ class _TaskRequestDetailsViewState extends State { ? const ALoading() : Column(children: [ SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -136,7 +137,7 @@ class _TaskRequestDetailsViewState extends State { const Divider().defaultStyle(context), InfoHeader16Widget("Task Details".addTranslation), InfoTextWidget(label: context.translation.taskType, value: taskModel?.taskType?.typeName ?? "-"), - InfoTextWidget(label: context.translation.taskNo, value: taskModel?.taskJobNo ?? "-"), + InfoTextWidget(label: context.translation.taskNo, value: taskModel?.taskJobNo ?? "-", copyValue: true), InfoTextWidget(label: context.translation.createdBy, value: taskModel?.userCreated?.userName ?? "-"), if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == true) ...[ recallAlertTypeWidget(taskModel: taskProvider.taskRequestModel!), @@ -243,12 +244,13 @@ class _TaskRequestDetailsViewState extends State { return RichText( text: TextSpan( text: "${context.translation.totalWorkingTime}: ", - style: TextStyle( - fontSize: 14.toScreenWidth, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, decoration: TextDecoration.none, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), + style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)), + // TextStyle( + // fontSize: 14.toScreenWidth, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, decoration: TextDecoration.none, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), children: [ - TextSpan(text: "$totalHourString ", style: AppTextStyles.bodyText), - const WidgetSpan( - child: Icon(Icons.info_outline_rounded, color: AppColor.primary10, size: 14), + TextSpan(text: "$totalHourString ", style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))), + WidgetSpan( + child: Icon(Icons.info_outline_rounded, color: AppColor.loadingColor(context), size: 14), ), ], ), diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 84eed483..4d552cf1 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -519,7 +519,7 @@ class _CreateTRAFRequestPageState extends State { attachment: attachments, buttonColor: AppColor.black10, onlyImages: false, - buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.neutral120), + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), onChange: (attachment) { attachments = attachment; }, @@ -531,6 +531,7 @@ class _CreateTRAFRequestPageState extends State { children: [ Checkbox( value: _acknowledgement, + side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), activeColor: AppColor.blueStatus(context), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/lib/modules/traf_module/traf_request_detail_page.dart b/lib/modules/traf_module/traf_request_detail_page.dart index 31fcf68e..00673a35 100644 --- a/lib/modules/traf_module/traf_request_detail_page.dart +++ b/lib/modules/traf_module/traf_request_detail_page.dart @@ -119,7 +119,7 @@ class _TrafRequestDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoHeader16Widget("Request Information"), - InfoTextWidget(label: 'Request No', value: data.reqCode ?? '-'), + InfoTextWidget(label: 'Request No', value: data.reqCode ?? '-', copyValue: true), InfoTextWidget(label: 'Request type', value: data.requestTypeName ?? '-'), if (data.requestTypeValue == 1) ...[ 4.height, diff --git a/lib/new_views/common_widgets/custom_app_bar.dart b/lib/new_views/common_widgets/custom_app_bar.dart index 9ab589d1..edfda750 100644 --- a/lib/new_views/common_widgets/custom_app_bar.dart +++ b/lib/new_views/common_widgets/custom_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -20,7 +22,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { titleSpacing: 16, title: Row( children: [ - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { Navigator.of(context).pop(); }), Text( diff --git a/lib/new_views/common_widgets/default_app_bar.dart b/lib/new_views/common_widgets/default_app_bar.dart index 58ac9631..1f68b56a 100644 --- a/lib/new_views/common_widgets/default_app_bar.dart +++ b/lib/new_views/common_widgets/default_app_bar.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -16,8 +18,11 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { final bool showBackButton; final VoidCallback? onWillPopScope; final TextStyle? titleStyle; + final bool showBackButtonAsCancel; - const DefaultAppBar({this.title, this.onBackPress, this.onWillPopScope, this.actions, this.titleStyle, this.showHomeActionButton = false, this.showBackButton = true, Key? key}) : super(key: key); + const DefaultAppBar( + {this.title, this.onBackPress, this.onWillPopScope, this.actions, this.titleStyle, this.showHomeActionButton = false, this.showBackButton = true, this.showBackButtonAsCancel = false, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { @@ -44,7 +49,7 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { title: Row( children: [ if (showBackButton) - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(showBackButtonAsCancel ? Icons.clear : (Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back)).onPress(() { if (onWillPopScope != null) { showDialog( context: context, @@ -63,11 +68,10 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { Navigator.of(context).pop(); } }), - 10.width, + 12.width, Text( title ?? "", - style: titleStyle ?? - (context.isTablet() ? AppTextStyles.heading4 : AppTextStyles.heading3).copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + style: titleStyle ?? (AppTextStyles.heading5).copyWith(fontWeight: FontWeight.w500, color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ).expanded, ], ), diff --git a/lib/new_views/pages/land_page/mark_attendance_widget.dart b/lib/new_views/pages/land_page/mark_attendance_widget.dart deleted file mode 100644 index 1ab12c67..00000000 --- a/lib/new_views/pages/land_page/mark_attendance_widget.dart +++ /dev/null @@ -1,866 +0,0 @@ -// import 'dart:async'; -// import 'dart:developer'; -// import 'dart:io'; -// -// import 'package:flutter/material.dart'; -// import 'package:flutter_svg/flutter_svg.dart'; -// import 'package:geolocator/geolocator.dart'; -// import 'package:huawei_location/huawei_location.dart'; -// import 'package:nfc_manager/nfc_manager.dart'; -// import 'package:permission_handler/permission_handler.dart'; -// import 'package:provider/provider.dart'; -// import 'package:test_sa/dashboard_latest/dashboard_provider.dart'; -// // import 'package:platform_device_id/platform_device_id.dart'; -// import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; -// import 'package:test_sa/extensions/enum_extensions.dart'; -// import 'package:test_sa/extensions/text_extensions.dart'; -// import 'package:test_sa/extensions/widget_extensions.dart'; -// import 'package:test_sa/helper/utils.dart'; -// import 'package:test_sa/models/enums/swipe_type.dart'; -// import 'package:test_sa/models/lookup.dart'; -// import 'package:test_sa/models/new_models/swipe_model.dart'; -// import 'package:test_sa/nfc/nfc_reader_sheet.dart'; -// import 'package:test_sa/utilities/Location.dart' as location; -// import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart'; -// import 'package:test_sa/views/widgets/dialogs/success_dialog.dart'; -// import 'package:test_sa/views/widgets/qr_scanner_dialog.dart'; -// import 'package:wifi_iot/wifi_iot.dart'; -// -// import '../../app_style/app_color.dart'; -// -// class MarkAttendanceWidget extends StatefulWidget { -// // DashboardProviderModel model; -// double topPadding; -// bool isFromDashboard; -// -// MarkAttendanceWidget( {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// // todo MarkAttendanceWidget(this.model, {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// @override -// _MarkAttendanceWidgetState createState() { -// return _MarkAttendanceWidgetState(); -// } -// } -// -// class _MarkAttendanceWidgetState extends State { -// bool isNfcEnabled = false, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false; -// -// int _locationUpdateCbId = 0; -// -// @override -// void initState() { -// super.initState(); -// checkAttendanceAvailability(); -// } -// -// void checkAttendanceAvailability() async { -// bool isAvailable = await NfcManager.instance.isAvailable(); -// // setState(() { -// // AppState().privilegeListModel!.forEach((PrivilegeListModel element) { -// // if (element.serviceName == "enableNFC") { -// // if (isAvailable) if (element.previlege ?? false) isNfcEnabled = true; -// // } else if (element.serviceName == "enableQR") { -// // if (element.previlege ?? false) isQrEnabled = true; -// // } else if (element.serviceName == "enableWIFI") { -// // if (element.previlege ?? false) isWifiEnabled = true; -// // } else if (element.serviceName!.trim() == "enableLocationNFC") { -// // if (element.previlege ?? false) isNfcLocationEnabled = true; -// // } else if (element.serviceName == "enableLocationQR") { -// // if (element.previlege ?? false) isQrLocationEnabled = true; -// // } else if (element.serviceName == "enableLocationWIFI") { -// // if (element.previlege ?? false) isWifiLocationEnabled = true; -// // } -// // }); -// // }); -// } -// -// void checkHuaweiLocationPermission(String attendanceType) async { -// // Permission_Handler permissionHandler = PermissionHandler(); -// location.Location.isEnabled((bool isEnabled) async { -// if (isEnabled) { -// location.Location.havePermission((bool permission) async { -// if (permission) { -// getHuaweiCurrentLocation(attendanceType); -// } else { -// bool has = await requestPermissions(); -// if (has) { -// getHuaweiCurrentLocation(attendanceType); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// -// // if (await permissionHandler.hasLocationPermission()) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // bool has = await requestPermissions(); -// // if (has) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } -// // } -// } -// -// Future requestPermissions() async { -// var result = await [ -// Permission.location, -// ].request(); -// return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); -// } -// -// @override -// void dispose() { -// super.dispose(); -// // Stop Session -// NfcManager.instance.stopSession(); -// } -// -// @override -// Widget build(BuildContext context) { -// return Container( -// padding: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: widget.topPadding), -// decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), -// width: double.infinity, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisSize: MainAxisSize.min, -// children: [ -// // LocaleKeys.markAttendance.tr().toSectionHeading(), -// // LocaleKeys.selectMethodOfAttendance.tr().toText11(color: const Color(0xff535353)), -// GridView( -// physics: const NeverScrollableScrollPhysics(), -// shrinkWrap: true, -// padding: const EdgeInsets.only(bottom: 0, top: 21), -// gridDelegate: -// SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 3 : 5, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), -// children: [ -// attendanceMethod(SwipeTypeEnum.NFC.name, Icons.nfc, true, () { -// log('i am here...nfc'); -// handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: true); -// return; -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("NFC"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("NFC", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// //if (isWifiEnabled) //todo -// attendanceMethod("Wifi", Icons.wifi, isWifiEnabled, () { -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("WIFI"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("WIFI", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performWifiAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// // if (isQrEnabled) //todo -// attendanceMethod(SwipeTypeEnum.QR.name, Icons.qr_code_2, true, () async { -// handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: true); -// return; -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("QR"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance("QR", position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performQrCodeAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// }), -// ], -// ) -// ], -// ), -// ); -// } -// -// void handleSwipe({required SwipeTypeEnum swipeType,required bool isEnable,}){ -// log('handle swipe value is ${swipeType.name}'); -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission("NFC"); -// } else { -// location.Location.isEnabled((bool isEnabled) { -// if (isEnabled) { -// location.Location.havePermission((bool permission) { -// if (permission) { -// Utils.showLoading(context); -// location.Location.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// Utils.hideLoading(context); -// markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? ""); -// } else { -// Utils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// handleSwipeOperation(swipeType: swipeType,lat: position.latitude,lang: position.longitude); -// } -// }, -// () { -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// } -// -// void handleSwipeOperation({required SwipeTypeEnum swipeType,double ?lat, double ?lang}){ -// switch(swipeType){ -// case SwipeTypeEnum.NFC: -// handleNfcAttendance(latitude: lat,longitude: lang); -// return; -// case SwipeTypeEnum.QR: -// performQrCodeAttendance(latitude: lat,longitude: lang); -// return; -// case SwipeTypeEnum.Wifi: -// return; -// -// } -// } -// -// void getHuaweiCurrentLocation(String attendanceType) async { -// try { -// Utils.showLoading(context); -// FusedLocationProviderClient locationService = FusedLocationProviderClient()..initFusedLocationService(); -// LocationRequest locationRequest = LocationRequest(); -// locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; -// locationRequest.interval = 500; -// List locationRequestList = [locationRequest]; -// LocationSettingsRequest locationSettingsRequest = LocationSettingsRequest(requests: locationRequestList); -// -// late StreamSubscription _streamSubscription; -// int requestCode = (await (locationService.requestLocationUpdates(locationRequest)))!; -// -// _streamSubscription = locationService.onLocationData!.listen( -// (Location location) async { -// Utils.hideLoading(context); -// await locationService.removeLocationUpdates(requestCode); -// if (attendanceType == "QR") { -// // todo performQrCodeAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// if (attendanceType == "WIFI") { -// // todo performWifiAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// if (attendanceType == "NFC") { -// //todo performNfcAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? ""); -// } -// requestCode = 0; -// }, -// ); -// -// // locationService.checkLocationSettings(locationSettingsRequest).then((settings) async { -// // await locationService.getLastLocation().then((value) { -// // if (value.latitude == null || value.longitude == null) { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "Unable to get your location, Please check your location settings & try again.", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // if (attendanceType == "QR") { -// // performQrCodeAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "WIFI") { -// // performWifiAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "NFC") { -// // performNfcAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // } -// // }).catchError((error) { -// // log("HUAWEI LOCATION getLastLocation ERROR!!!!!"); -// // log(error); -// // }); -// // }).catchError((error) { -// // log("HUAWEI LOCATION checkLocationSettings ERROR!!!!!"); -// // log(error); -// // if (error.code == "LOCATION_SETTINGS_NOT_AVAILABLE") { -// // // Location service not enabled. -// // } -// // }); -// } catch (error) { -// log("HUAWEI LOCATION ERROR!!!!!"); -// log('$error'); -// Utils.hideLoading(context); -// // Utils.handleException(error, context, null); -// } -// } -// -// Future handleNfcAttendance({double? latitude = 0, double? longitude = 0}) async { -// final dashBoardProvider = Provider.of(context, listen: false); -// -// if (Platform.isIOS) { -// Utils.readNFc(onRead: (String nfcId) async { -// await _processNfcAttendance(dashBoardProvider, nfcId, latitude, longitude); -// }); -// } else { -// showNfcReader(context, onNcfScan: (String? nfcId) async { -// await _processNfcAttendance(dashBoardProvider, nfcId ?? '', latitude, longitude); -// }); -// } -// } -// -// -// Future _processNfcAttendance( -// DashBoardProvider dashBoardProvider, -// String nfcId, -// double? latitude, -// double? longitude, -// ) async { -// Utils.showLoading(context); -// try { -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), -// value: nfcId, -// latitude: latitude, -// longitude: longitude, -// ); -// -// final swipeResponse = await dashBoardProvider.makeSwipe(model: swipeModel); -// log('swipe response i got is ${swipeResponse.toJson()}'); -// -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// _showErrorDialog(swipeResponse.message ?? "Unexpected error occurred"); -// } else { -// final isSuccess = swipeResponse.data; -// log('nfc swipe response is ${isSuccess}'); -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// _showSuccessDialog(); -// } -// } catch (error) { -// Utils.hideLoading(context); -// // Uncomment below line for error handling if needed -// // Utils.handleException(error, context, null); -// } -// } -// -// -// void _showErrorDialog(String message) { -// showDialog( -// context: context, -// builder: (context) => ConfirmDialog( -// message: message, -// onTap: () => Navigator.pop(context), -// ), -// ); -// } -// -// void _showSuccessDialog() { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// -// -// //older code.... -// Future performNfcAttendance({double? lat = 0, double ?lng = 0}) async { -// DashBoardProvider dashBoardProvider = Provider.of(context,listen:false); -// if (Platform.isIOS) { -// Utils.readNFc(onRead: (String nfcId) async { -// Utils.showLoading(context); -// try { -// SwipeModel? swipeResponse = await dashBoardProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '', latitude: lat, longitude: lng)); -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "Unexpected error occurred", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } else { -// bool status = swipeResponse.data; -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// } -// }); -// } else { -// showNfcReader(context, onNcfScan: (String? nfcId) async { -// Utils.showLoading(context); -// try { -// SwipeModel? swipeResponse = await dashBoardProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: nfcId??'', latitude: lat, longitude: lng)); -// log('api response i got is ${swipeResponse.toJson()}'); -// if (swipeResponse.responseCode != 1) { -// Utils.hideLoading(context); -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "Unexpected error occurred", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } else { -// bool status = swipeResponse.data; //use this status to get transactions. -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// Utils.hideLoading(context); -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// } -// // Utils.showLoading(context); -// // try { -// // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); -// // if (g?.messageStatus != 1) { -// // Utils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: g?.errorEndUserMessage ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = await model.fetchAttendanceTracking(context); -// // Utils.hideLoading(context); -// // showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: false, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // log(ex); -// // Utils.hideLoading(context); -// // // Utils.handleException(ex, context, (String msg) { -// // // Utils.confirmDialog(context, msg); -// // // }); -// // } -// }); -// } -// } -// -// -// -// void showMDialog(context, {Widget? child, Color? backgroundColor, bool isDismissable = true, bool isBusniessCard = false}) async { -// return showDialog( -// context: context, -// barrierDismissible: isDismissable, -// builder: (context) { -// return Dialog( -// shape: isBusniessCard -// ? const RoundedRectangleBorder( -// borderRadius: BorderRadius.all( -// Radius.circular(15.0), -// ), -// ) -// : null, -// backgroundColor: backgroundColor, -// child: child, -// ); -// }, -// ); -// } -// // -// // Future checkSession() async { -// // try { -// // Utils.showLoading(context); -// // await DashboardApiClient().getOpenMissingSwipes(); -// // Utils.hideLoading(context); -// // return true; -// // } catch (ex) { -// // Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// // return false; -// // } -// // } -// -// -// //TODO need to confirm .... -// // Future performWifiAttendance({double? latitude, double? lng}) async { -// // // if (Platform.isAndroid) { -// // // if (!(await checkSession())) { -// // // return; -// // // } -// // // } -// // Utils.showLoading(context); -// // bool isConnected = await WiFiForIoTPlugin.connect(AppState().getMohemmWifiSSID ?? "", -// // password: AppState().getMohemmWifiPassword ?? "", joinOnce: Platform.isIOS ? false : true, security: NetworkSecurity.WPA, withInternet: false); -// // -// // if (Platform.isIOS) { -// // if (await WiFiForIoTPlugin.getSSID() == AppState().getMohemmWifiSSID) { -// // isConnected = true; -// // } else { -// // isConnected = false; -// // } -// // } -// // -// // if (isConnected && AppState().isAuthenticated) { -// // await WiFiForIoTPlugin.forceWifiUsage(true); -// // await Future.delayed(const Duration(seconds: 6)); -// // try { -// // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 3, nfcValue: "", isGpsRequired: isWifiLocationEnabled, lat: lat, long: lng); -// // bool status = await model.fetchAttendanceTracking(context); -// // Utils.hideLoading(context); -// // await closeWifiRequest(); -// // if (g?.messageStatus == 2) { -// // showDialog( -// // barrierDismissible: true, -// // context: context, -// // builder: (cxt) => ConfirmDialog( -// // message: g?.errorEndUserMessage ?? "", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // onCloseTap: () {}, -// // ), -// // ); -// // } else { -// // showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: false, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // await closeWifiRequest(); -// // Utils.hideLoading(context); -// // Utils.handleException(ex, context, null); -// // } -// // } else { -// // if (AppState().isAuthenticated) { -// // Utils.hideLoading(context); -// // Utils.confirmDialog(context, "LocaleKeys.comeNearHMGWifi.tr()"); -// // } else { -// // await closeWifiRequest(); -// // } -// // } -// // } -// -// Future closeWifiRequest() async { -// if (Platform.isAndroid) { -// await WiFiForIoTPlugin.forceWifiUsage(false); -// } -// return await WiFiForIoTPlugin.disconnect(); -// } -// -// Future performQrCodeAttendance( {double ? latitude , double ?longitude}) async { -// DashBoardProvider dashBoardProvider = Provider.of(context,listen: false); -// var qrCodeValue = await Navigator.of(context).push( -// MaterialPageRoute( -// builder: (BuildContext context) => QrScannerDialog(), -// ), -// ); -// if (qrCodeValue != null) { -// Utils.showLoading(context); -// try { -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), -// value: qrCodeValue, -// latitude: latitude, -// longitude: longitude, -// ); -// log('model i got to scan qr is ${swipeModel.toJson()}'); -// final swipeResponse = await dashBoardProvider.makeSwipe(model: swipeModel); -// log('response of swipe is ${swipeResponse.toJson()}'); -// -// bool status = await swipeResponse.data; -// Utils.hideLoading(context); -// if (swipeResponse.responseCode == 2) { -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {}, -// ), -// ); -// } else { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: true, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// log('$ex'); -// Utils.hideLoading(context); -// //this need to confirm where it comes.. -// // Utils.handleException(ex, context, null); -// } -// } -// } -// -// void markFakeAttendance(dynamic sourceName, String lat, String long) async { -// Utils.showLoading(context); -// try { -// // await DashboardApiClient().markFakeLocation(sourceName: sourceName, lat: lat, long: long); -// Utils.hideLoading(context); -// Utils.confirmDialog(context, "LocaleKeys.fakeLocation.tr()"); -// } catch (ex) { -// log('$ex'); -// Utils.hideLoading(context); -// //Utils.handleException(ex, context, null); -// } -// } -// -// Widget attendanceMethod(String title, IconData iconData, bool isEnabled, VoidCallback onPress) => Container( -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(15), -// color: isEnabled ? null : Colors.grey.withOpacity(.5), -// gradient: isEnabled -// ? const LinearGradient( -// transform: GradientRotation(.64), -// begin: Alignment.topRight, -// end: Alignment.bottomLeft, -// colors: [ -// //ToDo set Colors according to design provided by designer... -// Colors.blue, -// Colors.green, -// // AppColor.gradiantEndColor, -// // MyColors.gradiantStartColor, -// ], -// ) -// : null, -// ), -// clipBehavior: Clip.antiAlias, -// padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// // SvgPicture.asset(image, color: Colors.white, alignment: Alignment.topLeft).expanded, -// Icon(iconData, color: isEnabled ? AppColor.black35 : Colors.grey), -// title.heading6(context), -// // title.toText17(isBold: true, color: Colors.white), -// ], -// ), -// ).onPress( -// () { -// log('isEnabled is ${!isEnabled}'); -// if (!isEnabled) return; -// onPress(); -// }, -// ); -// } diff --git a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart index a90c8188..0c4c61ab 100644 --- a/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart +++ b/lib/new_views/pages/land_page/my_request/all_requests_filter_page.dart @@ -169,7 +169,7 @@ class _AllRequestsFilterPageState extends State { } return Scaffold( appBar: DefaultAppBar( - title: context.translation.filter, + title: context.translation.filter,showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( diff --git a/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart b/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart index 1dd6da60..b49834b4 100644 --- a/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart +++ b/lib/new_views/pages/land_page/my_request/all_requests_search_page.dart @@ -88,6 +88,7 @@ class _AllRequestsSearchPageState extends State { // ).paddingOnly(bottom: 75), appBar: DefaultAppBar( title: context.translation.search, + showBackButtonAsCancel: true, actions: [ if (_showResetButton()) TextButton( diff --git a/lib/new_views/pages/land_page/my_request/my_requests_page.dart b/lib/new_views/pages/land_page/my_request/my_requests_page.dart index d1540eba..227d46f1 100644 --- a/lib/new_views/pages/land_page/my_request/my_requests_page.dart +++ b/lib/new_views/pages/land_page/my_request/my_requests_page.dart @@ -194,16 +194,17 @@ class _MyRequestsPageState extends State with SingleTickerProvid ], ), ).onPress(() async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => AllRequestsSearchPage(data: _search))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => AllRequestsSearchPage(data: _search))); if (result != null) { _search = result; setState(() {}); } }).expanded, - 16.width, + 12.width, IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: () async { final provider = Provider.of(context, listen: false); @@ -223,9 +224,10 @@ class _MyRequestsPageState extends State with SingleTickerProvid IconButton( icon: "filter".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => AllRequestsFilterPage(data: _search))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => AllRequestsFilterPage(data: _search))); if (result != null) { _search = result; setState(() {}); @@ -233,7 +235,7 @@ class _MyRequestsPageState extends State with SingleTickerProvid }, ), ], - ).paddingOnly(start: 16, end: 16), + ).paddingOnly(start: 16, end: 8), ), body: Consumer(builder: (context, snapshot, _) { return Column( diff --git a/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart b/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart index a26c7965..1e0cc7f0 100644 --- a/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart +++ b/lib/new_views/pages/land_page/non_hmg_employee_swipe_view.dart @@ -4,6 +4,7 @@ import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/widgets/item_views/info_header_widget.dart'; class NonHmgEmployeeSwipeView extends StatefulWidget { NonHmgEmployeeSwipeView({Key? key}) : super(key: key); @@ -47,6 +48,7 @@ class _NonHmgEmployeeSwipeViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + InfoHeader16Widget( "Mark Attendance"), Text( "Mark Attendance", style: AppTextStyles.heading5.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), diff --git a/lib/new_views/pages/login_page.dart b/lib/new_views/pages/login_page.dart index 1c6a83bd..4f8f7d7b 100644 --- a/lib/new_views/pages/login_page.dart +++ b/lib/new_views/pages/login_page.dart @@ -180,7 +180,7 @@ class _LoginPageState extends State { children: [ Checkbox( value: rememberMe, - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -190,7 +190,7 @@ class _LoginPageState extends State { }); }), 8.width, - "Remember Me".bodyText(context).custom(color: context.isDark ? AppColor.primary50 : AppColor.neutral50).expanded, + "Remember Me".bodyText(context).custom(color: AppColor.textStyleColor(context)).expanded, ], ), 8.height, @@ -200,7 +200,7 @@ class _LoginPageState extends State { Checkbox( value: privacyPolicyChecked, side: BorderSide(color: AppColor.loadingColor(context), width: 1.0), - activeColor: AppColor.blueStatus(context), + activeColor: AppColor.primary10, visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onChanged: (value) { @@ -210,10 +210,10 @@ class _LoginPageState extends State { }), 8.width, RichText( - text: TextSpan(text: "I have read and agree to ", style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.primary50 : AppColor.neutral50), children: [ + text: TextSpan(text: "I have read and agree to ", style: AppTextStyles.bodyText.copyWith(color: AppColor.textStyleColor(context)), children: [ TextSpan( text: "Privacy Policy", - style: AppTextStyles.bodyText.copyWith(color: AppColor.blueStatus(context), decoration: TextDecoration.underline), + style: AppTextStyles.bodyText.copyWith(color: AppColor.primary10, decoration: TextDecoration.underline), recognizer: TapGestureRecognizer() ..onTap = () async { Uri uri = Uri.parse("https://cloudsolutions.com.sa/en/privacy-policy"); diff --git a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart index 60db0c03..e7c92bec 100644 --- a/lib/new_views/swipe_module/dialoge/confirm_dialog.dart +++ b/lib/new_views/swipe_module/dialoge/confirm_dialog.dart @@ -1,61 +1,61 @@ -import 'package:flutter/material.dart'; -import 'package:test_sa/extensions/int_extensions.dart'; -import 'package:test_sa/extensions/text_extensions.dart'; -import 'package:test_sa/extensions/widget_extensions.dart'; -import 'package:test_sa/new_views/app_style/app_color.dart'; -import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; - -class ConfirmDialog extends StatelessWidget { - final String? title; - final String? message; - final String? okTitle; - final VoidCallback? onTap; - final VoidCallback? onCloseTap; - - const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - backgroundColor: AppColor.background(context), - shape: const RoundedRectangleBorder(), - insetPadding: const EdgeInsets.only(left: 21, right: 21), - child: Padding( - padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - title ?? "Confirm", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), - ).paddingOnly(top: 16), - ), - IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.close), - color: AppColor.iconColor(context), - constraints: const BoxConstraints(), - onPressed: () => onCloseTap ?? Navigator.pop(context), - // onPressed: () => Navigator.pop(context), - ) - ], - ), - message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), - 28.height, - AppFilledButton( - label: okTitle ?? "OK", - onPressed: onTap ?? () => Navigator.pop(context), - textColor: Colors.white, - //color: Ap.green, - ), - ], - ), - ), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:test_sa/extensions/int_extensions.dart'; +// import 'package:test_sa/extensions/text_extensions.dart'; +// import 'package:test_sa/extensions/widget_extensions.dart'; +// import 'package:test_sa/new_views/app_style/app_color.dart'; +// import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +// +// class ConfirmDialog extends StatelessWidget { +// final String? title; +// final String? message; +// final String? okTitle; +// final VoidCallback? onTap; +// final VoidCallback? onCloseTap; +// +// const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); +// +// @override +// Widget build(BuildContext context) { +// return Dialog( +// backgroundColor: AppColor.background(context), +// shape: const RoundedRectangleBorder(), +// insetPadding: const EdgeInsets.only(left: 21, right: 21), +// child: Padding( +// padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisSize: MainAxisSize.min, +// children: [ +// Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Expanded( +// child: Text( +// title ?? "Confirm", +// style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), +// ).paddingOnly(top: 16), +// ), +// IconButton( +// padding: EdgeInsets.zero, +// icon: const Icon(Icons.close), +// color: AppColor.iconColor(context), +// constraints: const BoxConstraints(), +// onPressed: () => onCloseTap ?? Navigator.pop(context), +// // onPressed: () => Navigator.pop(context), +// ) +// ], +// ), +// message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), +// 28.height, +// AppFilledButton( +// label: okTitle ?? "OK", +// onPressed: onTap ?? () => Navigator.pop(context), +// textColor: Colors.white, +// //color: Ap.green, +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/new_views/swipe_module/mark_attendance_widget.dart b/lib/new_views/swipe_module/mark_attendance_widget.dart deleted file mode 100644 index 7ebe6b6a..00000000 --- a/lib/new_views/swipe_module/mark_attendance_widget.dart +++ /dev/null @@ -1,821 +0,0 @@ -// import 'dart:async'; -// import 'dart:developer'; -// import 'dart:io'; -// -// import 'package:flutter/material.dart'; -// import 'package:geolocator/geolocator.dart'; -// import 'package:huawei_location/huawei_location.dart'; -// import 'package:nfc_manager/nfc_manager.dart'; -// import 'package:permission_handler/permission_handler.dart'; -// import 'package:provider/provider.dart'; -// import 'package:test_sa/controllers/providers/api/user_provider.dart'; -// import 'package:test_sa/extensions/int_extensions.dart'; -// import 'package:test_sa/extensions/text_extensions.dart'; -// import 'package:test_sa/extensions/widget_extensions.dart'; -// import 'package:test_sa/new_views/app_style/app_color.dart'; -// import 'package:test_sa/new_views/pages/land_page/nfc/nfc_reader_sheet.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/qr_scanner_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/dialoge/success_dialog.dart'; -// import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; -// import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; -// import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; -// import 'package:test_sa/new_views/swipe_module/utils/location_utils.dart'; -// import 'package:test_sa/new_views/swipe_module/utils/swipe_general_utils.dart'; -// import 'package:wifi_iot/wifi_iot.dart'; -// todo @sikander delete this file after compelte -// class MarkAttendanceWidget extends StatefulWidget { -// double topPadding; -// bool isFromDashboard; -// -// MarkAttendanceWidget({Key key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// // todo MarkAttendanceWidget(this.model, {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key); -// -// @override -// _MarkAttendanceWidgetState createState() { -// return _MarkAttendanceWidgetState(); -// } -// } -// -// class _MarkAttendanceWidgetState extends State { -// bool isNfcEnabled = false, isQrEnabled = false, isWifiEnabled = false; -// UserProvider _userProvider; -// -// int _locationUpdateCbId = 0; -// -// @override -// void initState() { -// super.initState(); -// checkAttendanceAvailability(); -// } -// -// void checkAttendanceAvailability() async { -// _userProvider = Provider.of(context, listen: false); -// bool isAvailable = await NfcManager.instance.isAvailable(); -// log('backend enabled values are nfc ${_userProvider.user.enableNFC} qr ${_userProvider.user.enableQR} wifi ${_userProvider.user.enableWifi}'); -// setState(() { -// if (isAvailable && _userProvider.user.enableNFC) isNfcEnabled = true; -// if (_userProvider.user.enableQR) isQrEnabled = true; -// if (_userProvider.user.enableWifi) isWifiEnabled = true; -// }); -// } -// -// void checkHuaweiLocationPermission(SwipeTypeEnum attendanceType, BuildContext context) async { -// // Permission_Handler permissionHandler = PermissionHandler(); -// LocationUtilities.isEnabled((bool isEnabled) async { -// if (isEnabled) { -// LocationUtilities.havePermission((bool permission) async { -// if (permission) { -// getHuaweiCurrentLocation(attendanceType, context); -// } else { -// bool has = await requestPermissions(); -// if (has) { -// getHuaweiCurrentLocation(attendanceType, context); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () { -// Navigator.pop(context); -// }, -// ), -// ); -// } -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// -// // if (await permissionHandler.hasLocationPermission()) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // bool has = await requestPermissions(); -// // if (has) { -// // getHuaweiCurrentLocation(attendanceType); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } -// // } -// } -// -// Future requestPermissions() async { -// var result = await [ -// Permission.location, -// ].request(); -// return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); -// } -// -// @override -// void dispose() { -// super.dispose(); -// // Stop Session -// NfcManager.instance.stopSession(); -// } -// -// @override -// Widget build(BuildContext context) { -// return Container( -// padding: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: widget.topPadding), -// decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), -// width: double.infinity, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisSize: MainAxisSize.min, -// children: [ -// 20.height, -// 'click me '.heading5(context).custom(color: AppColor.textStyleColor(context)), -// // LocaleKeys.markAttendance.tr().toSectionHeading(), -// // LocaleKeys.selectMethodOfAttendance.tr().toText11(color: const Color(0xff535353)), -// GridView( -// physics: const NeverScrollableScrollPhysics(), -// shrinkWrap: true, -// padding: const EdgeInsets.only(bottom: 0, top: 21), -// gridDelegate: -// SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 3 : 5, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), -// children: availableAttendanceMethodList(context: context), -// ) -// ], -// ), -// ); -// } -// -// void handleSwipe({ -// SwipeTypeEnum swipeType, -// @required bool isEnable, -// @required BuildContext context, -// }) { -// // if (AppState().getIsHuawei) { -// if (false) { -// checkHuaweiLocationPermission(swipeType, context); -// } else { -// LocationUtilities.isEnabled((bool isEnabled) { -// if (isEnabled) { -// LocationUtilities.havePermission((bool permission) { -// if (permission) { -// SwipeGeneralUtils.showLoading(context); -// LocationUtilities.getCurrentLocation( -// (Position position, bool isMocked) { -// if (isMocked) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? "", context); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// handleSwipeOperation(swipeType: swipeType, lat: position.latitude, lang: position.longitude); -// } -// }, -// () { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// }, -// context, -// ); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to give location permission to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openAppSettings(); -// }, -// ), -// ); -// } -// }); -// } else { -// showDialog( -// context: context, -// builder: (BuildContext cxt) => ConfirmDialog( -// message: "You need to enable location services to mark attendance", -// onTap: () async { -// Navigator.pop(context); -// await Geolocator.openLocationSettings(); -// }, -// ), -// ); -// } -// }); -// } -// } -// -// void handleSwipeOperation({@required SwipeTypeEnum swipeType, double lat, double lang, BuildContext context}) { -// switch (swipeType) { -// case SwipeTypeEnum.NFC: -// handleNfcAttendance(latitude: lat, longitude: lang, context: context); -// return; -// case SwipeTypeEnum.QR: -// performQrCodeAttendance(latitude: lat, longitude: lang, context: context); -// return; -// case SwipeTypeEnum.Wifi: -// //TODO need to implement. -// return; -// } -// } -// -// void getHuaweiCurrentLocation(SwipeTypeEnum attendanceType, BuildContext context) async { -// try { -// SwipeGeneralUtils.showLoading(context); -// FusedLocationProviderClient locationService = FusedLocationProviderClient()..initFusedLocationService(); -// LocationRequest locationRequest = LocationRequest(); -// locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; -// locationRequest.interval = 500; -// List locationRequestList = [locationRequest]; -// LocationSettingsRequest locationSettingsRequest = LocationSettingsRequest(requests: locationRequestList); -// -// StreamSubscription _streamSubscription; -// int requestCode = (await (locationService.requestLocationUpdates(locationRequest))); -// -// _streamSubscription = locationService.onLocationData.listen( -// (Location location) async { -// SwipeGeneralUtils.hideLoading(context); -// await locationService.removeLocationUpdates(requestCode); -// handleSwipeOperation(swipeType: attendanceType); -// requestCode = 0; -// }, -// ); -// -// // locationService.checkLocationSettings(locationSettingsRequest).then((settings) async { -// // await locationService.getLastLocation().then((value) { -// // if (value.latitude == null || value.longitude == null) { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "Unable to get your location, Please check your location settings & try again.", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // if (attendanceType == "QR") { -// // performQrCodeAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "WIFI") { -// // performWifiAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // if (attendanceType == "NFC") { -// // performNfcAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? ""); -// // } -// // } -// // }).catchError((error) { -// // log("HUAWEI LOCATION getLastLocation ERROR!!!!!"); -// // log(error); -// // }); -// // }).catchError((error) { -// // log("HUAWEI LOCATION checkLocationSettings ERROR!!!!!"); -// // log(error); -// // if (error.code == "LOCATION_SETTINGS_NOT_AVAILABLE") { -// // // Location service not enabled. -// // } -// // }); -// } catch (error) { -// log("HUAWEI LOCATION ERROR!!!!!"); -// log('$error'); -// SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.handleException(error, context, null); -// } -// } -// -// Future handleNfcAttendance({double latitude = 0, double longitude = 0, BuildContext context}) async { -// final userProvider = Provider.of(context, listen: false); -// -// if (Platform.isIOS) { -// SwipeGeneralUtils.readNFc(onRead: (String nfcId) async { -// await _processNfcAttendance(userProvider, nfcId, latitude, longitude, context); -// }); -// } else { -// showNfcReader(context, onNcfScan: (String nfcId) async { -// await _processNfcAttendance(userProvider, nfcId ?? '', latitude, longitude, context); -// }); -// } -// } -// -// Future _processNfcAttendance( -// UserProvider userProvider, -// String nfcId, -// double latitude, -// double longitude, -// BuildContext context, -// ) async { -// SwipeGeneralUtils.showLoading(context); -// try { -// // final swipeModel = Swipe( -// // swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), -// // value: nfcId, -// // latitude: latitude, -// // longitude: longitude, -// // ); -// //Test model... -// final swipeModel = Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '123', latitude: 24.70865415364271, longitude: 46.66600861881879); -// final swipeResponse = await userProvider.makeSwipe(model: swipeModel); -// if (swipeResponse.isSuccess) { -// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// SwipeGeneralUtils.hideLoading(context); -// Navigator.pushNamed(context, SwipeSuccessView.routeName); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.showErrorDialog(message: swipeResponse.message ?? "Unexpected error occurred", context: context); -// } -// } catch (errSwipeGeneralUtilsor) { -// SwipeGeneralUtils.hideLoading(context); -// // Uncomment below line for error handling if needed -// // SwipeGeneralUtils.handleException(error, context, null); -// } -// } -// -// Future closeWifiRequest() async { -// if (Platform.isAndroid) { -// await WiFiForIoTPlugin.forceWifiUsage(false); -// } -// return await WiFiForIoTPlugin.disconnect(); -// } -// -// Future performQrCodeAttendance({double latitude, double longitude, BuildContext context}) async { -// UserProvider userProvider = Provider.of(context, listen: false); -// var qrCodeValue = await Navigator.of(context).push( -// MaterialPageRoute( -// builder: (BuildContext context) => QrScannerDialog(), -// ), -// ); -// if (qrCodeValue != null) { -// SwipeGeneralUtils.showLoading(context); -// try { -// //test model.. -// final swipeModel = Swipe( -// swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), -// value: '2323', -// latitude: 24.70865415364271, -// longitude: 46.66600861881879, -// ); -// final swipeResponse = await userProvider.makeSwipe(model: swipeModel); -// if (swipeResponse.isSuccess) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.showMDialog(context, backgroundColor: Colors.transparent, isDismissable: true, child: SuccessDialog(widget.isFromDashboard)); -// } else { -// SwipeGeneralUtils.hideLoading(context); -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: swipeResponse.message ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {} -// ), -// ); -// } -// } catch (ex) { -// SwipeGeneralUtils.hideLoading(context); -// } -// } -// } -// -// List availableAttendanceMethodList({@required BuildContext context}) { -// List availableMethods = []; -// if (isNfcEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.NFC.name, 'nfc_icon', isNfcEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: isNfcEnabled, context: context); -// })); -// } -// if (isQrEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.QR.name, 'wifi_icon', isQrEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: isQrEnabled, context: context); -// })); -// } -// if (isWifiEnabled) { -// availableMethods.add(attendanceMethod(SwipeTypeEnum.Wifi.name, 'wifi_icon', isWifiEnabled, () { -// handleSwipe(swipeType: SwipeTypeEnum.Wifi, isEnable: isWifiEnabled, context: context); -// })); -// } -// return availableMethods; -// } -// -// Widget attendanceMethod(String title, String icon, bool isEnabled, VoidCallback onPress) { -// return Container( -// padding: EdgeInsets.all(12), -// decoration: BoxDecoration( -// color: Colors.white, -// borderRadius: BorderRadius.circular(18), -// border: Border.all(color: AppColor.white40, width: 2), -// ), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// icon.toSvgAsset(), -// title.heading5(context).custom(color: AppColor.neutral50), -// // Text( -// // title, -// // style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), -// // ), -// ], -// ), -// ).onPress( -// () { -// log('isEnabled is ${!isEnabled}'); -// if (!isEnabled) return; -// onPress(); -// }, -// ); -// // return Container( -// // decoration: BoxDecoration( -// // borderRadius: BorderRadius.circular(15), -// // color: isEnabled ? null : Colors.grey.withOpacity(.5), -// // gradient: isEnabled -// // ? const LinearGradient( -// // transform: GradientRotation(.64), -// // begin: Alignment.topRight, -// // end: Alignment.bottomLeft, -// // colors: [ -// // //ToDo set Colors according to design provided by designer... -// // Colors.blue, -// // Colors.green, -// // // AppColor.gradiantEndColor, -// // // MyColors.gradiantStartColor, -// // ], -// // ) -// // : null, -// // ), -// // clipBehavior: Clip.antiAlias, -// // padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), -// // child: Column( -// // crossAxisAlignment: CrossAxisAlignment.start, -// // children: [ -// // // SvgPicture.asset(image, color: Colors.white, alignment: Alignment.topLeft).expanded, -// // Icon(iconData, color: isEnabled ? AppColor.black35 : Colors.grey), -// // title.heading6(context), -// // // title.toText17(isBold: true, color: Colors.white), -// // ], -// // ), -// // ).onPress( -// // () { -// // log('isEnabled is ${!isEnabled}'); -// // if (!isEnabled) return; -// // onPress(); -// // }, -// // ); -// } -// } -// -// // Widget customListItem({required String icon, required String heading, required String subHeading, required VoidCallback onTap}) { -// // return GestureDetector( -// // onTap: onTap, // Handles the tap -// // child: Card( -// // shape: RoundedRectangleBorder( -// // borderRadius: BorderRadius.circular(14), // Circular border radius -// // ), -// // color: Colors.white, -// // child: Row( -// // crossAxisAlignment: CrossAxisAlignment.start, // Align items at the top -// // children: [ -// // // Icon Section -// // icon -// // .toSvgAsset( -// // width: 32, -// // color: AppColor.neutral120, -// // height: 29, -// // ) -// // .paddingOnly(top: 8), -// // 14.width, -// // Expanded( -// // child: Column( -// // crossAxisAlignment: CrossAxisAlignment.start, -// // children: [ -// // Text( -// // heading, -// // style: AppTextStyles.heading6.copyWith(color: AppColor.neutral50), -// // ), -// // 7.height, -// // Text( -// // subHeading, -// // style: AppTextStyles.bodyText2.copyWith(color: AppColor.neutral120), -// // ), -// // ], -// // ).paddingOnly(end: 50), -// // ), -// // ], -// // ).paddingAll(12), -// // ), -// // ); -// // } -// -// //qr older code... -// -// //nfc older code widget -// -// // // if (AppState().getIsHuawei) { -// // if (false) { -// // checkHuaweiLocationPermission(SwipeTypeEnum.NFC); -// // } else { -// // LocationUtilities.isEnabled((bool isEnabled) { -// // if (isEnabled) { -// // LocationUtilities.havePermission((bool permission) { -// // if (permission) { -// // SwipeGeneralUtils.showLoading(context); -// // LocationUtilities.getCurrentLocation( -// // (Position position, bool isMocked) { -// // if (isMocked) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.markFakeAttendance("NFC", position.latitude.toString() ?? "", position.longitude.toString() ?? "",context); -// // } else { -// // SwipeGeneralUtils.hideLoading(context); -// // //todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// // } -// // }, -// // () { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// // }, -// // context, -// // ); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openAppSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to enable location services to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openLocationSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } -// -// //older code.... -// // Future performNfcAttendance({double lat = 0, double lng = 0}) async { -// // UserProvider userProvider = Provider.of(context,listen:false); -// // if (Platform.isIOS) { -// // SwipeGeneralUtils.readNFc(onRead: (String nfcId) async { -// // SwipeGeneralUtils.showLoading(context); -// // try { -// // SwipeModel swipeResponse = await userProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: '', latitude: lat, longitude: lng)); -// // if (swipeResponse.responseCode != 1) { -// // SwipeGeneralUtils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: swipeResponse.message ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = swipeResponse.data; -// // if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: true, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // // SwipeGeneralUtils.handleException(ex, context, null); -// // } -// // }); -// // } else { -// // showNfcReader(context, onNcfScan: (String nfcId) async { -// // SwipeGeneralUtils.showLoading(context); -// // try { -// // SwipeModel swipeResponse = await userProvider.makeSwipe(model: Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: nfcId??'', latitude: lat, longitude: lng)); -// // log('api response i got is ${swipeResponse.toJson()}'); -// // if (swipeResponse.responseCode != 1) { -// // SwipeGeneralUtils.hideLoading(context); -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: swipeResponse.message ?? "Unexpected error occurred", -// // onTap: () { -// // Navigator.pop(context); -// // }, -// // ), -// // ); -// // } else { -// // bool status = swipeResponse.data; //use this status to get transactions. -// // if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3)); -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.showMDialog( -// // context, -// // backgroundColor: Colors.transparent, -// // isDismissable: true, -// // child: SuccessDialog(widget.isFromDashboard), -// // ); -// // } -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // // SwipeGeneralUtils.handleException(ex, context, null); -// // } -// // // SwipeGeneralUtils.showLoading(context); -// // // try { -// // // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); -// // // if (g?.messageStatus != 1) { -// // // SwipeGeneralUtils.hideLoading(context); -// // // showDialog( -// // // context: context, -// // // builder: (BuildContext cxt) => ConfirmDialog( -// // // message: g?.errorEndUserMessage ?? "Unexpected error occurred", -// // // onTap: () { -// // // Navigator.pop(context); -// // // }, -// // // ), -// // // ); -// // // } else { -// // // bool status = await model.fetchAttendanceTracking(context); -// // // SwipeGeneralUtils.hideLoading(context); -// // // showMDialog( -// // // context, -// // // backgroundColor: Colors.transparent, -// // // isDismissable: false, -// // // child: SuccessDialog(widget.isFromDashboard), -// // // ); -// // // } -// // // } catch (ex) { -// // // log(ex); -// // // SwipeGeneralUtils.hideLoading(context); -// // // // SwipeGeneralUtils.handleException(ex, context, (String msg) { -// // // // SwipeGeneralUtils.confirmDialog(context, msg); -// // // // }); -// // // } -// // }); -// // } -// // } -// -// // -// // Future checkSession() async { -// // try { -// // SwipeGeneralUtils.showLoading(context); -// // await DashboardApiClient().getOpenMissingSwipes(); -// // SwipeGeneralUtils.hideLoading(context); -// // return true; -// // } catch (ex) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.handleException(ex, context, null); -// // return false; -// // } -// // } -// -// //TODO need to confirm .... -// Future performWifiAttendance({double? latitude, double? lng}) async { -// // if (Platform.isAndroid) { -// // if (!(await checkSession())) { -// // return; -// // } -// // } -// SwipeGeneralUtils.showLoading(context); -// bool isConnected = await WiFiForIoTPlugin.connect(AppState().getMohemmWifiSSID ?? "", -// password: AppState().getMohemmWifiPassword ?? "", joinOnce: Platform.isIOS ? false : true, security: NetworkSecurity.WPA, withInternet: false); -// -// if (Platform.isIOS) { -// if (await WiFiForIoTPlugin.getSSID() == AppState().getMohemmWifiSSID) { -// isConnected = true; -// } else { -// isConnected = false; -// } -// } -// -// if (isConnected && AppState().isAuthenticated) { -// await WiFiForIoTPlugin.forceWifiUsage(true); -// await Future.delayed(const Duration(seconds: 6)); -// try { -// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 3, nfcValue: "", isGpsRequired: isWifiLocationEnabled, lat: lat, long: lng); -// bool status = await model.fetchAttendanceTracking(context); -// SwipeGeneralUtils.hideLoading(context); -// await closeWifiRequest(); -// if (g?.messageStatus == 2) { -// showDialog( -// barrierDismissible: true, -// context: context, -// builder: (cxt) => ConfirmDialog( -// message: g?.errorEndUserMessage ?? "", -// onTap: () { -// Navigator.pop(context); -// }, -// onCloseTap: () {}, -// ), -// ); -// } else { -// showMDialog( -// context, -// backgroundColor: Colors.transparent, -// isDismissable: false, -// child: SuccessDialog(widget.isFromDashboard), -// ); -// } -// } catch (ex) { -// await closeWifiRequest(); -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.handleException(ex, context, null); -// } -// } else { -// if (AppState().isAuthenticated) { -// SwipeGeneralUtils.hideLoading(context); -// SwipeGeneralUtils.confirmDialog(context, "LocaleKeys.comeNearHMGWifi.tr()"); -// } else { -// await closeWifiRequest(); -// } -// } -// } -// -// // older grid widget.. -// // attendanceMethod(SwipeTypeEnum.NFC.name, Icons.nfc, isNfcEnabled, () { -// // handleSwipe(swipeType: SwipeTypeEnum.NFC, isEnable: isNfcEnabled); -// // }), -// // attendanceMethod(SwipeTypeEnum.QR.name, Icons.qr_code_2, isQrEnabled, () async { -// // handleSwipe(swipeType: SwipeTypeEnum.QR, isEnable: true); -// // }), -// // //if (isWifiEnabled) //todo -// // attendanceMethod(SwipeTypeEnum.Wifi.name, Icons.wifi, isWifiEnabled, () { -// // // if (AppState().getIsHuawei) { -// // if (false) { -// // checkHuaweiLocationPermission(SwipeTypeEnum.Wifi); -// // } else { -// // LocationUtilities.isEnabled((bool isEnabled) { -// // if (isEnabled) { -// // LocationUtilities.havePermission((bool permission) { -// // if (permission) { -// // SwipeGeneralUtils.showLoading(context); -// // LocationUtilities.getCurrentLocation( -// // (Position position, bool isMocked) { -// // if (isMocked) { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.markFakeAttendance("WIFI", position.latitude.toString() ?? "", position.longitude.toString() ?? "", context); -// // } else { -// // SwipeGeneralUtils.hideLoading(context); -// // //todo performWifiAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? ""); -// // } -// // }, -// // () { -// // SwipeGeneralUtils.hideLoading(context); -// // SwipeGeneralUtils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); -// // }, -// // context, -// // ); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to give location permission to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openAppSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } else { -// // showDialog( -// // context: context, -// // builder: (BuildContext cxt) => ConfirmDialog( -// // message: "You need to enable location services to mark attendance", -// // onTap: () async { -// // Navigator.pop(context); -// // await Geolocator.openLocationSettings(); -// // }, -// // ), -// // ); -// // } -// // }); -// // } -// // }), diff --git a/lib/new_views/swipe_module/swipe_history_view.dart b/lib/new_views/swipe_module/swipe_history_view.dart index c340f7bb..3a939898 100644 --- a/lib/new_views/swipe_module/swipe_history_view.dart +++ b/lib/new_views/swipe_module/swipe_history_view.dart @@ -47,87 +47,86 @@ class _SwipeHistoryViewState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: const DefaultAppBar( - title: 'Swipe History', - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 8.height, - Row( - children: [ - ADatePicker( - label: context.translation.from, - date: dateFrom, - from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), - formatDateWithTime: true, - onDatePicker: (selectedDate) { - if (selectedDate != null) { - showThemedTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ).then((selectedTime) { - // Handle the selected date and time here. - if (selectedTime != null) { - DateTime selectedDateTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - if (selectedDateTime != null) { - setState(() { - dateFrom = selectedDateTime; - }); + appBar: const DefaultAppBar(title: 'Swipe History'), + body: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8, + children: [ + ADatePicker( + label: context.translation.from, + date: dateFrom, + from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), + formatDateWithTime: true, + onDatePicker: (selectedDate) { + if (selectedDate != null) { + showThemedTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ).then((selectedTime) { + // Handle the selected date and time here. + if (selectedTime != null) { + DateTime selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + if (selectedDateTime != null) { + setState(() { + dateFrom = selectedDateTime; + }); + } } - } - }); - } - }, - ).expanded, - 8.width, - ADatePicker( - label: context.translation.to, - date: dateTo, - from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), - formatDateWithTime: true, - onDatePicker: (selectedDate) { - if (selectedDate != null) { - showThemedTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ).then((selectedTime) { - // Handle the selected date and time here. - if (selectedTime != null) { - DateTime selectedDateTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - if (selectedDateTime != null) { - setState(() { - dateTo = selectedDateTime; - }); + }); + } + }, + ).expanded, + ADatePicker( + label: context.translation.to, + date: dateTo, + from: DateTime(DateTime.now().year - 5, DateTime.now().month, DateTime.now().day), + formatDateWithTime: true, + onDatePicker: (selectedDate) { + if (selectedDate != null) { + showThemedTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ).then((selectedTime) { + // Handle the selected date and time here. + if (selectedTime != null) { + DateTime selectedDateTime = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + if (selectedDateTime != null) { + setState(() { + dateTo = selectedDateTime; + }); + } } - } - }); - } - }, - ).expanded, - ], - ), - 12.height, - AppFilledButton(label: context.translation.search, maxWidth: false, onPressed: getSwipeHistory), - 8.height, - const Divider().defaultStyle(context), - Consumer(builder: (context, snapshot, child) { - return SwipeHistoryList(snapshot.swipeHistory ?? [], snapshot.isLoading).expanded; - }), - ], - ).paddingAll(20), + }); + } + }, + ).expanded, + ], + ), + 12.height, + AppFilledButton(label: context.translation.search, maxWidth: false, onPressed: getSwipeHistory), + 8.height, + const Divider().defaultStyle(context), + Consumer(builder: (context, snapshot, child) { + return SwipeHistoryList(snapshot.swipeHistory ?? [], snapshot.isLoading).expanded; + }).expanded, + ], + ).paddingOnly(start: 16, end: 16, top: 16), + ), ); } } @@ -143,7 +142,7 @@ class SwipeHistoryList extends StatelessWidget { return (list.isEmpty && !isLoading) ? NoDataFound(message: context.translation.noDataFound).center : ListView.separated( - padding: EdgeInsets.only(top: 12.toScreenHeight), + padding: const EdgeInsets.only(bottom: 16, top: 16), itemBuilder: (cxt, index) { if (isLoading) return const SizedBox().toRequestShimmer(cxt, isLoading); return SwipeHistoryCard(list[index]); diff --git a/lib/new_views/swipe_module/utils/swipe_general_utils.dart b/lib/new_views/swipe_module/utils/swipe_general_utils.dart index a24834e0..9bace21f 100644 --- a/lib/new_views/swipe_module/utils/swipe_general_utils.dart +++ b/lib/new_views/swipe_module/utils/swipe_general_utils.dart @@ -29,6 +29,9 @@ import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; import 'package:test_sa/new_views/swipe_module/utils/location_utils.dart'; +import 'package:test_sa/views/widgets/dialogs/confirm_dialog.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/qr/scan_qr.dart'; import 'package:wifi_iot/wifi_iot.dart'; @@ -125,18 +128,8 @@ class SwipeGeneralUtils { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - icon.toSvgAsset( - color: isEnabled - ? context.isDark - ? AppColor.neutral30 - : AppColor.neutral50 - : Colors.grey.withOpacity(0.5)), - title.heading5(context).custom( - color: isEnabled - ? context.isDark - ? AppColor.neutral30 - : AppColor.neutral50 - : Colors.grey.withOpacity(0.5)), + icon.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), + title.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), ], ), ).onPress( @@ -826,9 +819,9 @@ class _SwipeTypeBottomSheetContentState extends State<_SwipeTypeBottomSheetConte mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - context.translation.markAttendance.heading5(context).custom(color: AppColor.headingTextColor(context)), + InfoHeader16Widget("Mark Attendance"), 8.height, - context.translation.selectMethodToMarkAttendance.bodyText2(context).custom(color: AppColor.lightTextColor(context)), + InfoTextLabelWidget(label: context.translation.selectMethodToMarkAttendance), 12.height, GridView.builder( padding: const EdgeInsets.all(0), @@ -910,4 +903,3 @@ class _SwipeTypeItem { required this.isEnabled, }); } - diff --git a/lib/views/full_screen_image_viewer.dart b/lib/views/full_screen_image_viewer.dart new file mode 100644 index 00000000..9c463e06 --- /dev/null +++ b/lib/views/full_screen_image_viewer.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; + +import '../new_views/app_style/app_color.dart'; + +class FullScreenImageViewer extends StatelessWidget { + final Widget child; + + const FullScreenImageViewer(this.child, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + InteractiveViewer(clipBehavior: Clip.none, panEnabled: true, minScale: 0.5, maxScale: 4.0, child: child).center, + SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: FloatingActionButton( + heroTag: "full-screen-image-viewer", + backgroundColor: AppColor.background(context), + onPressed: () => Navigator.maybePop(context), + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder( + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14.0), + ), + child: Icon(Icons.clear, size: 24, color: AppColor.icon2Color(context)), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/update_available_screen.dart b/lib/views/update_available_screen.dart index 558a0233..ff71f215 100644 --- a/lib/views/update_available_screen.dart +++ b/lib/views/update_available_screen.dart @@ -25,13 +25,13 @@ class UpdateAvailableScreen extends StatelessWidget { 48.height, Text( "Update Available", - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + style: AppTextStyles.heading4.copyWith(color: AppColor.headingTextColor(context)), ), 24.height, Text( "A new update is available on store.\nUpdate now to enjoy the latest features, improvements, and bug fixes for a smoother experience! ", textAlign: TextAlign.center, - style: AppTextStyles.heading6.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral20), + style: AppTextStyles.heading6.copyWith(color: AppColor.lightTextColor(context)), ), 24.height, AppFilledButton( diff --git a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart index b157df2e..5d01acce 100644 --- a/lib/views/widgets/auto_complete/wo_auto_complete_field.dart +++ b/lib/views/widgets/auto_complete/wo_auto_complete_field.dart @@ -59,11 +59,12 @@ class _WoAutoCompleteFieldState extends State { @override Widget build(BuildContext context) { - final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10)); + final border = UnderlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(16)); return Container( decoration: BoxDecoration( color: AppColor.background(context), - borderRadius: BorderRadius.circular(AppStyle.borderRadius * AppStyle.getScaleFactor(context)), + border: Border.all(color: AppColor.border2Color(context), width: 1), + borderRadius: BorderRadius.circular(16), // boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], ), child: Autocomplete( @@ -93,7 +94,7 @@ class _WoAutoCompleteFieldState extends State { return TextField( controller: _controller, focusNode: fieldFocusNode, - style: AppTextStyles.bodyText.copyWith(color: AppColor.black10), + style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 14, color: AppColor.textStyleColor(context)), textAlign: TextAlign.start, decoration: InputDecoration( border: border, @@ -101,16 +102,16 @@ class _WoAutoCompleteFieldState extends State { focusedBorder: border, enabledBorder: border, errorBorder: border, - contentPadding: EdgeInsets.symmetric(vertical: 8.toScreenHeight, horizontal: 16.toScreenWidth), + contentPadding: EdgeInsets.symmetric(vertical: 12.toScreenHeight, horizontal: 16.toScreenWidth), constraints: const BoxConstraints(), suffixIconConstraints: const BoxConstraints(maxHeight: 24, maxWidth: 24 + 8), filled: true, enabled: widget.enabled, - fillColor: AppColor.fieldBgColor(context), + fillColor: AppColor.dropDownBackground(context), errorStyle: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60), - floatingLabelStyle: AppTextStyle.body1.copyWith(fontWeight: FontWeight.w500, color: context.isDark ? null : AppColor.neutral20), + floatingLabelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.floatingTextStyleColor(context)), + labelStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.labelTextStyleColor(context)), labelText: "CM Work Order Number", - labelStyle: AppTextStyles.tinyFont.copyWith(color: AppColor.textColor(context)), suffixIcon: loading ? const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 3.0).paddingOnly(end: 8) : null, ), textInputAction: TextInputAction.search, diff --git a/lib/views/widgets/dialogs/confirm_dialog.dart b/lib/views/widgets/dialogs/confirm_dialog.dart index 6d8ef764..60db0c03 100644 --- a/lib/views/widgets/dialogs/confirm_dialog.dart +++ b/lib/views/widgets/dialogs/confirm_dialog.dart @@ -2,21 +2,22 @@ import 'package:flutter/material.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; class ConfirmDialog extends StatelessWidget { final String? title; - final String message; + final String? message; final String? okTitle; final VoidCallback? onTap; final VoidCallback? onCloseTap; - const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + const ConfirmDialog({Key? key, this.title, this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); @override Widget build(BuildContext context) { return Dialog( - backgroundColor: Colors.white, + backgroundColor: AppColor.background(context), shape: const RoundedRectangleBorder(), insetPadding: const EdgeInsets.only(left: 21, right: 21), child: Padding( @@ -31,21 +32,20 @@ class ConfirmDialog extends StatelessWidget { Expanded( child: Text( title ?? "Confirm", - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: Colors.black87, height: 35 / 24, letterSpacing: -0.96), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), ).paddingOnly(top: 16), ), IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.close), - color: Colors.black87, + color: AppColor.iconColor(context), constraints: const BoxConstraints(), onPressed: () => onCloseTap ?? Navigator.pop(context), // onPressed: () => Navigator.pop(context), ) ], ), - 16.height, - message.bodyText(context), + message != null ? message!.heading5(context).custom(color: AppColor.textColor(context)) : const SizedBox(), 28.height, AppFilledButton( label: okTitle ?? "OK", diff --git a/lib/views/widgets/dialogs/dialog.dart b/lib/views/widgets/dialogs/dialog.dart index 0cdfa87c..e7132eb1 100644 --- a/lib/views/widgets/dialogs/dialog.dart +++ b/lib/views/widgets/dialogs/dialog.dart @@ -37,6 +37,7 @@ class AAlertDialog extends StatelessWidget { ) : AlertDialog( backgroundColor: AppColor.background(context), + actionsPadding: const EdgeInsets.only(bottom: 12, left: 24, right: 24), title: title != null ? Text(title!) : null, content: content != null ? Text(content!) : null, // contentTextStyle: AppTextStyles.bodyText.copyWith(color: (context.isDark ? AppColor.neutral30 : AppColor.neutral50)), diff --git a/lib/views/widgets/dialogs/loading_with_text_dialog.dart b/lib/views/widgets/dialogs/loading_with_text_dialog.dart new file mode 100644 index 00000000..b07b3eb6 --- /dev/null +++ b/lib/views/widgets/dialogs/loading_with_text_dialog.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/views/app_style/sizing.dart'; + +class LoadingWithTextDialog extends StatelessWidget { + final String? message; + + const LoadingWithTextDialog({Key? key, this.message}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Material( + borderRadius: BorderRadius.circular(20.0), + child: Container( + height: 150.toScreenWidth, + width: 200.toScreenWidth, + alignment: Alignment.center, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColor.neutral30, + borderRadius: BorderRadius.circular(20.0), + boxShadow: [AppStyle.boxShadow], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(strokeWidth: 3), + 16.height, + Text( + message ?? "Uploading...", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index dd94a08d..0a51cc87 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -7,10 +7,17 @@ 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/helper/utils.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/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.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/equipment/asset_image_upload_page.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/failed_loading.dart'; @@ -32,6 +39,7 @@ class _AssetDetailPageState extends State with SingleTickerProv late AnimationController _animationController; int? assetId; AssetProvider? _assetProvider; + AssetByIdModel? assetModel; @override void initState() { @@ -48,6 +56,7 @@ class _AssetDetailPageState extends State with SingleTickerProv super.dispose(); } + List attachments = []; @override Widget build(BuildContext context) { @@ -57,7 +66,7 @@ class _AssetDetailPageState extends State with SingleTickerProv return Scaffold( appBar: DefaultAppBar(title: context.translation.assetDetails), body: FutureBuilder( - future: _assetProvider!.getAssetById(assetId!, context.translation), + future: assetModel == null ? _assetProvider!.getAssetById(assetId!, context.translation) : null, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return FailedLoading( @@ -71,9 +80,19 @@ class _AssetDetailPageState extends State with SingleTickerProv return const Center(child: ALoading()); } else if (snapshot.hasData) {} if (snapshot.hasData) { - AssetByIdModel assetModel = snapshot.data!; - // Start animation when data is loaded - _animationController.forward(from: 0.0); + assetModel = snapshot.data!; + attachments = assetModel?.assetAttachments + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? + []; + + if (!_animationController.isCompleted) { + _animationController.forward(from: 0.0); + } // Image animation - fade in with scale final imageAnimation = Tween(begin: 0.0, end: 1.0).animate( @@ -180,142 +199,193 @@ class _AssetDetailPageState extends State with SingleTickerProv ), ); - return SingleChildScrollView( - padding: const EdgeInsets.all(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(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), - )), - ), - ), - ), - ), - 6.height, - // Animated content column - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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.all(16), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status label with animation - if (assetModel.commissioningStatus != null) - FadeTransition( - opacity: statusAnimation, - child: SlideTransition( - position: statusSlideAnimation, - child: StatusLabel( - label: assetModel.commissioningStatus!.name, - textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + // 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(assetModel?.assetPhoto != null ? URLs.getFileUrl(assetModel!.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), + )), ), ), ), - if (assetModel.commissioningStatus != null) 8.height, - - // Header with animation - FadeTransition( - opacity: headerAnimation, - child: SlideTransition( - position: headerSlideAnimation, - child: InfoHeader16Widget(assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), - ), ), - 8.height, + 6.height, + // Animated content column + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status label with animation + if (assetModel?.commissioningStatus != null) + FadeTransition( + opacity: statusAnimation, + child: SlideTransition( + position: statusSlideAnimation, + child: StatusLabel( + label: assetModel!.commissioningStatus!.name, + textColor: AppColor.getRequestStatusTextColorByName(context, assetModel!.commissioningStatus!.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel!.commissioningStatus!.name!), + ), + ), + ), + if (assetModel?.commissioningStatus != null) 8.height, - // Details row with animation - FadeTransition( - opacity: detailsAnimation, - child: SlideTransition( - position: detailsSlideAnimation, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( + // Header with animation + FadeTransition( + opacity: headerAnimation, + child: SlideTransition( + position: headerSlideAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2, children: [ - InfoTextWidget(label: context.translation.assetNo, value: assetModel.multiAssets!.first.assetNumber, showEmptyValue: true), - InfoTextWidget(label: context.translation.modelName, value: assetModel.modelDefinition!.modelName, showEmptyValue: true), - InfoTextWidget(label: context.translation.supplier, value: assetModel.supplier?.suppliername, showEmptyValue: true), - InfoTextWidget(label: context.translation.manufacture, value: assetModel.modelDefinition!.manufacturerName, showEmptyValue: true), + InfoHeader16Widget(assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"), + if (context.userProvider.isEngineer && !(assetModel?.hasTrainingImageData ?? true)) + const InfoTextLabelWidget(label: '*Upload Images for this asset', color: AppColor.red30), ], - ).expanded, - 8.width, - Column( + ), + ), + ), + 8.height, + // Details row with animation + FadeTransition( + opacity: detailsAnimation, + child: SlideTransition( + position: detailsSlideAnimation, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, children: [ - InfoTextWidget(label: context.translation.snNo, value: assetModel.multiAssets!.first.assetSerialNo, showEmptyValue: true), - InfoTextWidget(label: context.translation.site, value: assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.building, value: assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.floor, value: assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.md, value: assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), - InfoTextWidget(label: context.translation.room, value: assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.assetNo, value: assetModel!.multiAssets!.first.assetNumber, showEmptyValue: true, copyValue: true), + InfoTextWidget(label: context.translation.modelName, value: assetModel!.modelDefinition!.modelName, showEmptyValue: true), + InfoTextWidget(label: context.translation.supplier, value: assetModel!.supplier?.suppliername, showEmptyValue: true), + InfoTextWidget(label: context.translation.manufacture, value: assetModel!.modelDefinition!.manufacturerName, showEmptyValue: true), + ], + ).expanded, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.snNo, value: assetModel!.multiAssets!.first.assetSerialNo, showEmptyValue: true), + InfoTextWidget(label: context.translation.site, value: assetModel!.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.building, value: assetModel!.building?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.floor, value: assetModel!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.md, value: assetModel!.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + InfoTextWidget(label: context.translation.room, value: assetModel!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach, showEmptyValue: true), + ], + ).expanded, ], - ).expanded, - ], + ), + ), + ), + // 8.height, + // Divider with animation + FadeTransition( + opacity: datesAnimation, + child: const Divider().defaultStyle(context), + ), + // 8.height, + // Dates section with animation + FadeTransition( + opacity: datesAnimation, + child: SlideTransition( + position: datesSlideAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget(label: context.translation.installationDate, value: assetModel?.installationDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.nextPmDate, value: assetModel?.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), + InfoTextWidget(label: context.translation.lastPmDate, value: assetModel?.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), + ], + ), + ), ), - ), - ), - 8.height, - // Divider with animation - FadeTransition( - opacity: datesAnimation, - child: const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - ), - 8.height, + // Description with animation (if exists) + if ((assetModel?.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ + // 8.height, + FadeTransition( + opacity: descAnimation, + child: const Divider().defaultStyle(context), + ), + FadeTransition( + opacity: descAnimation, + child: SlideTransition( + position: descSlideAnimation, + child: InfoTextWidget(label: assetModel!.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), + ), + ), + ], - // Dates section with animation - FadeTransition( - opacity: datesAnimation, - child: SlideTransition( - position: datesSlideAnimation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InfoTextWidget(label: context.translation.installationDate, value: assetModel.installationDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.nextPmDate, value: assetModel.nextPMDate?.toAssetDetailsFormat, showEmptyValue: true), - InfoTextWidget(label: context.translation.lastPmDate, value: assetModel.lastPMDate?.toAssetDetailsFormat, showEmptyValue: true), - ], - ), - ), + if (attachments.isNotEmpty) ...[ + // 8.height, + 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()), + ), + ), + ] + ], ), - - // Description with animation (if exists) - if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ - 8.height, - FadeTransition( - opacity: descAnimation, - child: const Divider().defaultStyle(context), - ), - FadeTransition( - opacity: descAnimation, - child: SlideTransition( - position: descSlideAnimation, - child: InfoTextWidget(label: assetModel.modelDefinition!.assetDescription!, showEmptyValue: true, isMsg: true), - ), - ), - ] ], - ) - ], - ).toShadowContainer(context), + ).toShadowContainer(context), + ).expanded, + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: _upload, + ), + ), + ], ); } return const Center(child: ALoading()); @@ -323,4 +393,12 @@ class _AssetDetailPageState extends State with SingleTickerProv ), ); } + + void _upload() async { + bool reload = await Navigator.push(context, MaterialPageRoute(builder: (context) => AssetImageUploadPage(assetId: assetId!))) ?? false; + if (reload) { + assetModel = null; + setState(() {}); + } + } } diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart new file mode 100644 index 00000000..33d48a62 --- /dev/null +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -0,0 +1,251 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/providers/api/devices_provider.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/helper/utils.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/cm_request_utils.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/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/full_screen_image_viewer.dart'; +import 'package:test_sa/views/widgets/dialogs/dialog.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.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'; + +class AssetImageUploadPage extends StatefulWidget { + int assetId; + TrainingImageData? attachments; + + AssetImageUploadPage({Key? key, required this.assetId, this.attachments}) : super(key: key); + + @override + _AssetImageUploadPageState createState() { + return _AssetImageUploadPageState(); + } +} + +class _AssetImageUploadPageState extends State { + late TrainingImageData trainingImageData; + + @override + void initState() { + super.initState(); + trainingImageData = widget.attachments ?? TrainingImageData(additionalImages: []); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const DefaultAppBar(title: "Upload Images"), + body: Column( + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + InfoHeader16Widget("Instructions"), + InfoTextLabelWidget( + label: + "* Please upload all images of the sides of the asset as mentioned below.\n* Images should be clear and high quality.\n* Avoid uploading blurry, low-light or low-resolution images."), + GridView( + padding: const EdgeInsets.only(top: 16, bottom: 16), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 4 : 4, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), + children: [ + gridCard( + icon: "asset_image_upload/side_right", + title: "Right Side", + attachment: trainingImageData.rightImage, + onUpdate: (value) { + trainingImageData.rightImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_left", + title: "Left Side", + attachment: trainingImageData.leftImage, + onUpdate: (value) { + trainingImageData.leftImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_top", + title: "Top Side", + attachment: trainingImageData.upImage, + onUpdate: (value) { + trainingImageData.upImage = value; + setState(() {}); + }, + ), + gridCard( + icon: "asset_image_upload/side_bottom", + title: "Bottom Side", + attachment: trainingImageData.downImage, + onUpdate: (value) { + trainingImageData.downImage = value; + setState(() {}); + }, + ), + ], + ), + AttachmentPicker( + label: "Additional Images", + pickerTitle: "Attach Images", + attachment: trainingImageData.additionalImages!, + buttonColor: AppColor.black10, + onlyImages: true, + pickMultiple: true, + // showAsGrid: true, + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), + onChange: (_attachments) { + trainingImageData.additionalImages = _attachments; + setState(() {}); + }, + ).toShadowContainer(context), + ], + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + disableButton: trainingImageData.leftImage == null || trainingImageData.rightImage == null || trainingImageData.upImage == null || trainingImageData.downImage == null, + label: "Upload", + onPressed: () async { + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: context.translation.confirm, + content: "I followed all the instructions and uploading the images in high quality, clear and not blurry.", + ), + ); + if (result) { + Utils.showLoadingWithTextDialog(context); + result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); + Utils.hideLoading(context); + if (result) { + Navigator.pop(context, result); + } + } + }, + ), + ), + ], + )); + } + + Widget gridCard({ + required String icon, + required String title, + required GenericAttachmentModel? attachment, + required Function(GenericAttachmentModel?) onUpdate, + }) { + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Positioned.fill( + child: AttachmentPicker( + label: "", + pickerTitle: title, + onlyImages: true, + checkQuality: true, + enabled: attachment == null, + attachment: attachment == null ? [] : [attachment], + buttonIcon: 'image-plus'.toSvgAsset(color: AppColor.primary10), + onChange: (_attachments) { + if (_attachments.isNotEmpty) { + onUpdate(_attachments.first); + } else { + onUpdate(null); + } + }, + child: imageAngleCard(icon, title, attachment), + ).toShadowContainer(context, padding: 0), + ), + if (attachment != null) + Positioned( + top: 4, + right: 4, + child: IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { + onUpdate(null); + }, + )), + ], + ); + } + + Widget imageAngleCard(String icon, String title, GenericAttachmentModel? attachment) { + if (attachment != null) { + Widget imageWidget = _getImageWidget(attachment.name!); + return ClipRRect(borderRadius: BorderRadius.circular(12), child: imageWidget.onPress(() => context.fullScreenImageViewer(imageWidget))); + } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + icon.toSvgAsset(height: 50, width: 50), + Text( + title, + style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), + ), + ], + ); + } + + Widget _getImageWidget(String imageUrl) { + // Check if it's base64 encoded (contains | separator) + if (imageUrl.contains('|')) { + try { + final base64Data = imageUrl.split('|').last; + return Image.memory( + base64Decode(base64Data), + fit: BoxFit.cover, + ); + } catch (e) { + return const Icon(Icons.broken_image, size: 48); + } + } + + // Check if it's a local file path + if (CMRequestUtils.isLocalUrl(imageUrl)) { + return Image.file( + File(imageUrl), + fit: BoxFit.cover, + ); + } + + // Otherwise treat as network URL + return Image.network( + imageUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.broken_image, size: 48); + }, + ); + } +} diff --git a/lib/views/widgets/equipment/asset_item_gridview.dart b/lib/views/widgets/equipment/asset_item_gridview.dart index 4947d46d..8c10be02 100644 --- a/lib/views/widgets/equipment/asset_item_gridview.dart +++ b/lib/views/widgets/equipment/asset_item_gridview.dart @@ -13,7 +13,7 @@ class AssetItemGridView extends StatelessWidget { final Asset device; final Function(Asset) onPressed; - const AssetItemGridView({Key? key,required this.device,required this.onPressed}) : super(key: key); + const AssetItemGridView({Key? key, required this.device, required this.onPressed}) : super(key: key); @override Widget build(BuildContext context) { @@ -44,6 +44,13 @@ class AssetItemGridView extends StatelessWidget { overflow: TextOverflow.ellipsis, style: AppTextStyles.bodyText.copyWith(color: AppColor.textStyleColor(context)), ), + if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[ + 2.height, + Text( + '*Upload Images for this asset', + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30), + ), + ], Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/views/widgets/equipment/asset_item_listview.dart b/lib/views/widgets/equipment/asset_item_listview.dart index 6fced692..73c9187b 100644 --- a/lib/views/widgets/equipment/asset_item_listview.dart +++ b/lib/views/widgets/equipment/asset_item_listview.dart @@ -44,6 +44,13 @@ class AssetItemListView extends StatelessWidget { children: [ InfoHeaderWidget(device.modelDefinition!.assetName!.cleanupWhitespace), // device.modelDefinition!.assetName!.cleanupWhitespace.capitalizeFirstOfEach.heading6(context), + if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[ + 2.height, + Text( + '*Upload Images for this asset', + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30), + ), + ], 4.height, InfoTextWidget(label: context.translation.assetNumber, value: device.assetNumber), InfoTextWidget(label: context.translation.modelName, value: device.modelDefinition!.modelName), diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index 92a168a8..37df435b 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -97,7 +99,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt mainAxisAlignment: MainAxisAlignment.center, children: [ if (!widget.fromBottomBar) - const Icon(Icons.arrow_back_ios).onPress(() { + Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back).onPress(() { Navigator.of(context).pop(); }), Container( @@ -122,20 +124,28 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt ], ), ).onPress(() async { - final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => const DeviceSearchScreen())); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => const DeviceSearchScreen())); if (result != null) { _searchAsset = result; } _searchableList.clear(); _searchableList.addAll(_devicesProvider.devices); }).expanded, - 16.width, - Icon(Icons.grid_view_outlined, color: AppColor.icon2Color(context)).onPress(() => setState(() { - showListView = !showListView; - })), + 12.width, + IconButton( + icon: Icon(!showListView ? Icons.view_list_sharp : Icons.grid_view_outlined, color: AppColor.icon2Color(context)), + padding: EdgeInsets.zero, + iconSize: 20, + visualDensity: VisualDensity.compact, + tooltip: !showListView ? "View as List" : "View as Grid", + onPressed: () => setState(() { + showListView = !showListView; + }), + ), IconButton( icon: "download".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Export", onPressed: () async { await Navigator.push( @@ -154,9 +164,10 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt IconButton( icon: "filter".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, + visualDensity: VisualDensity.compact, tooltip: "Filter", onPressed: () async { - final result = await Navigator.push(context, CupertinoPageRoute(builder: (context) => DeviceFilterScreen(data: _searchAsset))); + final result = await Navigator.push(context, CupertinoPageRoute(fullscreenDialog: true, builder: (context) => DeviceFilterScreen(data: _searchAsset))); if (result != null) { _searchAsset = result; } @@ -165,7 +176,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt }, ), ], - ).paddingOnly(start: 16, end: 16), + ).paddingOnly(start: 16, end: 8), ), body: SafeArea( child: LoadingManager( @@ -305,7 +316,7 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt floatingActionButton: FloatingActionButton( heroTag: "some tag 2", backgroundColor: AppColor.primary10, - child: "qr".toSvgAsset(height: 32, fit: BoxFit.fitHeight, color: AppColor.background(context)), + child: Icon(Icons.qr_code_2_rounded, size: 40, color: AppColor.addItemButtonColor(context)), onPressed: () async { String? result = await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const ScanQr()), diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart b/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart index 5adeb69e..c07d2870 100644 --- a/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart +++ b/lib/views/widgets/fullscreen_dialogs/multiple_selection_fullscreen_dialog.dart @@ -7,6 +7,7 @@ import 'package:test_sa/models/base.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/new_views/common_widgets/default_app_bar.dart'; typedef SelectionBuilderString = String Function(dynamic); @@ -61,8 +62,9 @@ class _SelectionBottomSheetState extends State extends State Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: getImageWidget(itemIndex)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(getImageWidget(itemIndex)); } else if (_isLocalUrl(images[itemIndex])) { OpenFile.open(images[itemIndex]); } else { @@ -162,21 +149,7 @@ class FilesList extends StatelessWidget { ), ).onPress(() async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - body: SafeArea( - child: Stack( - children: [ - // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, - InteractiveViewer(child: getImageWidget(itemIndex)).center, - const ABackButton(), - ], - ), - ), - ), - ), - ); + context.fullScreenImageViewer(getImageWidget(itemIndex)); } else if (_isLocalUrl(images[itemIndex])) { OpenFile.open(images[itemIndex]); } else { @@ -192,59 +165,6 @@ class FilesList extends StatelessWidget { }); }, ); - - // return ListView.separated( - // padding: padding ?? EdgeInsets.symmetric(horizontal: 32 * AppStyle.getScaleFactor(context)), - // scrollDirection: Axis.horizontal, - // itemCount: images?.length ?? 0, - // separatorBuilder: (context, itemIndex) => 8.width, - // itemBuilder: (context, itemIndex) { - // if (images[itemIndex] == null || !images[itemIndex].contains(".")) { - // return Text("Bad\nFile"); - // } - // var isImage = - // images[itemIndex].split(".").last.toLowerCase() == "png" || images[itemIndex].split(".").last.toLowerCase() == "jpg" || images[itemIndex].split(".").last.toLowerCase() == "jpeg"; - // var isPdf = images[itemIndex].split(".").last.toLowerCase() == "pdf"; - // var isExcel = images[itemIndex].split(".").last.toLowerCase() == "xlsx"; - // - // return (isImage - // ? (_isLocalUrl(images[itemIndex]) - // ? Image.file(File(images[itemIndex])) - // : ImageLoader( - // url: images[itemIndex], - // boxFit: BoxFit.cover, - // )) - // : Image.asset("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png")) - // .onPress(() async { - // if (isImage) { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (_) => Scaffold( - // body: SafeArea( - // child: Stack( - // children: [ - // InteractiveViewer(child: Image(image: (_isLocalUrl(images[itemIndex]) ? FileImage(File(images[itemIndex])) : NetworkImage(images[itemIndex])))).center, - // const ABackButton(), - // ], - // ), - // ), - // ), - // ), - // ); - // } else if (_isLocalUrl(images[itemIndex])) { - // OpenFile.open(images[itemIndex]); - // } else { - // try { - // if (!await launchUrl(Uri.parse(images[itemIndex]), mode: LaunchMode.externalApplication)) { - // Fluttertoast.showToast(msg: "UnExpected Error with file."); - // throw Exception('Could not launch '); - // } - // } catch (ex) { - // Fluttertoast.showToast(msg: "No Viewer found."); - // } - // } - // }); - // }); } Widget getImageWidget(int itemIndex) { diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index 582bc6ea..a0533210 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -3,11 +3,13 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_blur_detection/image_blur_detection.dart'; import 'package:image_picker/image_picker.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/helper/utils.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -15,280 +17,6 @@ import 'package:test_sa/new_views/app_style/app_color.dart'; import '../../../new_views/common_widgets/app_dashed_button.dart'; import 'multi_image_picker_item.dart'; -// class MultiFilesPicker extends StatefulWidget { -// final String label; -// final bool error; -// final List files; -// final List attachment; -// -// final bool enabled, onlyImages; -// double? buttonHeight; -// Widget? buttonIcon; -// Color? buttonColor; -// final Function(List)? onChange; -// final bool showAsGrid; -// -// MultiFilesPicker( -// {Key? key, -// this.files = const [], -// this.attachment = const [], -// required this.label, -// this.error = false, -// this.buttonHeight, -// this.buttonIcon, -// this.enabled = true, -// this.onlyImages = false, -// this.onChange, -// this.showAsGrid = false, -// this.buttonColor}) -// : super(key: key); -// -// @override -// State createState() => _MultiFilesPickerState(); -// } -// -// class _MultiFilesPickerState extends State { -// @override -// Widget build(BuildContext context) { -// return Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// if (widget.enabled) ...[ -// AppDashedButton( -// title: widget.label, -// height: widget.buttonHeight, -// buttonColor: widget.buttonColor, -// icon: widget.buttonIcon, -// onPressed: (widget.enabled == false) -// ? () {} -// : widget.showAsGrid -// ? showFileSourceSheet -// : onFilePicker), -// 16.height, -// ], -// if (widget.files.isNotEmpty) -// Wrap( -// spacing: 8.toScreenWidth, -// children: List.generate( -// widget.files!.length, -// (index) { -// File image = widget.files![index]; -// return MultiFilesPickerItem( -// file: image, -// enabled: widget.enabled, -// onRemoveTap: (image) { -// if (!widget.enabled) { -// return; -// } -// widget.files.remove(image); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// setState(() {}); -// }, -// ); -// }, -// ), -// ), -// ], -// ); -// } -// -// fromFilePicker() async { -// FilePickerResult? result = await FilePicker.platform.pickFiles( -// type: FileType.custom, -// allowMultiple: true, -// allowedExtensions: widget.onlyImages ? ['jpg', 'jpeg', 'png'] : ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx'], -// ); -// if (result != null) { -// for (var path in result.paths) { -// widget.files.add(File(path!)); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// } -// setState(() {}); -// } -// } -// -// void showFileSourceSheet() async { -// if (widget.files.length >= 5) { -// Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); -// return; -// } -// -// ImageSource? source = (await showModalBottomSheet( -// context: context, -// shape: const RoundedRectangleBorder( -// borderRadius: BorderRadius.vertical( -// top: Radius.circular(20), -// ), -// ), -// clipBehavior: Clip.antiAliasWithSaveLayer, -// builder: (BuildContext context) => Column( -// mainAxisSize: MainAxisSize.min, -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// "Attach File".heading4(context), -// 12.height, -// GridView( -// padding: const EdgeInsets.all(0), -// shrinkWrap: true, -// physics: const NeverScrollableScrollPhysics(), -// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), -// children: [ -// gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)), -// gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)), -// gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { -// await fromFilePicker(); -// Navigator.pop(context); -// }), -// ], -// ), -// 12.height, -// ], -// ).paddingAll(21), -// )) as ImageSource?; -// if (source == null) return; -// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); -// -// if (pickedFile != null) { -// File fileImage = File(pickedFile.path); -// widget.files.add(fileImage); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// setState(() {}); -// } -// } -// -// Widget gridItem(IconData iconData, String title) { -// return Container( -// padding: const EdgeInsets.all(12), -// decoration: BoxDecoration( -// color: Colors.white, -// borderRadius: BorderRadius.circular(12), -// border: Border.all(color: const Color(0xffF1F1F1), width: 1), -// ), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// Icon(iconData, color: const Color(0xff7D859A), size: 36), -// Text( -// title, -// style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), -// ), -// ], -// ), -// ); -// } -// -// onFilePicker() async { -// if (widget.files.length >= 5) { -// Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); -// return; -// } -// ImageSource? source = await showModalBottomSheet( -// context: context, -// builder: (BuildContext context) { -// Widget listCard({required String icon, required String label, required VoidCallback onTap}) { -// return GestureDetector( -// onTap: onTap, -// child: Container( -// constraints: BoxConstraints(minWidth: 111.toScreenWidth, minHeight: 111.toScreenHeight), -// padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 12.toScreenHeight), -// decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(width: 1, color: AppColor.white70)), -// child: Column( -// mainAxisSize: MainAxisSize.min, -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// icon.toSvgAsset(), -// 24.height, -// label.bodyText2(context).custom(color: AppColor.black20), -// ], -// ), -// ), -// ); -// } -// -// return Container( -// padding: const EdgeInsets.all(16.0), -// child: Row( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// children: [ -// listCard( -// icon: 'camera_icon', -// label: '${context.translation.open}\n${context.translation.camera}', -// onTap: () { -// Navigator.of(context).pop(ImageSource.camera); -// }, -// ), -// listCard( -// icon: 'gallery_icon', -// label: '${context.translation.open}\n${context.translation.gallery}', -// onTap: () { -// Navigator.of(context).pop(ImageSource.gallery); -// }, -// ), -// listCard( -// icon: 'file_icon', -// label: '${context.translation.open}\n${context.translation.files}', -// onTap: () async { -// await fromFilePicker(); -// Navigator.pop(context); -// }, -// ), -// ], -// ), -// ); -// }, -// ); -// // ImageSource source = await showDialog( -// // context: context, -// // builder: (dialogContext) => CupertinoAlertDialog( -// // actions: [ -// // TextButton( -// // child: Text(context.translation.pickFromCamera), -// // onPressed: () { -// // Navigator.of(dialogContext).pop(ImageSource.camera); -// // }, -// // ), -// // TextButton( -// // child: Text(context.translation.pickFromGallery), -// // onPressed: () { -// // Navigator.of(dialogContext).pop(ImageSource.gallery); -// // }, -// // ), -// // TextButton( -// // child: Text(context.translation.pickFromFiles), -// // onPressed: () async { -// // await fromFilePicker(); -// // Navigator.pop(context); -// // }, -// // ), -// // ], -// // ), -// // ); -// if (source == null) return; -// -// final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); -// -// if (pickedFile != null) { -// File fileImage = File(pickedFile.path); -// if (fileImage != null) { -// widget.files.add(fileImage); -// if (widget.onChange != null) { -// widget.onChange!(widget.files); -// } -// setState(() {}); -// } -// } -// -// setState(() {}); -// } -// } - class AttachmentModel { int id = 0; File? file; @@ -311,6 +39,7 @@ class AttachmentModel { } class AttachmentPicker extends StatefulWidget { + final String pickerTitle; final String label; final bool error; final List attachment; @@ -320,14 +49,17 @@ class AttachmentPicker extends StatefulWidget { Widget? buttonIcon; Color? buttonColor; final Function(List)? onChange; - final bool showAsGrid; final bool showAsListView; + final bool pickMultiple; Lookup? documentType; + Widget? child; + bool checkQuality; AttachmentPicker( {Key? key, this.attachment = const [], required this.label, + this.pickerTitle = "Attach File", this.error = false, this.buttonHeight, this.buttonIcon, @@ -335,8 +67,10 @@ class AttachmentPicker extends StatefulWidget { this.onlyImages = false, this.onChange, this.documentType, - this.showAsGrid = false, this.showAsListView = false, + this.pickMultiple = false, + this.checkQuality = false, + this.child, this.buttonColor}) : super(key: key); @@ -344,75 +78,91 @@ class AttachmentPicker extends StatefulWidget { State createState() => _AttachmentPickerState(); } -class _AttachmentPickerState extends State { +class _AttachmentPickerState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AppDashedButton( - title: widget.label, - height: widget.buttonHeight, - buttonColor: widget.buttonColor, - icon: widget.buttonIcon, - onPressed: (widget.enabled == false) - ? () {} - : widget.showAsGrid - ? showFileSourceSheet - : onFilePicker), - 16.height, - if (widget.attachment.isNotEmpty) - widget.showAsListView - ? ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemBuilder: (cxt, index) { - File image = File(widget.attachment[index].name!); - return MultiFilesPickerItem( - file: image, - showAsListView: widget.showAsListView, - enabled: widget.enabled, - documentType: widget.attachment[index].documentTypeId?.name, - onRemoveTap: (image) { - if (!widget.enabled) { - return; - } - widget.attachment.removeAt(index); - if (widget.onChange != null) { - widget.onChange!(widget.attachment); - } - setState(() {}); - }, - ); - }, - separatorBuilder: (cxt, index) => 8.height, - itemCount: widget.attachment.length) - : Wrap( - spacing: 8.toScreenWidth, - children: List.generate( - widget.attachment.length, - (index) { - File image = File(widget.attachment[index].name!); - return MultiFilesPickerItem( - file: image, - enabled: widget.enabled, - onRemoveTap: (image) { - if (!widget.enabled) { - return; - } - widget.attachment.removeAt(index); - if (widget.onChange != null) { - widget.onChange!(widget.attachment); - } - setState(() {}); + return widget.child != null + ? widget.child!.onPress((widget.enabled == false) ? () {} : () => onFilePicker(widget.checkQuality)) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, + children: [ + AppDashedButton( + title: widget.label, + height: widget.buttonHeight, + buttonColor: widget.buttonColor, + icon: widget.buttonIcon, + onPressed: (widget.enabled == false) ? () {} : () => onFilePicker(widget.checkQuality)), + if (widget.attachment.isNotEmpty) + widget.showAsListView + ? ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.only(top: 0), + itemBuilder: (cxt, index) { + File image = File(widget.attachment[index].name!); + return MultiFilesPickerItem( + file: image, + showAsListView: widget.showAsListView, + enabled: widget.enabled, + documentType: widget.attachment[index].documentTypeId?.name, + onRemoveTap: (image) { + if (!widget.enabled) { + return; + } + widget.attachment.removeAt(index); + if (widget.onChange != null) { + widget.onChange!(widget.attachment); + } + setState(() {}); + }, + ); }, - ); - }, - ), - ), - ], - ); + separatorBuilder: (cxt, index) => 8.height, + itemCount: widget.attachment.length) + : Wrap( + spacing: 8.toScreenWidth, + children: List.generate( + widget.attachment.length, + (index) { + File image = File(widget.attachment[index].name!); + return MultiFilesPickerItem( + file: image, + enabled: widget.enabled, + onRemoveTap: (image) { + if (!widget.enabled) { + return; + } + widget.attachment.removeAt(index); + if (widget.onChange != null) { + widget.onChange!(widget.attachment); + } + setState(() {}); + }, + ); + }, + ), + ).paddingOnly(top: 0), + ], + ); } fromFilePicker() async { @@ -432,52 +182,6 @@ class _AttachmentPickerState extends State { } } - void showFileSourceSheet() async { - ImageSource source = (await showModalBottomSheet( - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - clipBehavior: Clip.antiAliasWithSaveLayer, - builder: (BuildContext context) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Attach File".heading4(context), - 12.height, - GridView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), - children: [ - gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)), - gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)), - gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { - await fromFilePicker(); - Navigator.pop(context); - }), - ], - ), - 12.height, - ], - ).paddingAll(21), - )) as ImageSource; - - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); - - if (pickedFile != null) { - File fileImage = File(pickedFile.path); - widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); - if (widget.onChange != null) { - widget.onChange!(widget.attachment); - } - setState(() {}); - } - } - Widget gridItem(IconData iconData, String title) { return Container( padding: const EdgeInsets.all(12), @@ -490,7 +194,7 @@ class _AttachmentPickerState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Icon(iconData, color: AppColor.icon2Color(context), size: 24), + Icon(iconData, color: AppColor.icon2Color(context), size: 24), title.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), // Text( // title, @@ -501,7 +205,11 @@ class _AttachmentPickerState extends State { ); } - onFilePicker() async { + onFilePicker(bool checkQuality) async { + // Reset and restart animation for bottom sheet + _animationController.reset(); + _animationController.forward(); + ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -517,122 +225,129 @@ class _AttachmentPickerState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - icon.toSvgAsset(color: AppColor.icon2Color(context), width: 36, height: 36), + icon.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), label.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context), fontSize: context.isTablet() ? 10.toScreenWidth : null), ], ), ).onPress(onTap); } + List listItems = [ + listCard( + icon: 'camera_icon', + label: '${context.translation.open}\n${context.translation.camera}', + onTap: () { + Navigator.of(context).pop(ImageSource.camera); + }, + ), + listCard( + icon: 'gallery_icon', + label: '${context.translation.open}\n${context.translation.gallery}', + onTap: () { + Navigator.of(context).pop(ImageSource.gallery); + }, + ), + listCard( + icon: 'file_icon', + label: '${context.translation.open}\n${context.translation.files}', + onTap: () async { + await fromFilePicker(); + Navigator.pop(context); + }, + ), + ]; + return SafeArea( top: false, child: Container( width: double.infinity, - color: AppColor.background(context), + color: AppColor.bottomSheetColor(context), + padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Attach File".heading4(context), - 12.height, - GridView( - padding: const EdgeInsets.all(0), + widget.pickerTitle.heading4(context), + GridView.builder( + padding: const EdgeInsets.only(top: 16, bottom: 0), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), - children: [ - listCard( - icon: 'camera_icon', - label: '${context.translation.open}\n${context.translation.camera}', - onTap: () { - Navigator.of(context).pop(ImageSource.camera); - }, - ), - listCard( - icon: 'gallery_icon', - label: '${context.translation.open}\n${context.translation.gallery}', - onTap: () { - Navigator.of(context).pop(ImageSource.gallery); - }, - ), - listCard( - icon: 'file_icon', - label: '${context.translation.open}\n${context.translation.files}', - onTap: () async { - await fromFilePicker(); - Navigator.pop(context); - }, - ), - ], + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16), + itemCount: listItems.length, + itemBuilder: (context, index) { + // Calculate staggered animation delay + final delay = index * 0.08; + final animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOut, + ), + ), + ); + + final slideAnimation = Tween( + begin: const Offset(0.0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.8), + (delay + 0.3).clamp(0.0, 1.0), + curve: Curves.easeOutCubic, + ), + ), + ); + + return FadeTransition( + opacity: animation, + child: SlideTransition(position: slideAnimation, child: listItems[index]), + ); + }, ), - // Container( - // padding: const EdgeInsets.all(16.0), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // listCard( - // icon: 'camera_icon', - // label: '${context.translation.open}\n${context.translation.camera}', - // onTap: () { - // Navigator.of(context).pop(ImageSource.camera); - // }, - // ), - // listCard( - // icon: 'gallery_icon', - // label: '${context.translation.open}\n${context.translation.gallery}', - // onTap: () { - // Navigator.of(context).pop(ImageSource.gallery); - // }, - // ), - // listCard( - // icon: 'file_icon', - // label: '${context.translation.open}\n${context.translation.files}', - // onTap: () async { - // await fromFilePicker(); - // Navigator.pop(context); - // }, - // ), - // ], - // ), - // ), ], - ).paddingAll(16), + ), ), ); }, ); - // ImageSource source = await showDialog( - // context: context, - // builder: (dialogContext) => CupertinoAlertDialog( - // actions: [ - // TextButton( - // child: Text(context.translation.pickFromCamera), - // onPressed: () { - // Navigator.of(dialogContext).pop(ImageSource.camera); - // }, - // ), - // TextButton( - // child: Text(context.translation.pickFromGallery), - // onPressed: () { - // Navigator.of(dialogContext).pop(ImageSource.gallery); - // }, - // ), - // TextButton( - // child: Text(context.translation.pickFromFiles), - // onPressed: () async { - // await fromFilePicker(); - // Navigator.pop(context); - // }, - // ), - // ], - // ), - // ); if (source == null) return; - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); + if (widget.pickMultiple && source == ImageSource.gallery) { + final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70); + if (pickedFiles.isNotEmpty) { + pickedFiles.forEach((pickedFile) { + File fileImage = File(pickedFile.path); + widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); + }); + if (widget.onChange != null) { + widget.onChange!(widget.attachment); + } + setState(() {}); + } + return; + } + + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 50); if (pickedFile != null) { File fileImage = File(pickedFile.path); + + if (checkQuality) { + ImageQualityValidator imageQualityValidator = ImageQualityValidator(); + final result = await imageQualityValidator.validate(await fileImage.readAsBytes()); + + if (!result.isValid) { + String issues = "Your image have following problems:"; + result.issues.forEach((issue) => (issues += "\n- $issue")); + context.showInfoDialog(issues, title: "Bad Quality Found"); + return; + } + } + widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType)); if (widget.onChange != null) { widget.onChange!(widget.attachment); @@ -640,6 +355,6 @@ class _AttachmentPickerState extends State { setState(() {}); } - setState(() {}); + // setState(() {}); } } diff --git a/lib/views/widgets/images/multi_image_picker_item.dart b/lib/views/widgets/images/multi_image_picker_item.dart index acd8a992..e555a9dd 100644 --- a/lib/views/widgets/images/multi_image_picker_item.dart +++ b/lib/views/widgets/images/multi_image_picker_item.dart @@ -70,15 +70,20 @@ class MultiFilesPickerItem extends StatelessWidget { ), 8.width, if (enabled) - InkWell( - child: "remove".toSvgAsset(width: 16,color: AppColor.red30), - onTap: () { + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { onRemoveTap(file); if (onRemove != null) { onRemove!(); } }, - ) + ), ], ), 4.height, @@ -92,23 +97,12 @@ class MultiFilesPickerItem extends StatelessWidget { recognizer: TapGestureRecognizer() ..onTap = () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer( - child: _isLocalUrl(file.path) - ? Image.file(file) - : ImageLoader( - url: URLs.getFileUrl(file.path), - boxFit: BoxFit.cover, - ), - ).center, - ), - ), - ), - ); + context.fullScreenImageViewer(_isLocalUrl(file.path) + ? Image.file(file) + : ImageLoader( + url: URLs.getFileUrl(file.path), + boxFit: BoxFit.cover, + )); } else if (_isLocalUrl(file.path)) { OpenFile.open(file.path); } else { @@ -150,23 +144,12 @@ class MultiFilesPickerItem extends StatelessWidget { padding: EdgeInsets.zero, onPressed: () async { if (isImage) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea( - child: InteractiveViewer( - child: _isLocalUrl(file.path) - ? Image.file(file) - : ImageLoader( - url: URLs.getFileUrl(file.path), - boxFit: BoxFit.cover, - ), - ).center, - ), - ), - ), - ); + context.fullScreenImageViewer(_isLocalUrl(file.path) + ? Image.file(file) + : ImageLoader( + url: URLs.getFileUrl(file.path), + boxFit: BoxFit.cover, + )); } else if (_isLocalUrl(file.path)) { OpenFile.open(file.path); } else { @@ -179,9 +162,14 @@ class MultiFilesPickerItem extends StatelessWidget { ), ), if (enabled) - InkWell( - child: "remove".toSvgAsset(width: 16), - onTap: () { + IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.clear), + iconSize: 18, + style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(), + onPressed: () { onRemoveTap(file); if (onRemove != null) { onRemove!(); diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index 06e15e91..29f3fba0 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -1,4 +1,9 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.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/new_views/app_style/app_color.dart'; @@ -8,9 +13,10 @@ class InfoTextWidget extends StatelessWidget { final String label; final bool showEmptyValue; final bool showArrow; + final bool copyValue; final bool isMsg; - InfoTextWidget({required this.label, this.value, this.showEmptyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); + InfoTextWidget({required this.label, this.value, this.showEmptyValue = false, this.copyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -33,19 +39,38 @@ class InfoTextWidget extends StatelessWidget { "arrow_right".toSvgAsset(width: 17, color: AppColor.primary10), ], ); + } else if (copyValue && (value?.isNotEmpty ?? false)) { + text = Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible(child: text), + 8.width, + const Icon(Icons.copy_rounded, color: AppColor.primary10, size: 14).onPress(() { + Clipboard.setData(ClipboardData(text: value!)).then((value) { + if (Platform.isIOS) { + "Copied.".addTranslation.showToast; + } + }); + }), + ], + ); } return text; } } + class InfoTextLabelWidget extends StatelessWidget { final String label; + final Color? color; + final TextStyle? textStyle; - InfoTextLabelWidget({required this.label, Key? key}) : super(key: key); + const InfoTextLabelWidget({required this.label, this.color, this.textStyle, Key? key}) : super(key: key); @override Widget build(BuildContext context) { Widget text; - text = Text(label, style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context))); + text = Text(label, style: (textStyle ?? AppTextStyles.bodyText2).copyWith(color: color ?? AppColor.lightTextColor(context))); return text; } -} \ No newline at end of file +} diff --git a/lib/views/widgets/qr/scan_qr.dart b/lib/views/widgets/qr/scan_qr.dart index 78c22e46..81fc6dfa 100644 --- a/lib/views/widgets/qr/scan_qr.dart +++ b/lib/views/widgets/qr/scan_qr.dart @@ -155,10 +155,16 @@ class _ScannerOverlay extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: FloatingActionButton( - heroTag: "some tag 3", + heroTag: "qr-scan", backgroundColor: AppColor.background(context), onPressed: onBack, - child: Icon(Icons.arrow_back, size: 24.toScreenWidth, color: AppColor.icon2Color(context)), + hoverColor: Colors.transparent, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder( + side: BorderSide(color: AppColor.borderColor(context)), + borderRadius: BorderRadius.circular(14.0), + ), + child: Icon(Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back, size: 24, color: AppColor.icon2Color(context)), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 937cec84..8fa3ed1c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -758,6 +758,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.0" + image_blur_detection: + dependency: "direct main" + description: + name: image_blur_detection + sha256: "0d1043422b90b97dea2aa282aa5cbb206e34bdc43acc0cb312b31f160f977333" + url: "https://pub.dev" + source: hosted + version: "1.0.1" image_cropper: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 23e7cefa..3d3b693d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.8+44 +version: 1.8.1+47 environment: sdk: ">=3.5.0 <4.0.0" @@ -88,6 +88,7 @@ dependencies: flutter_timezone: ^3.0.1 device_calendar: ^4.3.3 pie_chart: ^5.4.0 + image_blur_detection: ^1.0.1 badges: ^3.1.1 # buttons_tabbar: ^1.1.2 @@ -139,6 +140,7 @@ flutter: - assets/images/ - assets/images/dashboard/ - assets/images/wo/ + - assets/images/asset_image_upload/ - assets/lottie/ - assets/subtitles/ - assets/rives/