From edb8676f5af4d174c28851358f635c993e273b62 Mon Sep 17 00:00:00 2001 From: Dennis Nemec Date: Sat, 20 Dec 2025 21:00:33 +0100 Subject: [PATCH] Added custom tour ordering --- ios/Runner.xcodeproj/project.pbxproj | 114 ++++++++++++ .../contents.xcworkspacedata | 3 + .../presentation/login_page.dart | 2 +- .../presentation/car_management_page.dart | 2 +- .../delivery/detail/bloc/delivery_bloc.dart | 104 ++++++----- .../delivery/detail/bloc/note_bloc.dart | 56 ++++-- .../delivery/detail/bloc/note_event.dart | 8 + .../delivery/detail/bloc/note_state.dart | 15 +- .../detail/presentation/delivery_sign.dart | 81 +++++++-- .../presentation/delivery_overview.dart | 162 ++++++++++-------- .../delivery_overview_custom_sort.dart | 113 ++++++++++++ .../presentation/delivery_overview_page.dart | 7 +- lib/widget/app.dart | 1 + 13 files changed, 502 insertions(+), 166 deletions(-) create mode 100644 lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 35cd0bb..565af1d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 997C0E4FB7B2C67AB8388B3F /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB0CFA44E0F4A317CC3E8B41 /* Pods_RunnerTests.framework */; }; + CAFC9CC8D4DE6A37AF21BCD7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2499FC75E94DB5A00A1507 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,14 +42,20 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0F2499FC75E94DB5A00A1507 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 20C61E69ACC59657689C39B0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 2504B6EAF74460EDFF2C3034 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 2F591058654CE4129294673B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6E068ED7E85C34D749C5FEEC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 85C723A0EDD97FFF5B2BD758 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,13 +63,24 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F632AF4257FECE6E4D6B8524 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + FB0CFA44E0F4A317CC3E8B41 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 8C070AF792EDA6C56B543C6F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 997C0E4FB7B2C67AB8388B3F /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CAFC9CC8D4DE6A37AF21BCD7 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +95,15 @@ path = RunnerTests; sourceTree = ""; }; + 7A92659C1D86874FFAC72EDC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0F2499FC75E94DB5A00A1507 /* Pods_Runner.framework */, + FB0CFA44E0F4A317CC3E8B41 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +122,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + ED39057CEE7CA15DEFF8D2EE /* Pods */, + 7A92659C1D86874FFAC72EDC /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +151,19 @@ path = Runner; sourceTree = ""; }; + ED39057CEE7CA15DEFF8D2EE /* Pods */ = { + isa = PBXGroup; + children = ( + 85C723A0EDD97FFF5B2BD758 /* Pods-Runner.debug.xcconfig */, + 6E068ED7E85C34D749C5FEEC /* Pods-Runner.release.xcconfig */, + 20C61E69ACC59657689C39B0 /* Pods-Runner.profile.xcconfig */, + 2F591058654CE4129294673B /* Pods-RunnerTests.debug.xcconfig */, + F632AF4257FECE6E4D6B8524 /* Pods-RunnerTests.release.xcconfig */, + 2504B6EAF74460EDFF2C3034 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +171,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 2682F75C0133161F95975778 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 8C070AF792EDA6C56B543C6F /* Frameworks */, ); buildRules = ( ); @@ -145,12 +190,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 4C53ED7C58784643594B286A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 174761F3B03AFDA6621CD1D2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +269,45 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 174761F3B03AFDA6621CD1D2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 2682F75C0133161F95975778 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +324,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 4C53ED7C58784643594B286A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -365,6 +473,7 @@ DEVELOPMENT_TEAM = 37Z4ZA4QU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -379,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2F591058654CE4129294673B /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F632AF4257FECE6E4D6B8524 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2504B6EAF74460EDFF2C3034 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -545,6 +657,7 @@ DEVELOPMENT_TEAM = 37Z4ZA4QU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -568,6 +681,7 @@ DEVELOPMENT_TEAM = 37Z4ZA4QU2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/feature/authentication/presentation/login_page.dart b/lib/feature/authentication/presentation/login_page.dart index 5901320..684b5fb 100644 --- a/lib/feature/authentication/presentation/login_page.dart +++ b/lib/feature/authentication/presentation/login_page.dart @@ -60,7 +60,7 @@ class _LoginPageState extends State { debugPrint("🔵 Opening browser to: http://localhost:3000/login"); - final loginUrl = Uri.parse('http://192.168.1.9:3000/login'); + final loginUrl = Uri.parse('http://100.72.100.33:3000/login'); final launched = await launchUrl( loginUrl, mode: LaunchMode.externalApplication, diff --git a/lib/feature/cars/presentation/car_management_page.dart b/lib/feature/cars/presentation/car_management_page.dart index a1d354e..fb6f0eb 100644 --- a/lib/feature/cars/presentation/car_management_page.dart +++ b/lib/feature/cars/presentation/car_management_page.dart @@ -56,7 +56,7 @@ class _CarManagementPageState extends State { return Scaffold( body: BlocConsumer( listener: (context, state) { - if (state is CarsLoaded) { + if (state is CarsLoaded && context.read().state is TourLoaded) { var tour = (context.read().state as TourLoaded).tour.copyWith(); tour.driver.cars = state.cars; context.read().add(UpdateTour(tour: tour)); diff --git a/lib/feature/delivery/detail/bloc/delivery_bloc.dart b/lib/feature/delivery/detail/bloc/delivery_bloc.dart index 6eae017..8366063 100644 --- a/lib/feature/delivery/detail/bloc/delivery_bloc.dart +++ b/lib/feature/delivery/detail/bloc/delivery_bloc.dart @@ -4,6 +4,8 @@ import 'package:hl_lieferservice/dto/discount_add_response.dart'; import 'package:hl_lieferservice/dto/discount_update_response.dart'; import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart'; import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart'; +import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart'; import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart'; import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; @@ -15,6 +17,7 @@ import '../../../../model/delivery.dart' as model; class DeliveryBloc extends Bloc { OperationBloc opBloc; + NoteBloc noteBloc; DeliveryRepository repository; NoteRepository noteRepository; @@ -22,6 +25,7 @@ class DeliveryBloc extends Bloc { required this.opBloc, required this.repository, required this.noteRepository, + required this.noteBloc }) : super(DeliveryInitial()) { on(_unscan); on(_resetAmount); @@ -34,10 +38,8 @@ class DeliveryBloc extends Bloc { on(_finishDelivery); } - void _finishDelivery( - FinishDeliveryEvent event, - Emitter emit, - ) async { + void _finishDelivery(FinishDeliveryEvent event, + Emitter emit,) async { final currentState = state; opBloc.add(LoadOperation()); @@ -70,10 +72,8 @@ class DeliveryBloc extends Bloc { } } - void _updatePayment( - UpdateSelectedPaymentMethodEvent event, - Emitter emit, - ) { + void _updatePayment(UpdateSelectedPaymentMethodEvent event, + Emitter emit,) { final currentState = state; if (currentState is DeliveryLoaded) { @@ -85,25 +85,23 @@ class DeliveryBloc extends Bloc { } } - void _updateDeliveryOptions( - UpdateDeliveryOptionEvent event, - Emitter emit, - ) { + void _updateDeliveryOptions(UpdateDeliveryOptionEvent event, + Emitter emit,) { final currentState = state; if (currentState is DeliveryLoaded) { List options = - currentState.delivery.options.map((option) { - if (option.key == event.key) { - if (option.numerical) { - return option.copyWith(value: event.value); - } else { - return option.copyWith(value: event.value == true ? "1" : "0"); - } - } + currentState.delivery.options.map((option) { + if (option.key == event.key) { + if (option.numerical) { + return option.copyWith(value: event.value); + } else { + return option.copyWith(value: event.value == true ? "1" : "0"); + } + } - return option; - }).toList(); + return option; + }).toList(); emit( DeliveryLoaded( @@ -113,10 +111,8 @@ class DeliveryBloc extends Bloc { } } - void _updateDiscount( - UpdateDiscountEvent event, - Emitter emit, - ) async { + void _updateDiscount(UpdateDiscountEvent event, + Emitter emit,) async { opBloc.add(LoadOperation()); try { @@ -139,22 +135,22 @@ class DeliveryBloc extends Bloc { String discountArticleNumber = delivery.discount!.article.articleNumber; delivery.discount = model.Discount( article: - response.values?.article != null - ? Article.fromDTO(response.values!.article) - : delivery.discount!.article, + response.values?.article != null + ? Article.fromDTO(response.values!.article) + : delivery.discount!.article, note: - response.values?.note != null - ? response.values!.note.noteDescription - : delivery.discount!.note, + response.values?.note != null + ? response.values!.note.noteDescription + : delivery.discount!.note, noteId: - response.values?.note != null - ? response.values!.note.rowId - : delivery.discount!.noteId, + response.values?.note != null + ? response.values!.note.rowId + : delivery.discount!.noteId, ); delivery.articles = [ ...delivery.articles.where( - (article) => article.articleNumber != discountArticleNumber, + (article) => article.articleNumber != discountArticleNumber, ), delivery.discount!.article, ]; @@ -165,7 +161,8 @@ class DeliveryBloc extends Bloc { } } catch (e, st) { debugPrint( - "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:", + "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event + .deliveryId}:", ); debugPrint("$e"); debugPrint("$st"); @@ -176,10 +173,8 @@ class DeliveryBloc extends Bloc { } } - void _removeDiscount( - RemoveDiscountEvent event, - Emitter emit, - ) async { + void _removeDiscount(RemoveDiscountEvent event, + Emitter emit,) async { opBloc.add(LoadOperation()); try { @@ -196,9 +191,9 @@ class DeliveryBloc extends Bloc { delivery.articles .where( (article) => - article.internalId != - delivery.discount?.article.internalId, - ) + article.internalId != + delivery.discount?.article.internalId, + ) .toList(); delivery.discount = null; @@ -245,6 +240,10 @@ class DeliveryBloc extends Bloc { noteId: response.values.note.rowId, ); + noteBloc.add(AddNoteOffline(note: response.values.note.noteDescription, + deliveryId: delivery.id, + noteId: response.values.note.rowId)); + delivery.articles = [...delivery.articles, delivery.discount!.article]; emit(currentState.copyWith(delivery)); @@ -253,7 +252,8 @@ class DeliveryBloc extends Bloc { } } catch (e, st) { debugPrint( - "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:", + "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event + .deliveryId}:", ); debugPrint("$e"); debugPrint("$st"); @@ -284,7 +284,7 @@ class DeliveryBloc extends Bloc { if (currentState is DeliveryLoaded) { Article article = currentState.delivery.articles.firstWhere( - (article) => article.internalId == int.parse(event.articleId), + (article) => article.internalId == int.parse(event.articleId), ); article.removeNoteId = noteId; @@ -293,7 +293,7 @@ class DeliveryBloc extends Bloc { List
articles = [ ...currentState.delivery.articles.where( - (article) => article.internalId != int.parse(event.articleId), + (article) => article.internalId != int.parse(event.articleId), ), article, ]; @@ -313,10 +313,8 @@ class DeliveryBloc extends Bloc { } } - void _resetAmount( - ResetScanAmountEvent event, - Emitter emit, - ) async { + void _resetAmount(ResetScanAmountEvent event, + Emitter emit,) async { opBloc.add(LoadOperation()); try { @@ -325,7 +323,7 @@ class DeliveryBloc extends Bloc { if (currentState is DeliveryLoaded) { Article article = currentState.delivery.articles.firstWhere( - (article) => article.internalId == int.parse(event.articleId), + (article) => article.internalId == int.parse(event.articleId), ); article.removeNoteId = null; @@ -334,7 +332,7 @@ class DeliveryBloc extends Bloc { List
articles = [ ...currentState.delivery.articles.where( - (article) => article.internalId != int.parse(event.articleId), + (article) => article.internalId != int.parse(event.articleId), ), article, ]; diff --git a/lib/feature/delivery/detail/bloc/note_bloc.dart b/lib/feature/delivery/detail/bloc/note_bloc.dart index 967ccf3..6bb7783 100644 --- a/lib/feature/delivery/detail/bloc/note_bloc.dart +++ b/lib/feature/delivery/detail/bloc/note_bloc.dart @@ -15,7 +15,7 @@ class NoteBloc extends Bloc { final OperationBloc opBloc; NoteBloc({required this.repository, required this.opBloc}) - : super(NoteInitial()) { + : super(NoteInitial()) { on(_load); on(_add); on(_edit); @@ -23,16 +23,44 @@ class NoteBloc extends Bloc { on(_upload); on(_removeImage); on(_reset); + on(_addOffline); } Future _reset(ResetNotes event, Emitter emit) async { emit.call(NoteInitial()); } - Future _removeImage( - RemoveImageNote event, - Emitter emit, - ) async { + Future _addOffline(AddNoteOffline event, + Emitter emit,) async { + if (state is NoteInitial) { + emit( + NoteLoadedBase( + notes: [Note(content: event.note, id: int.parse(event.noteId))], + ), + ); + } + + if (state is NoteLoadedBase) { + emit( + NoteLoadedBase( + notes: [ + ...(state as NoteLoadedBase).notes, + Note(content: event.note, id: int.parse(event.noteId)), + ], + ), + ); + } + + if (state is NoteLoaded) { + final current = state as NoteLoaded; + emit(NoteLoaded(notes: [...current.notes, Note(content: event.note, id: int.parse(event.noteId))], + templates: [...current.templates], + images: [...current.images])); + } + } + + Future _removeImage(RemoveImageNote event, + Emitter emit,) async { opBloc.add(LoadOperation()); try { @@ -43,9 +71,9 @@ class NoteBloc extends Bloc { emit.call( currentState.copyWith( images: - currentState.images - .where((image) => image.$1.objectId != event.objectId) - .toList(), + currentState.images + .where((image) => image.$1.objectId != event.objectId) + .toList(), ), ); } @@ -89,7 +117,7 @@ class NoteBloc extends Bloc { try { List urls = - event.delivery.images.map((image) => image.url).toList(); + event.delivery.images.map((image) => image.url).toList(); List notes = await repository.loadNotes(event.delivery.id); List templates = await repository.loadTemplates(); List images = await repository.loadImages(urls); @@ -100,7 +128,7 @@ class NoteBloc extends Bloc { templates: templates, images: List.generate( images.length, - (index) => (event.delivery.images[index], images[index]), + (index) => (event.delivery.images[index], images[index]), ), ), ); @@ -148,7 +176,7 @@ class NoteBloc extends Bloc { if (currentState is NoteLoaded) { List refreshedNotes = [ ...currentState.notes.where( - (note) => note.id != int.parse(event.noteId), + (note) => note.id != int.parse(event.noteId), ), Note(content: event.content, id: int.parse(event.noteId)), ]; @@ -173,9 +201,9 @@ class NoteBloc extends Bloc { if (currentState is NoteLoaded) { List refreshedNotes = - currentState.notes - .where((note) => note.id != int.parse(event.noteId)) - .toList(); + currentState.notes + .where((note) => note.id != int.parse(event.noteId)) + .toList(); emit.call(currentState.copyWith(notes: refreshedNotes)); } diff --git a/lib/feature/delivery/detail/bloc/note_event.dart b/lib/feature/delivery/detail/bloc/note_event.dart index eedf60b..a69bd5d 100644 --- a/lib/feature/delivery/detail/bloc/note_event.dart +++ b/lib/feature/delivery/detail/bloc/note_event.dart @@ -18,6 +18,14 @@ class AddNote extends NoteEvent { final String deliveryId; } +class AddNoteOffline extends NoteEvent { + AddNoteOffline({required this.note, required this.deliveryId, required this.noteId}); + + final String note; + final String noteId; + final String deliveryId; +} + class RemoveNote extends NoteEvent { RemoveNote({required this.noteId}); diff --git a/lib/feature/delivery/detail/bloc/note_state.dart b/lib/feature/delivery/detail/bloc/note_state.dart index 4565839..ca9e7a5 100644 --- a/lib/feature/delivery/detail/bloc/note_state.dart +++ b/lib/feature/delivery/detail/bloc/note_state.dart @@ -10,14 +10,21 @@ class NoteLoading extends NoteState {} class NoteLoadingFailed extends NoteState {} -class NoteLoaded extends NoteState { - NoteLoaded({ +class NoteLoadedBase extends NoteState { + NoteLoadedBase({ required this.notes, - required this.templates, - required this.images, }); List notes; +} + +class NoteLoaded extends NoteLoadedBase { + NoteLoaded({ + required this.templates, + required this.images, + required super.notes, + }); + List templates; List<(ImageNote, Uint8List)> images; diff --git a/lib/feature/delivery/detail/presentation/delivery_sign.dart b/lib/feature/delivery/detail/presentation/delivery_sign.dart index 84d071f..ba9981d 100644 --- a/lib/feature/delivery/detail/presentation/delivery_sign.dart +++ b/lib/feature/delivery/detail/presentation/delivery_sign.dart @@ -48,11 +48,29 @@ class _SignatureViewState extends State { bool _customerAccepted = false; bool _noteAccepted = false; bool _notesEmpty = true; + bool _isCustomerSignatureEmpty = true; + bool _isDriverSignatureEmpty = true; @override void initState() { super.initState(); + _customerController.addListener(() { + if (_isCustomerSignatureEmpty != _customerController.isEmpty) { + setState(() { + _isCustomerSignatureEmpty = _customerController.isEmpty; + }); + } + }); + + _driverController.addListener(() { + if (_isDriverSignatureEmpty != _driverController.isEmpty) { + setState(() { + _isDriverSignatureEmpty = _driverController.isEmpty; + }); + } + }); + // only load notes if they are not already loaded final noteState = context.read().state; if (noteState is NoteInitial) { @@ -63,6 +81,7 @@ class _SignatureViewState extends State { @override void dispose() { _customerController.dispose(); + _driverController.dispose(); super.dispose(); } @@ -92,12 +111,18 @@ class _SignatureViewState extends State { _notesEmpty = current.notes.isEmpty; }); } + + if (current is NoteLoadedBase) { + setState(() { + _notesEmpty = current.notes.isEmpty; + }); + } }, builder: (context, state) { final current = state; - if (current is NoteLoaded) { + if (current is NoteLoadedBase) { if (current.notes.isEmpty) { return const SizedBox( width: double.infinity, @@ -107,12 +132,12 @@ class _SignatureViewState extends State { return ListView.separated( shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), + physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return ListTile( leading: const Icon(Icons.event_note_outlined), title: Text(current.notes[index].content), - contentPadding: EdgeInsets.all(20), + contentPadding: const EdgeInsets.all(20), tileColor: Theme.of(context).colorScheme.onSecondary, ); }, @@ -121,7 +146,7 @@ class _SignatureViewState extends State { ); } - return SizedBox( + return const SizedBox( width: double.infinity, child: Center(child: CircularProgressIndicator()), ); @@ -156,10 +181,17 @@ class _SignatureViewState extends State { }); }, ), - const Flexible( - child: Text( - "Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.", - overflow: TextOverflow.fade, + Flexible( + child: InkWell( + onTap: _notesEmpty ? null : () { + setState(() { + _noteAccepted = !_noteAccepted; + }); + }, + child: Text( + "Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.", + overflow: TextOverflow.fade, + ), ), ), ], @@ -177,10 +209,17 @@ class _SignatureViewState extends State { }); }, ), - const Flexible( - child: Text( - "Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.", - overflow: TextOverflow.fade, + Flexible( + child: InkWell( + child: Text( + "Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.", + overflow: TextOverflow.fade, + ), + onTap: () { + setState(() { + _customerAccepted = !_customerAccepted; + }); + }, ), ), ], @@ -195,6 +234,16 @@ class _SignatureViewState extends State { Widget build(BuildContext context) { String formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now()); + bool isButtonEnabled; + if (!_isDriverSigning) { + isButtonEnabled = + _customerAccepted && + (_noteAccepted || _notesEmpty) && + !_isCustomerSignatureEmpty; + } else { + isButtonEnabled = !_isDriverSignatureEmpty; + } + return Scaffold( appBar: AppBar( title: @@ -247,9 +296,8 @@ class _SignatureViewState extends State { child: Center( child: FilledButton( onPressed: - !(_customerAccepted && (_noteAccepted || _notesEmpty)) - ? null - : () async { + isButtonEnabled + ? () async { if (!_isDriverSigning) { setState(() { _isDriverSigning = true; @@ -260,7 +308,8 @@ class _SignatureViewState extends State { (await _driverController.toPngBytes())!, ); } - }, + } + : null, child: !_isDriverSigning ? const Text("Weiter") diff --git a/lib/feature/delivery/overview/presentation/delivery_overview.dart b/lib/feature/delivery/overview/presentation/delivery_overview.dart index c971195..0f0a085 100644 --- a/lib/feature/delivery/overview/presentation/delivery_overview.dart +++ b/lib/feature/delivery/overview/presentation/delivery_overview.dart @@ -4,6 +4,7 @@ import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_info.dart'; import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_list.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart'; import 'package:hl_lieferservice/model/tour.dart'; import '../../../../model/delivery.dart'; @@ -11,7 +12,11 @@ import '../../../authentication/bloc/auth_bloc.dart'; import '../../../authentication/bloc/auth_state.dart'; class DeliveryOverview extends StatefulWidget { - const DeliveryOverview({super.key, required this.tour, required this.distances}); + const DeliveryOverview({ + super.key, + required this.tour, + required this.distances, + }); final Tour tour; final Map distances; @@ -44,9 +49,7 @@ class _DeliveryOverviewState extends State { } Future _loadTour() async { - Authenticated state = context - .read() - .state as Authenticated; + Authenticated state = context.read().state as Authenticated; context.read().add(LoadTour(teamId: state.user.number)); } @@ -57,58 +60,47 @@ class _DeliveryOverviewState extends State { child: ListView( scrollDirection: Axis.horizontal, children: - widget.tour.driver.cars.map((car) { - Color? backgroundColor; - Color? iconColor = Theme - .of(context) - .primaryColor; - Color? textColor; + widget.tour.driver.cars.map((car) { + Color? backgroundColor; + Color? iconColor = Theme.of(context).primaryColor; + Color? textColor; - if (_selectedCarId == car.id) { - backgroundColor = Theme - .of(context) - .primaryColor; - textColor = Theme - .of(context) - .colorScheme - .onSecondary; - iconColor = Theme - .of(context) - .colorScheme - .onSecondary; - } + if (_selectedCarId == car.id) { + backgroundColor = Theme.of(context).primaryColor; + textColor = Theme.of(context).colorScheme.onSecondary; + iconColor = Theme.of(context).colorScheme.onSecondary; + } - return Padding( - padding: const EdgeInsets.only(right: 8), - child: GestureDetector( - onTap: () { - setState(() { - _selectedCarId = car.id; - }); - }, - child: Chip( - backgroundColor: backgroundColor, - label: Row( - children: [ - Icon(Icons.local_shipping, color: iconColor, size: 20), - Padding( - padding: const EdgeInsets.only(left: 5), - child: Text( - car.plate, - style: TextStyle(color: textColor, fontSize: 12), - ), + return Padding( + padding: const EdgeInsets.only(right: 8), + child: GestureDetector( + onTap: () { + setState(() { + _selectedCarId = car.id; + }); + }, + child: Chip( + backgroundColor: backgroundColor, + label: Row( + children: [ + Icon(Icons.local_shipping, color: iconColor, size: 20), + Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + car.plate, + style: TextStyle(color: textColor, fontSize: 12), + ), + ), + ], ), - ], + ), ), - ), - ), - ); - }).toList(), + ); + }).toList(), ), ); } - @override Widget build(BuildContext context) { return RefreshIndicator( @@ -118,7 +110,12 @@ class _DeliveryOverviewState extends State { children: [ DeliveryInfo(tour: widget.tour), Padding( - padding: const EdgeInsets.only(left: 10, right: 10), + padding: const EdgeInsets.only( + left: 10, + right: 10, + top: 15, + bottom: 10, + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -126,10 +123,7 @@ class _DeliveryOverviewState extends State { children: [ Text( "Fahrten", - style: Theme - .of(context) - .textTheme - .headlineSmall, + style: Theme.of(context).textTheme.headlineSmall, ), ], ), @@ -147,25 +141,41 @@ class _DeliveryOverviewState extends State { _deliveries = _deliveries.reversed.toList(); }); } + + if (value == "custom") { + showDialog( + context: context, + fullscreenDialog: true, + builder: (context) => CustomSortDialog(), + ); + } + + if (value == "distance") { + // TODO: muss noch implementiert werden + } }); }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: 'name-asc', - child: Text('Name (A-Z)'), - ), - PopupMenuItem( - value: 'name-desc', - child: Text('Name (Z-A)'), - ), - PopupMenuItem( - value: 'distance', - child: Text('Entfernung'), - ), - ], + itemBuilder: + (BuildContext context) => >[ + PopupMenuItem( + value: 'name-asc', + child: Text('Name (A-Z)'), + ), + PopupMenuItem( + value: 'name-desc', + child: Text('Name (Z-A)'), + ), + PopupMenuItem( + value: 'distance', + child: Text('Entfernung'), + ), + PopupMenuItem( + value: 'custom', + child: Text('Eigene Sortierung'), + ), + ], child: Icon(Icons.filter_list), - ) - + ), ], ), ), @@ -177,13 +187,13 @@ class _DeliveryOverviewState extends State { child: DeliveryList( distances: widget.distances, deliveries: - _deliveries - .where( - (delivery) => - delivery.carId == _selectedCarId && - delivery.allArticlesScanned(), - ) - .toList(), + _deliveries + .where( + (delivery) => + delivery.carId == _selectedCarId && + delivery.allArticlesScanned(), + ) + .toList(), ), ), ], diff --git a/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart b/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart new file mode 100644 index 0000000..680a1a9 --- /dev/null +++ b/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_bloc.dart'; +import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart'; + +class CustomSortDialog extends StatefulWidget { + const CustomSortDialog({super.key}); + + @override + State createState() => _CustomSortDialogState(); +} + +class _CustomSortDialogState extends State { + Widget _information() { + return Padding( + padding: EdgeInsets.only(top: 15), + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(15), + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(right: 15), + child: Icon(Icons.info_outline, color: Colors.blueAccent), + ), + Expanded( + child: Text( + "Ziehen Sie die einzelnen Lieferungen mit dem Finger in die gewünschte Position.", + ), + ), + ], + ), + ), + Divider(), + _sortableList(), + ], + ), + ); + } + + Widget _sortableList() { + return BlocBuilder( + builder: (context, state) { + final currentState = state; + + if (currentState is TourLoaded) { + return Expanded( + child: ReorderableListView( + onReorder: (oldIndex, newIndex) {}, + children: currentState.tour.deliveries.indexed.fold([], ( + acc, + current, + ) { + final delivery = current.$2; + final index = current.$1; + + acc.add( + ListTile( + leading: CircleAvatar( + backgroundColor: Theme.of(context).primaryColor, + child: Text( + "${index + 1}", + style: TextStyle( + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + ), + title: Text(delivery.customer.name), + subtitle: Text(delivery.customer.address.toString(), style: TextStyle(fontSize: 11),), + trailing: Icon(Icons.drag_handle), + key: Key("reorder-item-${delivery.id}"), + ), + ); + return acc; + }), + ), + ); + } + + return Center(child: CircularProgressIndicator()); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 20, right: 10, top: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Fahrten sortieren", + style: Theme.of(context).textTheme.headlineSmall, + ), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close), + ), + ], + ), + ), + + Expanded(child: _information()), + ], + ), + ); + } +} diff --git a/lib/feature/delivery/overview/presentation/delivery_overview_page.dart b/lib/feature/delivery/overview/presentation/delivery_overview_page.dart index 872de13..c891c837 100644 --- a/lib/feature/delivery/overview/presentation/delivery_overview_page.dart +++ b/lib/feature/delivery/overview/presentation/delivery_overview_page.dart @@ -20,7 +20,12 @@ class _DeliveryOverviewPageState extends State { if (state is TourLoaded) { final currentState = state; - return Center(child: DeliveryOverview(tour: currentState.tour, distances: currentState.distances)); + return Center( + child: DeliveryOverview( + tour: currentState.tour, + distances: currentState.distances, + ), + ); } return Container(); diff --git a/lib/widget/app.dart b/lib/widget/app.dart index 676a75d..5441479 100644 --- a/lib/widget/app.dart +++ b/lib/widget/app.dart @@ -71,6 +71,7 @@ class _DeliveryAppState extends State { BlocProvider( create: (context) => DeliveryBloc( + noteBloc: context.read(), opBloc: context.read(), noteRepository: NoteRepository( service: NoteService(