Update iOS deployment target to 13.0, enhance Flutter integration, and improve tender submission flow

- Changed the iOS deployment target from 12.0 to 13.0 in Podfile and project settings.
- Updated AppDelegate to support implicit Flutter engine initialization.
- Modified Info.plist to include scene configuration for multiple scenes.
- Enhanced CompletionOfDocumentsRouteData to accept a tenderId parameter.
- Implemented tender submission functionality in TenderDetailViewModel and updated UI components to reflect loading states and success/error messages.
- Added new strings for submission success and error notifications.
This commit is contained in:
AmirReza Jamali
2026-05-30 16:48:05 +03:30
parent ae08b946f6
commit e6b4720dcd
19 changed files with 445 additions and 55 deletions
-2
View File
@@ -20,7 +20,5 @@
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '12.0' # platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+25 -3
View File
@@ -14,6 +14,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -55,6 +56,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -62,6 +64,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -79,6 +82,7 @@
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -142,6 +146,9 @@
productType = "com.apple.product-type.bundle.unit-test"; productType = "com.apple.product-type.bundle.unit-test";
}; };
97C146ED1CF9000F007C117D /* Runner */ = { 97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
@@ -165,6 +172,9 @@
/* Begin PBXProject section */ /* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
@@ -346,7 +356,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@@ -472,7 +482,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -523,7 +533,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@@ -611,6 +621,18 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 97C146E61CF9000F007C117D /* Project object */; rootObject = 97C146E61CF9000F007C117D /* Project object */;
} }
@@ -0,0 +1,176 @@
{
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "dkcamera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKCamera",
"state" : {
"branch" : "master",
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
}
},
{
"identity" : "dkimagepickercontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKImagePickerController",
"state" : {
"branch" : "4.3.9",
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
}
},
{
"identity" : "dkphotogallery",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
"state" : {
"branch" : "master",
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "8d5b4189f1f482df8d5c58c9985ea70491ef5382",
"version" : "12.14.0"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "9bfcc6cf435b2e7c5562c1900b8680c594fa9a64",
"version" : "3.6.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "219e564a8510e983e675c94f77f7f7c50049f22d",
"version" : "12.14.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "c0ac7575d70050c2973ba2318bd5af47f8e8153a",
"version" : "5.3.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
"version" : "5.21.7"
}
},
{
"identity" : "swiftygif",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif.git",
"state" : {
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
"version" : "5.4.5"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
"version" : "2.8.0"
}
}
],
"version" : 2
}
@@ -5,6 +5,24 @@
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
@@ -26,6 +44,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@@ -54,6 +73,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
+5 -2
View File
@@ -2,12 +2,15 @@ import Flutter
import UIKit import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} }
+25 -4
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@@ -24,6 +26,29 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
@@ -41,9 +66,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>
+5 -2
View File
@@ -250,11 +250,14 @@ class FinalCompletionOfDocumentsRouteData extends GoRouteData
@TypedGoRoute<CompletionOfDocumentsRouteData>(path: '/completion_of_documents') @TypedGoRoute<CompletionOfDocumentsRouteData>(path: '/completion_of_documents')
class CompletionOfDocumentsRouteData extends GoRouteData class CompletionOfDocumentsRouteData extends GoRouteData
with _$CompletionOfDocumentsRouteData { with _$CompletionOfDocumentsRouteData {
const CompletionOfDocumentsRouteData(); final String? tenderId;
const CompletionOfDocumentsRouteData({this.tenderId});
@override @override
Widget build(BuildContext context, GoRouterState state) { Widget build(BuildContext context, GoRouterState state) {
return const CompletionOfDocumentsScreen(); return tenderDetailProvider(
child: CompletionOfDocumentsScreen(tenderId: tenderId),
);
} }
} }
+12 -2
View File
@@ -308,10 +308,20 @@ RouteBase get $completionOfDocumentsRouteData => GoRouteData.$route(
mixin _$CompletionOfDocumentsRouteData on GoRouteData { mixin _$CompletionOfDocumentsRouteData on GoRouteData {
static CompletionOfDocumentsRouteData _fromState(GoRouterState state) => static CompletionOfDocumentsRouteData _fromState(GoRouterState state) =>
const CompletionOfDocumentsRouteData(); CompletionOfDocumentsRouteData(
tenderId: state.uri.queryParameters['tender-id'],
);
CompletionOfDocumentsRouteData get _self =>
this as CompletionOfDocumentsRouteData;
@override @override
String get location => GoRouteData.$location('/completion_of_documents'); String get location => GoRouteData.$location(
'/completion_of_documents',
queryParams: {
if (_self.tenderId != null) 'tender-id': _self.tenderId!,
},
);
@override @override
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);
@@ -6,6 +6,7 @@ import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart'; import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart';
import '../core/constants/tender_approval_status.dart'; import '../core/constants/tender_approval_status.dart';
import '../core/constants/tender_submision_mode.dart';
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart'; import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart';
import '../data/services/model/tender_approvals_data/tender_approvals_data.dart'; import '../data/services/model/tender_approvals_data/tender_approvals_data.dart';
import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart'; import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
@@ -143,4 +144,33 @@ class TenderDetailViewModel with ChangeNotifier {
_errorMessage = null; _errorMessage = null;
notifyListeners(); notifyListeners();
} }
/// Submits the tender for completion via the self-apply flow.
/// POSTs to /api/v1/tender-approvals with status "submitted" and
/// submission_mode "self-apply". Returns true on success.
Future<bool> submitForCompletion({required String tenderId}) async {
_isSubmitApprovalLoading = true;
_errorMessage = null;
notifyListeners();
bool success = false;
final result = await _tendersRepository.toggleApprovals(
tenderId: tenderId,
status: TenderApprovalStatus.submitted.value,
submissionMode: TenderSubmissionMode.selfApply.value,
);
switch (result) {
case Ok<TenderApprovalsToggleResponse>():
success = true;
_status = result.value.data?.status ?? '';
break;
case Error<TenderApprovalsToggleResponse>():
_errorMessage = result.error.toString();
break;
}
_isSubmitApprovalLoading = false;
notifyListeners();
return success;
}
} }
+13 -4
View File
@@ -206,7 +206,9 @@ class YourTendersViewModel with ChangeNotifier {
offset: _tendersOffset, offset: _tendersOffset,
); );
await getTenders(requestModel: requestModel); await getTenders(requestModel: requestModel);
} else if (selectedStatus == YourTendersStrings.tendersSubmitted) { } else if (selectedStatus == YourTendersStrings.tendersSubmitted ||
selectedStatus == TenderApprovalStatus.submitted.value ||
selectedStatus == TenderApprovalStatus.approved.value) {
final requestModel = GetTendersRequestModel( final requestModel = GetTendersRequestModel(
status: TenderApprovalStatus.submitted.value, status: TenderApprovalStatus.submitted.value,
createdFrom: startDateUnix, createdFrom: startDateUnix,
@@ -299,10 +301,17 @@ class YourTendersViewModel with ChangeNotifier {
reset: true, reset: true,
); );
} else if (selectedStatus == TenderApprovalStatus.approved.value) { } else if (selectedStatus == TenderApprovalStatus.approved.value) {
await getTendersFeedback( // "Approved tenders" maps to submitted tender-approvals. The API has no
feedbackType: TenderApprovalStatus.approved.value, // `approved` value; approved tenders are the ones a company has submitted
reset: true, // for pursuit (GET /api/v1/tender-approvals?status=submitted).
final requestModel = GetTendersRequestModel(
status: TenderApprovalStatus.submitted.value,
createdFrom: startDateUnix,
createdTo: endDateUnix,
limit: _tendersLimit,
offset: _tendersOffset,
); );
await getTenders(requestModel: requestModel, reset: true);
} }
} }
@@ -7,7 +7,8 @@ import '../../../core/utils/size_config.dart';
import '../../shared/responsive_builder.dart'; import '../../shared/responsive_builder.dart';
class CompletionOfDocumentsScreen extends StatefulWidget { class CompletionOfDocumentsScreen extends StatefulWidget {
const CompletionOfDocumentsScreen({super.key}); final String? tenderId;
const CompletionOfDocumentsScreen({this.tenderId, super.key});
@override @override
State<CompletionOfDocumentsScreen> createState() => _CompletionOfDocumentsScreenState(); State<CompletionOfDocumentsScreen> createState() => _CompletionOfDocumentsScreenState();
@@ -41,10 +42,10 @@ class _CompletionOfDocumentsScreenState extends State<CompletionOfDocumentsScree
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context); SizeConfig.init(context);
return const ResponsiveBuilder( return ResponsiveBuilder(
mobile: CompletionOfDocumentsMobilePage(), mobile: CompletionOfDocumentsMobilePage(tenderId: widget.tenderId),
tablet: CompletionOfDocumentsTabletPage(), tablet: CompletionOfDocumentsTabletPage(tenderId: widget.tenderId),
desktop: CompletionOfDocumentsDesktopPage(), desktop: CompletionOfDocumentsDesktopPage(tenderId: widget.tenderId),
); );
} }
} }
@@ -3,6 +3,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/app_toast.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart'; import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/base_button.dart';
import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
@@ -10,7 +14,8 @@ import 'package:tm_app/views/shared/tablet_desktop_appbar.dart'
show TabletDesktopAppbar; show TabletDesktopAppbar;
class CompletionOfDocumentsDesktopPage extends StatelessWidget { class CompletionOfDocumentsDesktopPage extends StatelessWidget {
const CompletionOfDocumentsDesktopPage({super.key}); final String? tenderId;
const CompletionOfDocumentsDesktopPage({this.tenderId, super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -77,14 +82,17 @@ class CompletionOfDocumentsDesktopPage extends StatelessWidget {
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
BaseButton( Consumer<TenderDetailViewModel>(
isEnabled: true, builder: (context, viewModel, _) => BaseButton(
onPressed: () {}, isEnabled: !viewModel.isSubmitApprovalLoading,
text: isLoading: viewModel.isSubmitApprovalLoading,
CompletionOfDocumentsStrings onPressed: () => _submitForCompletion(context),
.submitForCompletion, text:
textColor: AppColors.textBlue, CompletionOfDocumentsStrings
backgroundColor: AppColors.primary30, .submitForCompletion,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary30,
),
), ),
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
], ],
@@ -304,6 +312,26 @@ class CompletionOfDocumentsDesktopPage extends StatelessWidget {
); );
} }
Future<void> _submitForCompletion(BuildContext context) async {
final id = tenderId;
if (id == null || id.isEmpty) {
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
return;
}
final viewModel = context.read<TenderDetailViewModel>();
final success = await viewModel.submitForCompletion(tenderId: id);
if (!context.mounted) return;
if (success) {
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
context.pop();
} else {
AppToast.error(
context,
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
);
}
}
Widget _docCard({ Widget _docCard({
required String title, required String title,
required String subtitle, required String subtitle,
@@ -3,13 +3,18 @@ import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/app_toast.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart'; import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/base_button.dart';
import '../../shared/tender_app_bar.dart'; import '../../shared/tender_app_bar.dart';
class CompletionOfDocumentsMobilePage extends StatelessWidget { class CompletionOfDocumentsMobilePage extends StatelessWidget {
const CompletionOfDocumentsMobilePage({super.key}); final String? tenderId;
const CompletionOfDocumentsMobilePage({this.tenderId, super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -242,12 +247,15 @@ class CompletionOfDocumentsMobilePage extends StatelessWidget {
), ),
), ),
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
BaseButton( Consumer<TenderDetailViewModel>(
isEnabled: true, builder: (context, viewModel, _) => BaseButton(
onPressed: () {}, isEnabled: !viewModel.isSubmitApprovalLoading,
text: CompletionOfDocumentsStrings.submitForCompletion, isLoading: viewModel.isSubmitApprovalLoading,
textColor: AppColors.textBlue, onPressed: () => _submitForCompletion(context),
backgroundColor: AppColors.primary30, text: CompletionOfDocumentsStrings.submitForCompletion,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary30,
),
), ),
], ],
), ),
@@ -256,6 +264,26 @@ class CompletionOfDocumentsMobilePage extends StatelessWidget {
); );
} }
Future<void> _submitForCompletion(BuildContext context) async {
final id = tenderId;
if (id == null || id.isEmpty) {
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
return;
}
final viewModel = context.read<TenderDetailViewModel>();
final success = await viewModel.submitForCompletion(tenderId: id);
if (!context.mounted) return;
if (success) {
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
context.pop();
} else {
AppToast.error(
context,
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
);
}
}
Widget _docCard({ Widget _docCard({
required String title, required String title,
required String subtitle, required String subtitle,
@@ -3,6 +3,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/utils/app_toast.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart'; import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/base_button.dart';
import 'package:tm_app/views/shared/tablet_desktop_appbar.dart'; import 'package:tm_app/views/shared/tablet_desktop_appbar.dart';
@@ -10,7 +14,8 @@ import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart';
class CompletionOfDocumentsTabletPage extends StatelessWidget { class CompletionOfDocumentsTabletPage extends StatelessWidget {
const CompletionOfDocumentsTabletPage({super.key}); final String? tenderId;
const CompletionOfDocumentsTabletPage({this.tenderId, super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -83,14 +88,17 @@ class CompletionOfDocumentsTabletPage extends StatelessWidget {
SizedBox(height: 24.0.h()), SizedBox(height: 24.0.h()),
BaseButton( Consumer<TenderDetailViewModel>(
isEnabled: true, builder: (context, viewModel, _) => BaseButton(
onPressed: () {}, isEnabled: !viewModel.isSubmitApprovalLoading,
text: isLoading: viewModel.isSubmitApprovalLoading,
CompletionOfDocumentsStrings onPressed: () => _submitForCompletion(context),
.submitForCompletion, text:
textColor: AppColors.textBlue, CompletionOfDocumentsStrings
backgroundColor: AppColors.primary30, .submitForCompletion,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary30,
),
), ),
], ],
), ),
@@ -309,6 +317,26 @@ class CompletionOfDocumentsTabletPage extends StatelessWidget {
); );
} }
Future<void> _submitForCompletion(BuildContext context) async {
final id = tenderId;
if (id == null || id.isEmpty) {
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
return;
}
final viewModel = context.read<TenderDetailViewModel>();
final success = await viewModel.submitForCompletion(tenderId: id);
if (!context.mounted) return;
if (success) {
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
context.pop();
} else {
AppToast.error(
context,
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
);
}
}
// -------------------- Document Card -------------------- // -------------------- Document Card --------------------
Widget _docCard({ Widget _docCard({
required String title, required String title,
@@ -7,4 +7,6 @@ class CompletionOfDocumentsStrings {
'Set Deadline for Completion of documents :'; 'Set Deadline for Completion of documents :';
static const String note = 'Note:'; static const String note = 'Note:';
static const String submitForCompletion = 'Submit for completion'; static const String submitForCompletion = 'Submit for completion';
static const String submitSuccess = 'Tender submitted for completion';
static const String submitError = 'Failed to submit tender for completion';
} }
@@ -75,9 +75,9 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
// submissionMode: value, // submissionMode: value,
// ); // );
const CompletionOfDocumentsRouteData().push( CompletionOfDocumentsRouteData(
context, tenderId: widget.detail.id!,
); ).push(context);
} else if (value == } else if (value ==
TenderSubmissionMode.partnership.value) { TenderSubmissionMode.partnership.value) {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
@@ -213,8 +213,9 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
const FinalCompletionOfDocumentsRouteData() const FinalCompletionOfDocumentsRouteData()
.push(context); .push(context);
} else { } else {
const CompletionOfDocumentsRouteData() CompletionOfDocumentsRouteData(
.push(context); tenderId: widget.detail.id!,
).push(context);
} }
} }
} else if (value == } else if (value ==
@@ -23,4 +23,5 @@ class ProfileStrings {
static const String mainDataTab = 'Main Data'; static const String mainDataTab = 'Main Data';
static const String membersTab = 'Members'; static const String membersTab = 'Members';
static const String subjectTab = 'Subject'; static const String subjectTab = 'Subject';
static const String appVersion = 'Version 1.0.7';
} }
@@ -98,6 +98,15 @@ class MainDataTab extends StatelessWidget {
), ),
), ),
), ),
SizedBox(height: 24.0.h()),
Text(
ProfileStrings.appVersion,
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
], ],
), ),
); );