Merge pull request 'Refactor date handling in tender data model and UI components. Added unixTimestampFromJson for parsing timestamps and updated timeConvertor to handle null values. Updated relevant model fields and UI to use the new parsing logic.' (#225) from milliseconds-timestamps into develop
Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/225 Reviewed-by: Hadi Barzegar <barzagarhadi@gmail.com>
This commit is contained in:
@@ -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
@@ -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'
|
||||||
|
|||||||
@@ -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 "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<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"
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ List<SingleChildWidget> get repositories {
|
|||||||
(context) => HomeRepository(
|
(context) => HomeRepository(
|
||||||
homeService: context.read(),
|
homeService: context.read(),
|
||||||
yourTendersService: context.read(),
|
yourTendersService: context.read(),
|
||||||
|
likedTendersService: context.read(),
|
||||||
),
|
),
|
||||||
lazy: true,
|
lazy: true,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
enum TaskStatus { toDo, inProgress, done }
|
|
||||||
|
|
||||||
extension TaskStatusExtension on TaskStatus {
|
|
||||||
// تبدیل enum به string
|
|
||||||
String get value => name;
|
|
||||||
|
|
||||||
// تبدیل string به enum
|
|
||||||
static TaskStatus fromString(String? status) {
|
|
||||||
if (status == null) {
|
|
||||||
return TaskStatus.toDo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TaskStatus.values.firstWhere(
|
|
||||||
(e) => e.name == status,
|
|
||||||
orElse: () => TaskStatus.toDo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String get displayNameEn {
|
|
||||||
switch (this) {
|
|
||||||
case TaskStatus.toDo:
|
|
||||||
return 'To Do';
|
|
||||||
case TaskStatus.inProgress:
|
|
||||||
return 'In Progress';
|
|
||||||
case TaskStatus.done:
|
|
||||||
return 'Done';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import '../../data/services/model/board_response/board_response.dart';
|
|
||||||
import '../enums/task_status.dart';
|
|
||||||
|
|
||||||
extension BoardDataExtension on BoardData {
|
|
||||||
// تبدیل status string به TaskStatus enum
|
|
||||||
TaskStatus get statusEnum => TaskStatusExtension.fromString(status);
|
|
||||||
|
|
||||||
// کپی BoardData با status جدید
|
|
||||||
BoardData copyWithStatus(TaskStatus newStatus) {
|
|
||||||
return copyWith(status: newStatus.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,9 +1,41 @@
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
String timeConvertor(int timestamp) {
|
/// Parses an API timestamp field that may arrive as int, double, or string.
|
||||||
// Convert seconds → milliseconds
|
/// Returns null for missing, empty, unparseable, or non-positive values
|
||||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
/// (a non-positive epoch is treated as "no value" by the API).
|
||||||
|
int? unixTimestampFromJson(dynamic value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
int? parsed;
|
||||||
|
if (value is int) {
|
||||||
|
parsed = value;
|
||||||
|
} else if (value is num) {
|
||||||
|
parsed = value.toInt();
|
||||||
|
} else if (value is String) {
|
||||||
|
final trimmed = value.trim();
|
||||||
|
if (trimmed.isEmpty) return null;
|
||||||
|
parsed = int.tryParse(trimmed);
|
||||||
|
}
|
||||||
|
if (parsed == null || parsed <= 0) return null;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
// Format to yyyy-MM-dd
|
/// Converts a Unix timestamp from the API to a display string.
|
||||||
|
///
|
||||||
|
/// The API contract is seconds since epoch, but some legacy fields still
|
||||||
|
/// emit milliseconds. Anything past the year 2286 in seconds (>1e10) is
|
||||||
|
/// assumed to be milliseconds and converted down. Remove this branch once
|
||||||
|
/// every endpoint has been migrated to seconds.
|
||||||
|
String timeConvertor(int? unix, {bool hasTime = false}) {
|
||||||
|
if (unix == null || unix <= 0) return '-';
|
||||||
|
|
||||||
|
final seconds = unix > 9999999999 ? unix ~/ 1000 : unix;
|
||||||
|
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
seconds * 1000,
|
||||||
|
isUtc: true,
|
||||||
|
).toLocal();
|
||||||
|
|
||||||
|
if (hasTime) {
|
||||||
|
return DateFormat('yyyy-MM-dd HH:mm').format(date);
|
||||||
|
}
|
||||||
return DateFormat('yyyy-MM-dd').format(date);
|
return DateFormat('yyyy-MM-dd').format(date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,17 @@ class BoardRepository {
|
|||||||
BoardRepository({required BoardService boardService})
|
BoardRepository({required BoardService boardService})
|
||||||
: _boardService = boardService;
|
: _boardService = boardService;
|
||||||
|
|
||||||
Future<Result<BoardResponse>> getBoard() async {
|
Future<Result<BoardResponse>> getBoard() => _boardService.getBoard();
|
||||||
return _boardService.getBoard();
|
|
||||||
|
Future<Result<MoveCardResponse>> moveCard({
|
||||||
|
required String cardId,
|
||||||
|
required String columnId,
|
||||||
|
required int newOrder,
|
||||||
|
}) {
|
||||||
|
return _boardService.moveCard(
|
||||||
|
cardId: cardId,
|
||||||
|
columnId: columnId,
|
||||||
|
newOrder: newOrder,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import 'package:tm_app/core/utils/result.dart';
|
import 'package:tm_app/core/utils/result.dart';
|
||||||
import 'package:tm_app/data/services/home_service.dart';
|
import 'package:tm_app/data/services/home_service.dart';
|
||||||
|
import 'package:tm_app/data/services/liked_tenders_service.dart';
|
||||||
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
|
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_approvals_stats_response/tender_approvals_stats_response.dart';
|
import 'package:tm_app/data/services/model/tender_approvals_stats_response/tender_approvals_stats_response.dart';
|
||||||
|
|
||||||
import '../services/model/feedback_stats_response/feedback_stat_response.dart';
|
|
||||||
import '../services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
import '../services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||||
|
import '../services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart';
|
||||||
import '../services/your_tenders_service.dart';
|
import '../services/your_tenders_service.dart';
|
||||||
|
|
||||||
class HomeRepository {
|
class HomeRepository {
|
||||||
HomeRepository({
|
HomeRepository({
|
||||||
required HomeService homeService,
|
required HomeService homeService,
|
||||||
required YourTendersService yourTendersService,
|
required YourTendersService yourTendersService,
|
||||||
|
required LikedTendersService likedTendersService,
|
||||||
}) : _homeService = homeService,
|
}) : _homeService = homeService,
|
||||||
_yourTendersService = yourTendersService;
|
_yourTendersService = yourTendersService,
|
||||||
|
_likedTendersService = likedTendersService;
|
||||||
final HomeService _homeService;
|
final HomeService _homeService;
|
||||||
final YourTendersService _yourTendersService;
|
final YourTendersService _yourTendersService;
|
||||||
|
final LikedTendersService _likedTendersService;
|
||||||
|
|
||||||
Future<Result<RecommendedTendersResponse>> getHomeRecommendedTenders({
|
Future<Result<RecommendedTendersResponse>> getHomeRecommendedTenders({
|
||||||
required int limit,
|
required int limit,
|
||||||
@@ -34,11 +38,25 @@ class HomeRepository {
|
|||||||
return _homeService.getTendersApprovalsStats();
|
return _homeService.getTendersApprovalsStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<FeedbackStatResponse>> getFeedbackStats() {
|
/// Returns how many tenders the current user has liked (not company-wide).
|
||||||
return _homeService.getFeedbackStats();
|
Future<Result<int>> getUserLikedTendersCount() async {
|
||||||
}
|
final result = await _likedTendersService.getLikedTenders(
|
||||||
Future<Result<Map<String, dynamic>>> checkUnreadNotifications() async {
|
requestModel: TenderFeedbackRequestModel(
|
||||||
return _homeService.checkUnreadNotifications();
|
feedbackType: 'like',
|
||||||
}
|
limit: 1,
|
||||||
|
offset: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case Ok(value: final response):
|
||||||
|
return Result.ok(response.data?.meta?.total ?? 0);
|
||||||
|
case Error(error: final error):
|
||||||
|
return Result.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<Map<String, dynamic>>> checkUnreadNotifications() async {
|
||||||
|
return _homeService.checkUnreadNotifications();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class TendersRepository {
|
|||||||
|
|
||||||
Future<Result<TendersResponse>> getTenders({
|
Future<Result<TendersResponse>> getTenders({
|
||||||
required int limit,
|
required int limit,
|
||||||
required int offset,
|
int? offset,
|
||||||
|
String? cursor,
|
||||||
String? query,
|
String? query,
|
||||||
String? sortBy,
|
String? sortBy,
|
||||||
String? sortOrder,
|
String? sortOrder,
|
||||||
@@ -23,6 +24,7 @@ class TendersRepository {
|
|||||||
return _tendersService.getTenders(
|
return _tendersService.getTenders(
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
cursor: cursor,
|
||||||
query: query,
|
query: query,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
sortOrder: sortOrder,
|
sortOrder: sortOrder,
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ class TendersApi {
|
|||||||
static const String tenders = '/api/v1/tenders';
|
static const String tenders = '/api/v1/tenders';
|
||||||
static String getTenders({
|
static String getTenders({
|
||||||
required int limit,
|
required int limit,
|
||||||
required int offset,
|
int? offset,
|
||||||
|
String? cursor,
|
||||||
String? query,
|
String? query,
|
||||||
String? sortBy,
|
String? sortBy,
|
||||||
String? sortOrder,
|
String? sortOrder,
|
||||||
String? publicationDateFrom,
|
String? publicationDateFrom,
|
||||||
String? publicationDateTo,
|
String? publicationDateTo,
|
||||||
}) {
|
}) {
|
||||||
String url = '$tenders?limit=$limit&offset=$offset';
|
String url = '$tenders?limit=$limit';
|
||||||
|
if (cursor != null && cursor.isNotEmpty) {
|
||||||
|
url += '&cursor=${Uri.encodeComponent(cursor)}';
|
||||||
|
} else if (offset != null) {
|
||||||
|
url += '&offset=$offset';
|
||||||
|
}
|
||||||
if (query != null && query.isNotEmpty) {
|
if (query != null && query.isNotEmpty) {
|
||||||
url += '&q=${Uri.encodeComponent(query)}';
|
url += '&q=${Uri.encodeComponent(query)}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import '../../../core/network/network_manager.dart';
|
import 'dart:convert';
|
||||||
import '../../core/config/app_config.dart';
|
|
||||||
|
import '../../core/network/network_manager.dart';
|
||||||
import '../../core/utils/result.dart';
|
import '../../core/utils/result.dart';
|
||||||
import 'model/board_response/board_response.dart';
|
import 'model/board_response/board_response.dart';
|
||||||
|
|
||||||
@@ -9,72 +10,45 @@ class BoardService {
|
|||||||
|
|
||||||
final NetworkManager _networkManager;
|
final NetworkManager _networkManager;
|
||||||
|
|
||||||
|
static const _basePath = '/admin/v1/kanban/kanban';
|
||||||
|
|
||||||
Future<Result<BoardResponse>> getBoard() async {
|
Future<Result<BoardResponse>> getBoard() async {
|
||||||
final uri = '${AppConfig.apiBaseUrl}/api/v1/board';
|
return _networkManager.makeRequest(
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
'$_basePath/boards/default',
|
||||||
|
(json) => BoardResponse.fromJson(json),
|
||||||
|
method: 'GET',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// final result = await _networkManager.makeRequest(
|
Future<Result<MoveCardResponse>> moveCard({
|
||||||
// uri,
|
required String cardId,
|
||||||
// (json) => BoardResponse.fromJson(json),
|
required String columnId,
|
||||||
// method: 'GET',
|
required int newOrder,
|
||||||
// );
|
}) async {
|
||||||
// return result;
|
final body = jsonEncode({
|
||||||
|
'card_id': cardId,
|
||||||
return Result.ok(
|
'column_id': columnId,
|
||||||
BoardResponse(
|
'new_order': newOrder,
|
||||||
success: true,
|
});
|
||||||
message: 'Board data fetched successfully',
|
return _networkManager.makeRequest(
|
||||||
data: tasks,
|
'$_basePath/cards/move',
|
||||||
error: null,
|
(json) => MoveCardResponse.fromJson(json),
|
||||||
),
|
method: 'PUT',
|
||||||
|
data: body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BoardData> tasks = [
|
class MoveCardResponse {
|
||||||
BoardData(
|
MoveCardResponse({required this.success, required this.message});
|
||||||
id: '1',
|
|
||||||
description:
|
final bool? success;
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
final String? message;
|
||||||
deadline: DateTime(2025, 6, 15),
|
|
||||||
assignedTo: 'Pratyush Tewari',
|
factory MoveCardResponse.fromJson(Map<String, dynamic> json) {
|
||||||
isReady: false,
|
return MoveCardResponse(
|
||||||
status: 'toDo',
|
success: json['success'] as bool?,
|
||||||
),
|
message: json['message'] as String?,
|
||||||
BoardData(
|
);
|
||||||
id: '2',
|
}
|
||||||
description:
|
}
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
||||||
deadline: DateTime(2025, 6, 15),
|
|
||||||
assignedTo: 'Pratyush Tewari',
|
|
||||||
isReady: true,
|
|
||||||
status: 'toDo',
|
|
||||||
),
|
|
||||||
BoardData(
|
|
||||||
id: '3',
|
|
||||||
description:
|
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
||||||
deadline: DateTime(2025, 6, 15),
|
|
||||||
assignedTo: 'Pratyush Tewari',
|
|
||||||
isReady: false,
|
|
||||||
status: 'inProgress',
|
|
||||||
),
|
|
||||||
BoardData(
|
|
||||||
id: '4',
|
|
||||||
description:
|
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
||||||
deadline: DateTime(2025, 6, 15),
|
|
||||||
assignedTo: 'Pratyush Tewari',
|
|
||||||
isReady: true,
|
|
||||||
status: 'inProgress',
|
|
||||||
),
|
|
||||||
BoardData(
|
|
||||||
id: '5',
|
|
||||||
description:
|
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
|
||||||
deadline: DateTime(2025, 6, 15),
|
|
||||||
assignedTo: 'Pratyush Tewari',
|
|
||||||
isReady: false,
|
|
||||||
status: 'done',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,34 +1,229 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import '../../../../core/utils/date_utils.dart';
|
||||||
|
|
||||||
import '../error/error_model.dart';
|
import '../error/error_model.dart';
|
||||||
|
|
||||||
part 'board_response.freezed.dart';
|
class BoardResponse {
|
||||||
part 'board_response.g.dart';
|
BoardResponse({
|
||||||
|
required this.success,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
required this.error,
|
||||||
|
});
|
||||||
|
|
||||||
@freezed
|
final bool? success;
|
||||||
abstract class BoardResponse with _$BoardResponse {
|
final String? message;
|
||||||
const factory BoardResponse({
|
final BoardDetailData? data;
|
||||||
required bool? success,
|
final ErrorModel? error;
|
||||||
required String? message,
|
|
||||||
required List<BoardData>? data,
|
|
||||||
required ErrorModel? error,
|
|
||||||
}) = _BoardResponse;
|
|
||||||
|
|
||||||
factory BoardResponse.fromJson(Map<String, Object?> json) =>
|
factory BoardResponse.fromJson(Map<String, dynamic> json) {
|
||||||
_$BoardResponseFromJson(json);
|
final dataJson = json['data'];
|
||||||
|
final errorJson = json['error'];
|
||||||
|
return BoardResponse(
|
||||||
|
success: json['success'] as bool?,
|
||||||
|
message: json['message'] as String?,
|
||||||
|
data:
|
||||||
|
dataJson is Map<String, dynamic>
|
||||||
|
? BoardDetailData.fromJson(dataJson)
|
||||||
|
: null,
|
||||||
|
error:
|
||||||
|
errorJson is Map<String, dynamic>
|
||||||
|
? ErrorModel.fromJson(errorJson)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
class BoardDetailData {
|
||||||
abstract class BoardData with _$BoardData {
|
BoardDetailData({required this.board});
|
||||||
const factory BoardData({
|
|
||||||
required String? id,
|
|
||||||
required String? description,
|
|
||||||
required DateTime? deadline,
|
|
||||||
required String? assignedTo,
|
|
||||||
required bool? isReady,
|
|
||||||
required String? status,
|
|
||||||
}) = _BoardData;
|
|
||||||
|
|
||||||
factory BoardData.fromJson(Map<String, Object?> json) =>
|
final Board? board;
|
||||||
_$BoardDataFromJson(json);
|
|
||||||
|
factory BoardDetailData.fromJson(Map<String, dynamic> json) {
|
||||||
|
final boardJson = json['board'];
|
||||||
|
return BoardDetailData(
|
||||||
|
board:
|
||||||
|
boardJson is Map<String, dynamic> ? Board.fromJson(boardJson) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Board {
|
||||||
|
Board({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.columns,
|
||||||
|
required this.isDefault,
|
||||||
|
required this.userId,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? description;
|
||||||
|
final List<BoardColumn> columns;
|
||||||
|
final bool? isDefault;
|
||||||
|
final String? userId;
|
||||||
|
final int? createdAt;
|
||||||
|
final int? updatedAt;
|
||||||
|
|
||||||
|
factory Board.fromJson(Map<String, dynamic> json) {
|
||||||
|
final columnsJson = json['columns'];
|
||||||
|
return Board(
|
||||||
|
id: json['id'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
columns:
|
||||||
|
columnsJson is List
|
||||||
|
? columnsJson
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(BoardColumn.fromJson)
|
||||||
|
.toList()
|
||||||
|
: <BoardColumn>[],
|
||||||
|
isDefault: json['is_default'] as bool?,
|
||||||
|
userId: json['user_id'] as String?,
|
||||||
|
createdAt: unixTimestampFromJson(json['created_at']),
|
||||||
|
updatedAt: unixTimestampFromJson(json['updated_at']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Board copyWith({List<BoardColumn>? columns}) {
|
||||||
|
return Board(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
description: description,
|
||||||
|
columns: columns ?? this.columns,
|
||||||
|
isDefault: isDefault,
|
||||||
|
userId: userId,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoardColumn {
|
||||||
|
BoardColumn({
|
||||||
|
required this.id,
|
||||||
|
required this.boardId,
|
||||||
|
required this.name,
|
||||||
|
required this.order,
|
||||||
|
required this.color,
|
||||||
|
required this.limit,
|
||||||
|
required this.cards,
|
||||||
|
required this.cardCount,
|
||||||
|
required this.isOverLimit,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? id;
|
||||||
|
final String? boardId;
|
||||||
|
final String? name;
|
||||||
|
final int? order;
|
||||||
|
final String? color;
|
||||||
|
final int? limit;
|
||||||
|
final List<BoardCard> cards;
|
||||||
|
final int? cardCount;
|
||||||
|
final bool? isOverLimit;
|
||||||
|
final int? createdAt;
|
||||||
|
final int? updatedAt;
|
||||||
|
|
||||||
|
factory BoardColumn.fromJson(Map<String, dynamic> json) {
|
||||||
|
final cardsJson = json['cards'];
|
||||||
|
return BoardColumn(
|
||||||
|
id: json['id'] as String?,
|
||||||
|
boardId: json['board_id'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
order: (json['order'] as num?)?.toInt(),
|
||||||
|
color: json['color'] as String?,
|
||||||
|
limit: (json['limit'] as num?)?.toInt(),
|
||||||
|
cards:
|
||||||
|
cardsJson is List
|
||||||
|
? cardsJson
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(BoardCard.fromJson)
|
||||||
|
.toList()
|
||||||
|
: <BoardCard>[],
|
||||||
|
cardCount: (json['card_count'] as num?)?.toInt(),
|
||||||
|
isOverLimit: json['is_over_limit'] as bool?,
|
||||||
|
createdAt: unixTimestampFromJson(json['created_at']),
|
||||||
|
updatedAt: unixTimestampFromJson(json['updated_at']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoardColumn copyWith({List<BoardCard>? cards, int? cardCount}) {
|
||||||
|
return BoardColumn(
|
||||||
|
id: id,
|
||||||
|
boardId: boardId,
|
||||||
|
name: name,
|
||||||
|
order: order,
|
||||||
|
color: color,
|
||||||
|
limit: limit,
|
||||||
|
cards: cards ?? this.cards,
|
||||||
|
cardCount: cardCount ?? this.cardCount,
|
||||||
|
isOverLimit: isOverLimit,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoardCard {
|
||||||
|
BoardCard({
|
||||||
|
required this.id,
|
||||||
|
required this.columnId,
|
||||||
|
required this.tenderId,
|
||||||
|
required this.title,
|
||||||
|
required this.order,
|
||||||
|
required this.priority,
|
||||||
|
required this.labels,
|
||||||
|
required this.dueDate,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? id;
|
||||||
|
final String? columnId;
|
||||||
|
final String? tenderId;
|
||||||
|
final String? title;
|
||||||
|
final int? order;
|
||||||
|
final String? priority;
|
||||||
|
final List<String> labels;
|
||||||
|
final int? dueDate;
|
||||||
|
final int? createdAt;
|
||||||
|
final int? updatedAt;
|
||||||
|
|
||||||
|
factory BoardCard.fromJson(Map<String, dynamic> json) {
|
||||||
|
final labelsJson = json['labels'];
|
||||||
|
return BoardCard(
|
||||||
|
id: json['id'] as String?,
|
||||||
|
columnId: json['column_id'] as String?,
|
||||||
|
tenderId: json['tender_id'] as String?,
|
||||||
|
title: json['title'] as String?,
|
||||||
|
order: (json['order'] as num?)?.toInt(),
|
||||||
|
priority: json['priority'] as String?,
|
||||||
|
labels:
|
||||||
|
labelsJson is List
|
||||||
|
? labelsJson.whereType<String>().toList()
|
||||||
|
: <String>[],
|
||||||
|
dueDate: unixTimestampFromJson(json['due_date']),
|
||||||
|
createdAt: unixTimestampFromJson(json['created_at']),
|
||||||
|
updatedAt: unixTimestampFromJson(json['updated_at']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoardCard copyWith({String? columnId, int? order}) {
|
||||||
|
return BoardCard(
|
||||||
|
id: id,
|
||||||
|
columnId: columnId ?? this.columnId,
|
||||||
|
tenderId: tenderId,
|
||||||
|
title: title,
|
||||||
|
order: order ?? this.order,
|
||||||
|
priority: priority,
|
||||||
|
labels: labels,
|
||||||
|
dueDate: dueDate,
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,596 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// coverage:ignore-file
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'board_response.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
// dart format off
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$BoardResponse {
|
|
||||||
|
|
||||||
bool? get success; String? get message; List<BoardData>? get data; ErrorModel? get error;
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$BoardResponseCopyWith<BoardResponse> get copyWith => _$BoardResponseCopyWithImpl<BoardResponse>(this as BoardResponse, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this BoardResponse to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BoardResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.error, error) || other.error == error));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,success,message,const DeepCollectionEquality().hash(data),error);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BoardResponse(success: $success, message: $message, data: $data, error: $error)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $BoardResponseCopyWith<$Res> {
|
|
||||||
factory $BoardResponseCopyWith(BoardResponse value, $Res Function(BoardResponse) _then) = _$BoardResponseCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
bool? success, String? message, List<BoardData>? data, ErrorModel? error
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$ErrorModelCopyWith<$Res>? get error;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$BoardResponseCopyWithImpl<$Res>
|
|
||||||
implements $BoardResponseCopyWith<$Res> {
|
|
||||||
_$BoardResponseCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final BoardResponse _self;
|
|
||||||
final $Res Function(BoardResponse) _then;
|
|
||||||
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<BoardData>?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ErrorModel?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$ErrorModelCopyWith<$Res>? get error {
|
|
||||||
if (_self.error == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ErrorModelCopyWith<$Res>(_self.error!, (value) {
|
|
||||||
return _then(_self.copyWith(error: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [BoardResponse].
|
|
||||||
extension BoardResponsePatterns on BoardResponse {
|
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BoardResponse value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// Callbacks receives the raw object, upcasted.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case final Subclass2 value:
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BoardResponse value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse():
|
|
||||||
return $default(_that);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BoardResponse value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to an `orElse` callback.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool? success, String? message, List<BoardData>? data, ErrorModel? error)? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse() when $default != null:
|
|
||||||
return $default(_that.success,_that.message,_that.data,_that.error);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// As opposed to `map`, this offers destructuring.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case Subclass2(:final field2):
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool? success, String? message, List<BoardData>? data, ErrorModel? error) $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse():
|
|
||||||
return $default(_that.success,_that.message,_that.data,_that.error);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to returning `null`
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool? success, String? message, List<BoardData>? data, ErrorModel? error)? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardResponse() when $default != null:
|
|
||||||
return $default(_that.success,_that.message,_that.data,_that.error);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _BoardResponse implements BoardResponse {
|
|
||||||
const _BoardResponse({required this.success, required this.message, required final List<BoardData>? data, required this.error}): _data = data;
|
|
||||||
factory _BoardResponse.fromJson(Map<String, dynamic> json) => _$BoardResponseFromJson(json);
|
|
||||||
|
|
||||||
@override final bool? success;
|
|
||||||
@override final String? message;
|
|
||||||
final List<BoardData>? _data;
|
|
||||||
@override List<BoardData>? get data {
|
|
||||||
final value = _data;
|
|
||||||
if (value == null) return null;
|
|
||||||
if (_data is EqualUnmodifiableListView) return _data;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableListView(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override final ErrorModel? error;
|
|
||||||
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$BoardResponseCopyWith<_BoardResponse> get copyWith => __$BoardResponseCopyWithImpl<_BoardResponse>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$BoardResponseToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BoardResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&const DeepCollectionEquality().equals(other._data, _data)&&(identical(other.error, error) || other.error == error));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,success,message,const DeepCollectionEquality().hash(_data),error);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BoardResponse(success: $success, message: $message, data: $data, error: $error)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$BoardResponseCopyWith<$Res> implements $BoardResponseCopyWith<$Res> {
|
|
||||||
factory _$BoardResponseCopyWith(_BoardResponse value, $Res Function(_BoardResponse) _then) = __$BoardResponseCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
bool? success, String? message, List<BoardData>? data, ErrorModel? error
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
@override $ErrorModelCopyWith<$Res>? get error;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$BoardResponseCopyWithImpl<$Res>
|
|
||||||
implements _$BoardResponseCopyWith<$Res> {
|
|
||||||
__$BoardResponseCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _BoardResponse _self;
|
|
||||||
final $Res Function(_BoardResponse) _then;
|
|
||||||
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) {
|
|
||||||
return _then(_BoardResponse(
|
|
||||||
success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<BoardData>?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ErrorModel?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a copy of BoardResponse
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$ErrorModelCopyWith<$Res>? get error {
|
|
||||||
if (_self.error == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ErrorModelCopyWith<$Res>(_self.error!, (value) {
|
|
||||||
return _then(_self.copyWith(error: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$BoardData {
|
|
||||||
|
|
||||||
String? get id; String? get description; DateTime? get deadline; String? get assignedTo; bool? get isReady; String? get status;
|
|
||||||
/// Create a copy of BoardData
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$BoardDataCopyWith<BoardData> get copyWith => _$BoardDataCopyWithImpl<BoardData>(this as BoardData, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this BoardData to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BoardData&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.deadline, deadline) || other.deadline == deadline)&&(identical(other.assignedTo, assignedTo) || other.assignedTo == assignedTo)&&(identical(other.isReady, isReady) || other.isReady == isReady)&&(identical(other.status, status) || other.status == status));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,description,deadline,assignedTo,isReady,status);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BoardData(id: $id, description: $description, deadline: $deadline, assignedTo: $assignedTo, isReady: $isReady, status: $status)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $BoardDataCopyWith<$Res> {
|
|
||||||
factory $BoardDataCopyWith(BoardData value, $Res Function(BoardData) _then) = _$BoardDataCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$BoardDataCopyWithImpl<$Res>
|
|
||||||
implements $BoardDataCopyWith<$Res> {
|
|
||||||
_$BoardDataCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final BoardData _self;
|
|
||||||
final $Res Function(BoardData) _then;
|
|
||||||
|
|
||||||
/// Create a copy of BoardData
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? description = freezed,Object? deadline = freezed,Object? assignedTo = freezed,Object? isReady = freezed,Object? status = freezed,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,deadline: freezed == deadline ? _self.deadline : deadline // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,assignedTo: freezed == assignedTo ? _self.assignedTo : assignedTo // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,isReady: freezed == isReady ? _self.isReady : isReady // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [BoardData].
|
|
||||||
extension BoardDataPatterns on BoardData {
|
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _BoardData value)? $default,{required TResult orElse(),}){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// Callbacks receives the raw object, upcasted.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case final Subclass2 value:
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _BoardData value) $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData():
|
|
||||||
return $default(_that);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `map` that fallback to returning `null`.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case final Subclass value:
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _BoardData value)? $default,){
|
|
||||||
final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData() when $default != null:
|
|
||||||
return $default(_that);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to an `orElse` callback.
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return orElse();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status)? $default,{required TResult orElse(),}) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData() when $default != null:
|
|
||||||
return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _:
|
|
||||||
return orElse();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A `switch`-like method, using callbacks.
|
|
||||||
///
|
|
||||||
/// As opposed to `map`, this offers destructuring.
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case Subclass2(:final field2):
|
|
||||||
/// return ...;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status) $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData():
|
|
||||||
return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _:
|
|
||||||
throw StateError('Unexpected subclass');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A variant of `when` that fallback to returning `null`
|
|
||||||
///
|
|
||||||
/// It is equivalent to doing:
|
|
||||||
/// ```dart
|
|
||||||
/// switch (sealedClass) {
|
|
||||||
/// case Subclass(:final field):
|
|
||||||
/// return ...;
|
|
||||||
/// case _:
|
|
||||||
/// return null;
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status)? $default,) {final _that = this;
|
|
||||||
switch (_that) {
|
|
||||||
case _BoardData() when $default != null:
|
|
||||||
return $default(_that.id,_that.description,_that.deadline,_that.assignedTo,_that.isReady,_that.status);case _:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _BoardData implements BoardData {
|
|
||||||
const _BoardData({required this.id, required this.description, required this.deadline, required this.assignedTo, required this.isReady, required this.status});
|
|
||||||
factory _BoardData.fromJson(Map<String, dynamic> json) => _$BoardDataFromJson(json);
|
|
||||||
|
|
||||||
@override final String? id;
|
|
||||||
@override final String? description;
|
|
||||||
@override final DateTime? deadline;
|
|
||||||
@override final String? assignedTo;
|
|
||||||
@override final bool? isReady;
|
|
||||||
@override final String? status;
|
|
||||||
|
|
||||||
/// Create a copy of BoardData
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$BoardDataCopyWith<_BoardData> get copyWith => __$BoardDataCopyWithImpl<_BoardData>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$BoardDataToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _BoardData&&(identical(other.id, id) || other.id == id)&&(identical(other.description, description) || other.description == description)&&(identical(other.deadline, deadline) || other.deadline == deadline)&&(identical(other.assignedTo, assignedTo) || other.assignedTo == assignedTo)&&(identical(other.isReady, isReady) || other.isReady == isReady)&&(identical(other.status, status) || other.status == status));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,description,deadline,assignedTo,isReady,status);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'BoardData(id: $id, description: $description, deadline: $deadline, assignedTo: $assignedTo, isReady: $isReady, status: $status)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$BoardDataCopyWith<$Res> implements $BoardDataCopyWith<$Res> {
|
|
||||||
factory _$BoardDataCopyWith(_BoardData value, $Res Function(_BoardData) _then) = __$BoardDataCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
String? id, String? description, DateTime? deadline, String? assignedTo, bool? isReady, String? status
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$BoardDataCopyWithImpl<$Res>
|
|
||||||
implements _$BoardDataCopyWith<$Res> {
|
|
||||||
__$BoardDataCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _BoardData _self;
|
|
||||||
final $Res Function(_BoardData) _then;
|
|
||||||
|
|
||||||
/// Create a copy of BoardData
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? description = freezed,Object? deadline = freezed,Object? assignedTo = freezed,Object? isReady = freezed,Object? status = freezed,}) {
|
|
||||||
return _then(_BoardData(
|
|
||||||
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,deadline: freezed == deadline ? _self.deadline : deadline // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DateTime?,assignedTo: freezed == assignedTo ? _self.assignedTo : assignedTo // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,isReady: freezed == isReady ? _self.isReady : isReady // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// dart format on
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'board_response.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_BoardResponse _$BoardResponseFromJson(Map<String, dynamic> json) =>
|
|
||||||
_BoardResponse(
|
|
||||||
success: json['success'] as bool?,
|
|
||||||
message: json['message'] as String?,
|
|
||||||
data:
|
|
||||||
(json['data'] as List<dynamic>?)
|
|
||||||
?.map((e) => BoardData.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
error:
|
|
||||||
json['error'] == null
|
|
||||||
? null
|
|
||||||
: ErrorModel.fromJson(json['error'] as Map<String, dynamic>),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$BoardResponseToJson(_BoardResponse instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'success': instance.success,
|
|
||||||
'message': instance.message,
|
|
||||||
'data': instance.data,
|
|
||||||
'error': instance.error,
|
|
||||||
};
|
|
||||||
|
|
||||||
_BoardData _$BoardDataFromJson(Map<String, dynamic> json) => _BoardData(
|
|
||||||
id: json['id'] as String?,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
deadline:
|
|
||||||
json['deadline'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['deadline'] as String),
|
|
||||||
assignedTo: json['assignedTo'] as String?,
|
|
||||||
isReady: json['isReady'] as bool?,
|
|
||||||
status: json['status'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$BoardDataToJson(_BoardData instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'description': instance.description,
|
|
||||||
'deadline': instance.deadline?.toIso8601String(),
|
|
||||||
'assignedTo': instance.assignedTo,
|
|
||||||
'isReady': instance.isReady,
|
|
||||||
'status': instance.status,
|
|
||||||
};
|
|
||||||
@@ -13,6 +13,9 @@ abstract class Meta with _$Meta {
|
|||||||
required int? page,
|
required int? page,
|
||||||
required int? pages,
|
required int? pages,
|
||||||
required int? total,
|
required int? total,
|
||||||
|
@JsonKey(name: 'has_more') bool? hasMore,
|
||||||
|
@JsonKey(name: 'next_cursor') String? nextCursor,
|
||||||
|
@JsonKey(name: 'prev_cursor') String? prevCursor,
|
||||||
}) = _Meta;
|
}) = _Meta;
|
||||||
|
|
||||||
factory Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
|
factory Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$Meta {
|
mixin _$Meta {
|
||||||
|
|
||||||
int? get limit; int? get offset; int? get page; int? get pages; int? get total;
|
int? get limit; int? get offset; int? get page; int? get pages; int? get total;@JsonKey(name: 'has_more') bool? get hasMore;@JsonKey(name: 'next_cursor') String? get nextCursor;@JsonKey(name: 'prev_cursor') String? get prevCursor;
|
||||||
/// Create a copy of Meta
|
/// Create a copy of Meta
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +28,16 @@ $MetaCopyWith<Meta> get copyWith => _$MetaCopyWithImpl<Meta>(this as Meta, _$ide
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total)&&(identical(other.hasMore, hasMore) || other.hasMore == hasMore)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.prevCursor, prevCursor) || other.prevCursor == prevCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total);
|
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total,hasMore,nextCursor,prevCursor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total)';
|
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total, hasMore: $hasMore, nextCursor: $nextCursor, prevCursor: $prevCursor)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ abstract mixin class $MetaCopyWith<$Res> {
|
|||||||
factory $MetaCopyWith(Meta value, $Res Function(Meta) _then) = _$MetaCopyWithImpl;
|
factory $MetaCopyWith(Meta value, $Res Function(Meta) _then) = _$MetaCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int? limit, int? offset, int? page, int? pages, int? total
|
int? limit, int? offset, int? page, int? pages, int? total,@JsonKey(name: 'has_more') bool? hasMore,@JsonKey(name: 'next_cursor') String? nextCursor,@JsonKey(name: 'prev_cursor') String? prevCursor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,14 +65,17 @@ class _$MetaCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of Meta
|
/// Create a copy of Meta
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,Object? hasMore = freezed,Object? nextCursor = freezed,Object? prevCursor = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
|
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
|
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as int?,hasMore: freezed == hasMore ? _self.hasMore : hasMore // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,nextCursor: freezed == nextCursor ? _self.nextCursor : nextCursor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,prevCursor: freezed == prevCursor ? _self.prevCursor : prevCursor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +160,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Meta() when $default != null:
|
case _Meta() when $default != null:
|
||||||
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
|
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -178,10 +181,10 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Meta():
|
case _Meta():
|
||||||
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
|
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -198,10 +201,10 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _Meta() when $default != null:
|
case _Meta() when $default != null:
|
||||||
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
|
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -213,7 +216,7 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _Meta implements Meta {
|
class _Meta implements Meta {
|
||||||
const _Meta({required this.limit, required this.offset, required this.page, required this.pages, required this.total});
|
const _Meta({required this.limit, required this.offset, required this.page, required this.pages, required this.total, @JsonKey(name: 'has_more') this.hasMore, @JsonKey(name: 'next_cursor') this.nextCursor, @JsonKey(name: 'prev_cursor') this.prevCursor});
|
||||||
factory _Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
|
factory _Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
|
||||||
|
|
||||||
@override final int? limit;
|
@override final int? limit;
|
||||||
@@ -221,6 +224,9 @@ class _Meta implements Meta {
|
|||||||
@override final int? page;
|
@override final int? page;
|
||||||
@override final int? pages;
|
@override final int? pages;
|
||||||
@override final int? total;
|
@override final int? total;
|
||||||
|
@override@JsonKey(name: 'has_more') final bool? hasMore;
|
||||||
|
@override@JsonKey(name: 'next_cursor') final String? nextCursor;
|
||||||
|
@override@JsonKey(name: 'prev_cursor') final String? prevCursor;
|
||||||
|
|
||||||
/// Create a copy of Meta
|
/// Create a copy of Meta
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -235,16 +241,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total)&&(identical(other.hasMore, hasMore) || other.hasMore == hasMore)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.prevCursor, prevCursor) || other.prevCursor == prevCursor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total);
|
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total,hasMore,nextCursor,prevCursor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total)';
|
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total, hasMore: $hasMore, nextCursor: $nextCursor, prevCursor: $prevCursor)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -255,7 +261,7 @@ abstract mixin class _$MetaCopyWith<$Res> implements $MetaCopyWith<$Res> {
|
|||||||
factory _$MetaCopyWith(_Meta value, $Res Function(_Meta) _then) = __$MetaCopyWithImpl;
|
factory _$MetaCopyWith(_Meta value, $Res Function(_Meta) _then) = __$MetaCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
int? limit, int? offset, int? page, int? pages, int? total
|
int? limit, int? offset, int? page, int? pages, int? total,@JsonKey(name: 'has_more') bool? hasMore,@JsonKey(name: 'next_cursor') String? nextCursor,@JsonKey(name: 'prev_cursor') String? prevCursor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -272,14 +278,17 @@ class __$MetaCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of Meta
|
/// Create a copy of Meta
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,Object? hasMore = freezed,Object? nextCursor = freezed,Object? prevCursor = freezed,}) {
|
||||||
return _then(_Meta(
|
return _then(_Meta(
|
||||||
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
|
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
|
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
|
||||||
as int?,
|
as int?,hasMore: freezed == hasMore ? _self.hasMore : hasMore // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,nextCursor: freezed == nextCursor ? _self.nextCursor : nextCursor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,prevCursor: freezed == prevCursor ? _self.prevCursor : prevCursor // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ _Meta _$MetaFromJson(Map<String, dynamic> json) => _Meta(
|
|||||||
page: (json['page'] as num?)?.toInt(),
|
page: (json['page'] as num?)?.toInt(),
|
||||||
pages: (json['pages'] as num?)?.toInt(),
|
pages: (json['pages'] as num?)?.toInt(),
|
||||||
total: (json['total'] as num?)?.toInt(),
|
total: (json['total'] as num?)?.toInt(),
|
||||||
|
hasMore: json['has_more'] as bool?,
|
||||||
|
nextCursor: json['next_cursor'] as String?,
|
||||||
|
prevCursor: json['prev_cursor'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$MetaToJson(_Meta instance) => <String, dynamic>{
|
Map<String, dynamic> _$MetaToJson(_Meta instance) => <String, dynamic>{
|
||||||
@@ -20,4 +23,7 @@ Map<String, dynamic> _$MetaToJson(_Meta instance) => <String, dynamic>{
|
|||||||
'page': instance.page,
|
'page': instance.page,
|
||||||
'pages': instance.pages,
|
'pages': instance.pages,
|
||||||
'total': instance.total,
|
'total': instance.total,
|
||||||
|
'has_more': instance.hasMore,
|
||||||
|
'next_cursor': instance.nextCursor,
|
||||||
|
'prev_cursor': instance.prevCursor,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ignore_for_file: invalid_annotation_target
|
// ignore_for_file: invalid_annotation_target
|
||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:tm_app/core/utils/date_utils.dart';
|
||||||
import 'package:tm_app/data/services/model/organization/organization.dart';
|
import 'package:tm_app/data/services/model/organization/organization.dart';
|
||||||
|
|
||||||
part 'tender_data.freezed.dart';
|
part 'tender_data.freezed.dart';
|
||||||
@@ -15,7 +16,8 @@ abstract class TenderData with _$TenderData {
|
|||||||
@JsonKey(name: 'tender_id') required String? tenderId,
|
@JsonKey(name: 'tender_id') required String? tenderId,
|
||||||
@JsonKey(name: 'notice_publication_id')
|
@JsonKey(name: 'notice_publication_id')
|
||||||
required String? noticePublicationId,
|
required String? noticePublicationId,
|
||||||
@JsonKey(name: 'publication_date') required int? publicationDate,
|
@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson)
|
||||||
|
required int? publicationDate,
|
||||||
required String? title,
|
required String? title,
|
||||||
required String? description,
|
required String? description,
|
||||||
@JsonKey(name: 'procurement_type_code')
|
@JsonKey(name: 'procurement_type_code')
|
||||||
@@ -24,9 +26,12 @@ abstract class TenderData with _$TenderData {
|
|||||||
@JsonKey(name: 'estimated_value') required int? estimatedValue,
|
@JsonKey(name: 'estimated_value') required int? estimatedValue,
|
||||||
required String? currency,
|
required String? currency,
|
||||||
required String? duration,
|
required String? duration,
|
||||||
@JsonKey(name: 'tender_deadline') required int? tenderDeadline,
|
@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson)
|
||||||
@JsonKey(name: 'submission_deadline') required int? submissionDeadline,
|
required int? tenderDeadline,
|
||||||
@JsonKey(name: 'application_deadline') required int? applicationDeadline,
|
@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson)
|
||||||
|
required int? submissionDeadline,
|
||||||
|
@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson)
|
||||||
|
required int? applicationDeadline,
|
||||||
@JsonKey(name: 'submission_url') required String? submissionUrl,
|
@JsonKey(name: 'submission_url') required String? submissionUrl,
|
||||||
@JsonKey(name: 'country_code') required String? countryCode,
|
@JsonKey(name: 'country_code') required String? countryCode,
|
||||||
@JsonKey(name: 'duration_unit') required String? durationUnit,
|
@JsonKey(name: 'duration_unit') required String? durationUnit,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
|
|||||||
id: json['id'] as String?,
|
id: json['id'] as String?,
|
||||||
tenderId: json['tender_id'] as String?,
|
tenderId: json['tender_id'] as String?,
|
||||||
noticePublicationId: json['notice_publication_id'] as String?,
|
noticePublicationId: json['notice_publication_id'] as String?,
|
||||||
publicationDate: (json['publication_date'] as num?)?.toInt(),
|
publicationDate: unixTimestampFromJson(json['publication_date']),
|
||||||
title: json['title'] as String?,
|
title: json['title'] as String?,
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
procurementTypeCode: json['procurement_type_code'] as String?,
|
procurementTypeCode: json['procurement_type_code'] as String?,
|
||||||
@@ -20,9 +20,9 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
|
|||||||
estimatedValue: (json['estimated_value'] as num?)?.toInt(),
|
estimatedValue: (json['estimated_value'] as num?)?.toInt(),
|
||||||
currency: json['currency'] as String?,
|
currency: json['currency'] as String?,
|
||||||
duration: json['duration'] as String?,
|
duration: json['duration'] as String?,
|
||||||
tenderDeadline: (json['tender_deadline'] as num?)?.toInt(),
|
tenderDeadline: unixTimestampFromJson(json['tender_deadline']),
|
||||||
submissionDeadline: (json['submission_deadline'] as num?)?.toInt(),
|
submissionDeadline: unixTimestampFromJson(json['submission_deadline']),
|
||||||
applicationDeadline: (json['application_deadline'] as num?)?.toInt(),
|
applicationDeadline: unixTimestampFromJson(json['application_deadline']),
|
||||||
submissionUrl: json['submission_url'] as String?,
|
submissionUrl: json['submission_url'] as String?,
|
||||||
countryCode: json['country_code'] as String?,
|
countryCode: json['country_code'] as String?,
|
||||||
durationUnit: json['duration_unit'] as String?,
|
durationUnit: json['duration_unit'] as String?,
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class TendersService {
|
|||||||
|
|
||||||
Future<Result<TendersResponse>> getTenders({
|
Future<Result<TendersResponse>> getTenders({
|
||||||
required int limit,
|
required int limit,
|
||||||
required int offset,
|
int? offset,
|
||||||
|
String? cursor,
|
||||||
String? query,
|
String? query,
|
||||||
String? sortBy,
|
String? sortBy,
|
||||||
String? sortOrder,
|
String? sortOrder,
|
||||||
@@ -27,6 +28,7 @@ class TendersService {
|
|||||||
TendersApi.getTenders(
|
TendersApi.getTenders(
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
cursor: cursor,
|
||||||
query: query,
|
query: query,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
sortOrder: sortOrder,
|
sortOrder: sortOrder,
|
||||||
@@ -37,7 +39,6 @@ class TendersService {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
(json) => TendersResponse.fromJson(json),
|
(json) => TendersResponse.fromJson(json),
|
||||||
);
|
);
|
||||||
print(result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -2,6 +2,7 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -130,6 +131,9 @@ class _MyAppState extends State<MyApp> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Listen to theme changes
|
// Listen to theme changes
|
||||||
final themeProvider = context.watch<ThemeProvider>();
|
final themeProvider = context.watch<ThemeProvider>();
|
||||||
|
final pageTransition = kIsWeb
|
||||||
|
? const FadeUpwardsPageTransitionsBuilder()
|
||||||
|
: const CupertinoPageTransitionsBuilder();
|
||||||
|
|
||||||
return ToastificationWrapper(
|
return ToastificationWrapper(
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
@@ -147,10 +151,7 @@ class _MyAppState extends State<MyApp> {
|
|||||||
pageTransitionsTheme: PageTransitionsTheme(
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
builders: {
|
builders: {
|
||||||
for (final platform in TargetPlatform.values)
|
for (final platform in TargetPlatform.values)
|
||||||
platform:
|
platform: pageTransition,
|
||||||
kIsWeb
|
|
||||||
? const FadeUpwardsPageTransitionsBuilder()
|
|
||||||
: const CupertinoPageTransitionsBuilder(),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
brightness:
|
brightness:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../core/enums/task_status.dart';
|
|
||||||
import '../core/utils/result.dart';
|
import '../core/utils/result.dart';
|
||||||
import '../data/repositories/board_repositories.dart';
|
import '../data/repositories/board_repositories.dart';
|
||||||
|
import '../data/services/board_service.dart';
|
||||||
import '../data/services/model/board_response/board_response.dart';
|
import '../data/services/model/board_response/board_response.dart';
|
||||||
|
|
||||||
class BoardViewModel with ChangeNotifier {
|
class BoardViewModel with ChangeNotifier {
|
||||||
@@ -12,23 +12,27 @@ class BoardViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
List<BoardData> _boardData = [];
|
Board? _board;
|
||||||
|
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
String? get errorMessage => _errorMessage;
|
String? get errorMessage => _errorMessage;
|
||||||
List<BoardData> get boardData => _boardData;
|
Board? get board => _board;
|
||||||
bool get hasData => _boardData.isNotEmpty;
|
List<BoardColumn> get columns => _board?.columns ?? const [];
|
||||||
|
bool get hasData => _board != null && columns.isNotEmpty;
|
||||||
|
|
||||||
Future<void> getBoard() async {
|
Future<void> getBoard() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final result = await _boardRepository.getBoard();
|
final result = await _boardRepository.getBoard();
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<BoardResponse>():
|
case Ok<BoardResponse>():
|
||||||
// تبدیل به لیست قابل تغییر
|
_board = result.value.data?.board;
|
||||||
_boardData = List.from(result.value.data ?? []);
|
if (_board == null) {
|
||||||
|
_errorMessage = result.value.error?.message ?? 'No board data';
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Error<BoardResponse>():
|
case Error<BoardResponse>():
|
||||||
_errorMessage = result.error.toString();
|
_errorMessage = result.error.toString();
|
||||||
@@ -38,40 +42,86 @@ class BoardViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update task status
|
/// Look up the column a given card currently lives in.
|
||||||
void updateTaskStatus(String taskId, String newStatus) {
|
BoardColumn? columnForCard(String cardId) {
|
||||||
final taskIndex = _boardData.indexWhere((task) => task.id == taskId);
|
for (final column in columns) {
|
||||||
if (taskIndex != -1) {
|
if (column.cards.any((c) => c.id == cardId)) return column;
|
||||||
_boardData[taskIndex] = _boardData[taskIndex].copyWith(status: newStatus);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValidMove(TaskStatus currentStatus, TaskStatus targetStatus) {
|
/// Move a card to [targetColumnId] at the end of that column.
|
||||||
if (currentStatus == TaskStatus.toDo) {
|
/// Optimistically updates local state, then calls the API; rolls back on
|
||||||
return targetStatus == TaskStatus.inProgress;
|
/// failure.
|
||||||
} else if (currentStatus == TaskStatus.inProgress) {
|
Future<Result<MoveCardResponse>> moveCard({
|
||||||
return targetStatus == TaskStatus.toDo || targetStatus == TaskStatus.done;
|
required BoardCard card,
|
||||||
} else if (currentStatus == TaskStatus.done) {
|
required BoardColumn targetColumn,
|
||||||
return targetStatus == TaskStatus.inProgress;
|
}) async {
|
||||||
}
|
final cardId = card.id;
|
||||||
return false;
|
final targetColumnId = targetColumn.id;
|
||||||
}
|
if (cardId == null || targetColumnId == null || _board == null) {
|
||||||
|
return Result.error(Exception('Invalid card or column'));
|
||||||
List<BoardData> getTasksByStatus(TaskStatus status) {
|
|
||||||
return boardData.where((task) => task.status == status.value).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateTaskStatus(BoardData task, TaskStatus newStatus) {
|
|
||||||
if (task.status == newStatus.value) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final oldStatus = task.status;
|
final sourceColumn = columnForCard(cardId);
|
||||||
|
if (sourceColumn == null) {
|
||||||
|
return Result.error(Exception('Card not found in any column'));
|
||||||
|
}
|
||||||
|
if (sourceColumn.id == targetColumnId) {
|
||||||
|
return Result.ok(MoveCardResponse(success: true, message: 'No change'));
|
||||||
|
}
|
||||||
|
|
||||||
// update task status in ViewModel
|
final previousColumns = _board!.columns;
|
||||||
updateTaskStatus(task.id!, newStatus.value);
|
final newOrder = targetColumn.cards.length;
|
||||||
|
|
||||||
// _showStatusChangeMessage(context, task, oldStatus, newStatus);
|
_board = _board!.copyWith(
|
||||||
|
columns:
|
||||||
|
previousColumns.map((column) {
|
||||||
|
if (column.id == sourceColumn.id) {
|
||||||
|
final remaining =
|
||||||
|
column.cards.where((c) => c.id != cardId).toList();
|
||||||
|
return column.copyWith(
|
||||||
|
cards: remaining,
|
||||||
|
cardCount: remaining.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.id == targetColumnId) {
|
||||||
|
final updated = List<BoardCard>.from(column.cards)
|
||||||
|
..add(card.copyWith(columnId: targetColumnId, order: newOrder));
|
||||||
|
return column.copyWith(
|
||||||
|
cards: updated,
|
||||||
|
cardCount: updated.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
final result = await _boardRepository.moveCard(
|
||||||
|
cardId: cardId,
|
||||||
|
columnId: targetColumnId,
|
||||||
|
newOrder: newOrder,
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case Ok<MoveCardResponse>():
|
||||||
|
// HTTP 200 alone isn't success: the API can return
|
||||||
|
// {"success": false, "message": ...} (e.g. column over-limit,
|
||||||
|
// permission denied). Only keep the optimistic move when the body
|
||||||
|
// confirms success; otherwise roll back and surface the message.
|
||||||
|
if (result.value.success == true) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
_board = _board!.copyWith(columns: previousColumns);
|
||||||
|
notifyListeners();
|
||||||
|
return Result.error(
|
||||||
|
Exception(result.value.message ?? 'Failed to move card'),
|
||||||
|
);
|
||||||
|
case Error<MoveCardResponse>():
|
||||||
|
_board = _board!.copyWith(columns: previousColumns);
|
||||||
|
notifyListeners();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tm_app/core/utils/result.dart';
|
import 'package:tm_app/core/utils/result.dart';
|
||||||
import 'package:tm_app/data/services/model/feedback_stats/feedback_stats.dart';
|
|
||||||
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
|
import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_approvals_stats_response/tender_approvals_stats_response.dart';
|
import 'package:tm_app/data/services/model/tender_approvals_stats_response/tender_approvals_stats_response.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
||||||
|
|
||||||
import '../data/repositories/home_repository.dart';
|
import '../data/repositories/home_repository.dart';
|
||||||
import '../data/services/model/feedback_stats_response/feedback_stat_response.dart';
|
|
||||||
import '../data/services/model/home/home_response/home_response_model.dart';
|
import '../data/services/model/home/home_response/home_response_model.dart';
|
||||||
import '../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
import '../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||||
import '../data/services/notification_state_service.dart';
|
import '../data/services/notification_state_service.dart';
|
||||||
@@ -46,7 +44,7 @@ class HomeViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
RecommendedTendersResponse? _recommendedTendersResponse;
|
RecommendedTendersResponse? _recommendedTendersResponse;
|
||||||
TenderApprovalsStatsResponse? tenderApprovalsStateResponse;
|
TenderApprovalsStatsResponse? tenderApprovalsStateResponse;
|
||||||
FeedbackStats? feedbackStats;
|
int userLikedTendersCount = 0;
|
||||||
final List<TenderData> _tenders = [];
|
final List<TenderData> _tenders = [];
|
||||||
final int _pageSize = 10;
|
final int _pageSize = 10;
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
@@ -65,11 +63,19 @@ class HomeViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
void init() async {
|
void init() async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
userLikedTendersCount = 0;
|
||||||
|
_tenders.clear();
|
||||||
|
_currentPage = 0;
|
||||||
|
_hasMore = true;
|
||||||
|
_isRecommendedMode = false;
|
||||||
|
data = null;
|
||||||
|
tenderApprovalsStateResponse = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
await getApprovedStates();
|
await getApprovedStates();
|
||||||
await getYourTenders(reset: true);
|
await getYourTenders(reset: true);
|
||||||
await getFeedbackStats();
|
await loadUserLikedTendersCount();
|
||||||
|
|
||||||
if (_errorMessage != null) {
|
if (_errorMessage != null) {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
@@ -192,15 +198,15 @@ class HomeViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getFeedbackStats() async {
|
Future<void> loadUserLikedTendersCount() async {
|
||||||
final result = await _homeRepository.getFeedbackStats();
|
final result = await _homeRepository.getUserLikedTendersCount();
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<FeedbackStatResponse>():
|
case Ok<int>():
|
||||||
feedbackStats = result.value.data;
|
userLikedTendersCount = result.value;
|
||||||
break;
|
break;
|
||||||
case Error<FeedbackStatResponse>():
|
case Error<int>():
|
||||||
_errorMessage = result.error.toString();
|
userLikedTendersCount = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:tm_app/core/utils/logger.dart';
|
|||||||
import 'package:tm_app/data/repositories/tenders_repository.dart';
|
import 'package:tm_app/data/repositories/tenders_repository.dart';
|
||||||
import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart';
|
import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart';
|
||||||
import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart';
|
import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart';
|
||||||
|
import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
||||||
import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart';
|
import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart';
|
||||||
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||||
|
|
||||||
@@ -36,15 +37,44 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
List<FeedbackData> feedbacks = [];
|
List<FeedbackData> feedbacks = [];
|
||||||
|
|
||||||
int currentPage = 1;
|
|
||||||
int _currentOffset = 0;
|
|
||||||
int _totalPages = 0;
|
|
||||||
int get totalPages => _totalPages;
|
|
||||||
static const int _pageSize = 10;
|
static const int _pageSize = 10;
|
||||||
bool _hasMoreData = true;
|
|
||||||
|
|
||||||
|
// --- Offset pagination state ---
|
||||||
|
int _currentOffset = 0;
|
||||||
|
int _lastValidPages = 1;
|
||||||
|
int _lastValidLimit = _pageSize;
|
||||||
|
|
||||||
|
bool _hasMoreData = true;
|
||||||
bool get hasMoreData => _hasMoreData;
|
bool get hasMoreData => _hasMoreData;
|
||||||
|
|
||||||
|
/// 0-based current page index.
|
||||||
|
int get currentPageIndex {
|
||||||
|
final meta = _tendersResponse?.meta;
|
||||||
|
final limit = meta?.limit ?? _lastValidLimit;
|
||||||
|
|
||||||
|
if (limit > 0) {
|
||||||
|
return _currentOffset ~/ limit;
|
||||||
|
}
|
||||||
|
if (meta?.offset != null && limit > 0) {
|
||||||
|
return meta!.offset! ~/ limit;
|
||||||
|
}
|
||||||
|
if (meta?.page != null) {
|
||||||
|
return meta!.page! - 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 1-based current page (for display).
|
||||||
|
int get currentPage => currentPageIndex + 1;
|
||||||
|
|
||||||
|
int get totalPages {
|
||||||
|
final pages = _tendersResponse?.meta?.pages;
|
||||||
|
if (pages != null && pages > 0) {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
return _lastValidPages;
|
||||||
|
}
|
||||||
|
|
||||||
// Filter States
|
// Filter States
|
||||||
DateTimeRange? selectedDateRange;
|
DateTimeRange? selectedDateRange;
|
||||||
String selectedStatus = 'All';
|
String selectedStatus = 'All';
|
||||||
@@ -80,15 +110,12 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setStatus(String status) {
|
void setStatus(String status) {
|
||||||
// Clear data when status changes
|
|
||||||
_clearAllData();
|
_clearAllData();
|
||||||
selectedStatus = status;
|
selectedStatus = status;
|
||||||
currentPage = 1;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSort(String sort) {
|
void setSort(String sort) {
|
||||||
// Clear data when status changes
|
|
||||||
_clearAllData();
|
_clearAllData();
|
||||||
selectedSort = sort;
|
selectedSort = sort;
|
||||||
if (sort == TendersStrings.minimumTime) {
|
if (sort == TendersStrings.minimumTime) {
|
||||||
@@ -98,16 +125,13 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
sortBy = 'submission_deadline';
|
sortBy = 'submission_deadline';
|
||||||
sortOrder = 'desc';
|
sortOrder = 'desc';
|
||||||
}
|
}
|
||||||
currentPage = 1;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearAllData() {
|
void _clearAllData() {
|
||||||
_tendersResponse = null;
|
_tendersResponse = null;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
_currentOffset = 0;
|
_resetPagination();
|
||||||
_hasMoreData = true;
|
|
||||||
currentPage = 1;
|
|
||||||
sortBy = '';
|
sortBy = '';
|
||||||
sortOrder = '';
|
sortOrder = '';
|
||||||
selectedSort = '';
|
selectedSort = '';
|
||||||
@@ -116,6 +140,11 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _resetPagination() {
|
||||||
|
_currentOffset = 0;
|
||||||
|
_hasMoreData = true;
|
||||||
|
}
|
||||||
|
|
||||||
void init(BuildContext context) async {
|
void init(BuildContext context) async {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -151,10 +180,7 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getTendersMobile({String? query}) async {
|
Future<void> getTendersMobile({String? query}) async {
|
||||||
// Clear all data before fetching new tenders
|
_resetPagination();
|
||||||
|
|
||||||
_currentOffset = 0;
|
|
||||||
_hasMoreData = true;
|
|
||||||
_searchQuery = query;
|
_searchQuery = query;
|
||||||
|
|
||||||
final result = await _tendersRepository.getTenders(
|
final result = await _tendersRepository.getTenders(
|
||||||
@@ -164,28 +190,7 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
sortBy: '',
|
sortBy: '',
|
||||||
sortOrder: '',
|
sortOrder: '',
|
||||||
);
|
);
|
||||||
switch (result) {
|
_handleListResult(result, fetchAllFeedbacks: false);
|
||||||
case Ok<TendersResponse>():
|
|
||||||
_tendersResponse = result.value;
|
|
||||||
// Check if we have more data
|
|
||||||
if (_tendersResponse?.data?.tenders != null) {
|
|
||||||
_hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize;
|
|
||||||
}
|
|
||||||
if (_tendersResponse?.data?.tenders?.isNotEmpty == true) {
|
|
||||||
// Fetch feedback and approvals for the first tender only
|
|
||||||
getTenderFeedback(_tendersResponse!.data!.tenders!.first.id!);
|
|
||||||
}
|
|
||||||
if (_tendersResponse?.meta?.pages != null) {
|
|
||||||
_totalPages = _tendersResponse!.meta!.pages!;
|
|
||||||
} else {
|
|
||||||
_totalPages = 1;
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
break;
|
|
||||||
case Error<TendersResponse>():
|
|
||||||
_errorMessage = result.error.toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
@@ -199,11 +204,9 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
String? publicationDateFrom,
|
String? publicationDateFrom,
|
||||||
String? publicationDateTo,
|
String? publicationDateTo,
|
||||||
}) async {
|
}) async {
|
||||||
// Clear all data before fetching new tenders
|
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_currentOffset = 0;
|
_resetPagination();
|
||||||
_hasMoreData = true;
|
|
||||||
_searchQuery = query;
|
_searchQuery = query;
|
||||||
|
|
||||||
final result = await _tendersRepository.getTenders(
|
final result = await _tendersRepository.getTenders(
|
||||||
@@ -215,35 +218,55 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
publicationDateFrom: publicationDateFrom ?? '',
|
publicationDateFrom: publicationDateFrom ?? '',
|
||||||
publicationDateTo: publicationDateTo ?? '',
|
publicationDateTo: publicationDateTo ?? '',
|
||||||
);
|
);
|
||||||
switch (result) {
|
_handleListResult(result, fetchAllFeedbacks: true);
|
||||||
case Ok<TendersResponse>():
|
|
||||||
_tendersResponse = result.value;
|
|
||||||
// Check if we have more data
|
|
||||||
if (_tendersResponse?.data?.tenders != null) {
|
|
||||||
_hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize;
|
|
||||||
}
|
|
||||||
if (_tendersResponse?.data?.tenders?.isNotEmpty == true) {
|
|
||||||
for (var tender in _tendersResponse!.data!.tenders!) {
|
|
||||||
getTenderFeedback(tender.id!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_tendersResponse?.meta?.pages != null) {
|
|
||||||
_totalPages = _tendersResponse!.meta!.pages!;
|
|
||||||
} else {
|
|
||||||
_totalPages = 1;
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
break;
|
|
||||||
case Error<TendersResponse>():
|
|
||||||
_errorMessage = result.error.toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleListResult(
|
||||||
|
Result<TendersResponse> result, {
|
||||||
|
required bool fetchAllFeedbacks,
|
||||||
|
}) {
|
||||||
|
switch (result) {
|
||||||
|
case Ok<TendersResponse>():
|
||||||
|
_tendersResponse = result.value;
|
||||||
|
final meta = _tendersResponse?.meta;
|
||||||
|
if (meta?.pages != null && meta!.pages! > 0) {
|
||||||
|
_lastValidPages = meta.pages!;
|
||||||
|
}
|
||||||
|
if (meta?.limit != null && meta!.limit! > 0) {
|
||||||
|
_lastValidLimit = meta.limit!;
|
||||||
|
}
|
||||||
|
if (meta?.offset != null) {
|
||||||
|
_currentOffset = meta!.offset!;
|
||||||
|
}
|
||||||
|
final tenders = _tendersResponse?.data?.tenders ?? [];
|
||||||
|
_hasMoreData =
|
||||||
|
meta?.hasMore ?? (tenders.length >= _pageSize);
|
||||||
|
if (tenders.isNotEmpty) {
|
||||||
|
if (fetchAllFeedbacks) {
|
||||||
|
for (final tender in tenders) {
|
||||||
|
if (tender.id != null) {
|
||||||
|
getTenderFeedback(tender.id!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getTenderFeedback(tenders.first.id!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Error<TendersResponse>():
|
||||||
|
_handleListError(result.error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleListError(Exception error) {
|
||||||
|
_errorMessage = error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadMoreTenders() async {
|
Future<void> loadMoreTenders() async {
|
||||||
if (_isLoadingMore || !_hasMoreData) {
|
if (_isLoadingMore || !_hasMoreData) {
|
||||||
return;
|
return;
|
||||||
@@ -253,7 +276,10 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
_currentOffset += _pageSize;
|
final meta = _tendersResponse?.meta;
|
||||||
|
final limit = meta?.limit ?? _pageSize;
|
||||||
|
_currentOffset += limit;
|
||||||
|
|
||||||
final result = await _tendersRepository.getTenders(
|
final result = await _tendersRepository.getTenders(
|
||||||
limit: _pageSize,
|
limit: _pageSize,
|
||||||
offset: _currentOffset,
|
offset: _currentOffset,
|
||||||
@@ -264,30 +290,31 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<TendersResponse>():
|
case Ok<TendersResponse>():
|
||||||
if (result.value.data?.tenders != null &&
|
final newTenders = result.value.data?.tenders ?? <TenderData>[];
|
||||||
result.value.data!.tenders!.isNotEmpty) {
|
if (newTenders.isNotEmpty) {
|
||||||
// Create a new modifiable list by combining existing and new tenders
|
final existingTenders =
|
||||||
if (_tendersResponse?.data?.tenders != null) {
|
_tendersResponse?.data?.tenders ?? <TenderData>[];
|
||||||
final existingTenders = _tendersResponse!.data!.tenders!;
|
final combined = <TenderData>[...existingTenders, ...newTenders];
|
||||||
final newTenders = result.value.data!.tenders!;
|
_tendersResponse = result.value.copyWith(
|
||||||
final combinedTenders = [...existingTenders, ...newTenders];
|
data: result.value.data?.copyWith(tenders: combined),
|
||||||
|
);
|
||||||
// Create a new TendersData with the combined list
|
final newMeta = result.value.meta;
|
||||||
_tendersResponse = _tendersResponse!.copyWith(
|
if (newMeta?.pages != null && newMeta!.pages! > 0) {
|
||||||
data: _tendersResponse!.data!.copyWith(tenders: combinedTenders),
|
_lastValidPages = newMeta.pages!;
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_tendersResponse = result.value;
|
|
||||||
}
|
}
|
||||||
// Check if we have more data
|
if (newMeta?.limit != null && newMeta!.limit! > 0) {
|
||||||
_hasMoreData = result.value.data!.tenders!.length >= _pageSize;
|
_lastValidLimit = newMeta.limit!;
|
||||||
|
}
|
||||||
|
_hasMoreData =
|
||||||
|
newMeta?.hasMore ?? (newTenders.length >= _pageSize);
|
||||||
} else {
|
} else {
|
||||||
_hasMoreData = false;
|
_hasMoreData = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Error<TendersResponse>():
|
case Error<TendersResponse>():
|
||||||
_errorMessage = result.error.toString();
|
final limit = meta?.limit ?? _pageSize;
|
||||||
_currentOffset -= _pageSize; // Revert offset on error
|
_currentOffset = (_currentOffset - limit).clamp(0, 1 << 30);
|
||||||
|
_handleListError(result.error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +325,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getTenderFeedback(String tenderId) async {
|
Future<void> getTenderFeedback(String tenderId) async {
|
||||||
// _isLoading = true;
|
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@@ -308,7 +334,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
switch (result) {
|
switch (result) {
|
||||||
case Ok<FeedbackResponse>():
|
case Ok<FeedbackResponse>():
|
||||||
_feedbackData = result.value.data;
|
_feedbackData = result.value.data;
|
||||||
// Add or update _feedbackData in feedbacks list by tender id
|
|
||||||
if (_feedbackData != null) {
|
if (_feedbackData != null) {
|
||||||
final index = feedbacks.indexWhere(
|
final index = feedbacks.indexWhere(
|
||||||
(f) => f.tenderId == _feedbackData!.tenderId,
|
(f) => f.tenderId == _feedbackData!.tenderId,
|
||||||
@@ -331,7 +356,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
_errorMessage = result.error.toString();
|
_errorMessage = result.error.toString();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// _isLoading = false;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -343,38 +367,32 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
}) async {
|
}) async {
|
||||||
_feedbackErrorMessage = null;
|
_feedbackErrorMessage = null;
|
||||||
|
|
||||||
// Get current feedback for this tender
|
|
||||||
final existingFeedback = getFeedbackForTender(tenderId);
|
final existingFeedback = getFeedbackForTender(tenderId);
|
||||||
final existingFeedbackIndex = feedbacks.indexWhere(
|
final existingFeedbackIndex = feedbacks.indexWhere(
|
||||||
(f) => f.tenderId == tenderId,
|
(f) => f.tenderId == tenderId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create optimistic feedback data
|
|
||||||
FeedbackData? optimisticFeedback;
|
FeedbackData? optimisticFeedback;
|
||||||
|
|
||||||
if (existingFeedback == null) {
|
if (existingFeedback == null) {
|
||||||
// No existing feedback - create new optimistic feedback
|
|
||||||
optimisticFeedback = FeedbackData(
|
optimisticFeedback = FeedbackData(
|
||||||
companyId: null, // Will be filled by server
|
companyId: null,
|
||||||
createdAt: DateTime.now().millisecondsSinceEpoch,
|
createdAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
customerId: null, // Will be filled by server
|
customerId: null,
|
||||||
feedbackType: feedbackType,
|
feedbackType: feedbackType,
|
||||||
id: null, // Will be filled by server
|
id: null,
|
||||||
tenderId: tenderId,
|
tenderId: tenderId,
|
||||||
tender: null,
|
tender: null,
|
||||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
);
|
);
|
||||||
feedbacks.add(optimisticFeedback);
|
feedbacks.add(optimisticFeedback);
|
||||||
} else {
|
} else {
|
||||||
// Existing feedback - update optimistically
|
|
||||||
if (existingFeedback.feedbackType == feedbackType) {
|
if (existingFeedback.feedbackType == feedbackType) {
|
||||||
// Same feedback type - remove feedback (toggle off)
|
|
||||||
if (existingFeedbackIndex != -1) {
|
if (existingFeedbackIndex != -1) {
|
||||||
feedbacks.removeAt(existingFeedbackIndex);
|
feedbacks.removeAt(existingFeedbackIndex);
|
||||||
}
|
}
|
||||||
optimisticFeedback = null;
|
optimisticFeedback = null;
|
||||||
} else {
|
} else {
|
||||||
// Different feedback type - update to new type
|
|
||||||
optimisticFeedback = existingFeedback.copyWith(
|
optimisticFeedback = existingFeedback.copyWith(
|
||||||
feedbackType: feedbackType,
|
feedbackType: feedbackType,
|
||||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
@@ -385,10 +403,8 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify listeners immediately for optimistic update
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
// Call API
|
|
||||||
final result = await _tendersRepository.toggleFeedback(
|
final result = await _tendersRepository.toggleFeedback(
|
||||||
tenderId: tenderId,
|
tenderId: tenderId,
|
||||||
feedbackType: feedbackType,
|
feedbackType: feedbackType,
|
||||||
@@ -398,7 +414,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
case Ok<FeedbackResponse>():
|
case Ok<FeedbackResponse>():
|
||||||
_feedbackData = result.value.data;
|
_feedbackData = result.value.data;
|
||||||
if (_feedbackData != null) {
|
if (_feedbackData != null) {
|
||||||
// Update with complete data from server
|
|
||||||
final index = feedbacks.indexWhere(
|
final index = feedbacks.indexWhere(
|
||||||
(f) => f.tender == _feedbackData!.tender,
|
(f) => f.tender == _feedbackData!.tender,
|
||||||
);
|
);
|
||||||
@@ -408,17 +423,13 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
feedbacks.add(_feedbackData!);
|
feedbacks.add(_feedbackData!);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Server returned null data - remove feedback
|
|
||||||
feedbacks.removeWhere((f) => f.tenderId == tenderId);
|
feedbacks.removeWhere((f) => f.tenderId == tenderId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Error<FeedbackResponse>():
|
case Error<FeedbackResponse>():
|
||||||
// Revert optimistic update on error
|
|
||||||
if (existingFeedback == null) {
|
if (existingFeedback == null) {
|
||||||
// Remove the optimistic feedback we added
|
|
||||||
feedbacks.removeWhere((f) => f.tenderId == tenderId);
|
feedbacks.removeWhere((f) => f.tenderId == tenderId);
|
||||||
} else {
|
} else {
|
||||||
// Restore the original feedback - find current index and update, or add if not found
|
|
||||||
final currentIndex = feedbacks.indexWhere(
|
final currentIndex = feedbacks.indexWhere(
|
||||||
(f) => f.tenderId == tenderId,
|
(f) => f.tenderId == tenderId,
|
||||||
);
|
);
|
||||||
@@ -437,7 +448,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear all feedbacks
|
|
||||||
void clearFeedbacks() {
|
void clearFeedbacks() {
|
||||||
feedbacks.clear();
|
feedbacks.clear();
|
||||||
_feedbackData = null;
|
_feedbackData = null;
|
||||||
@@ -445,13 +455,10 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear all tenders
|
|
||||||
void clearTenders() {
|
void clearTenders() {
|
||||||
_tendersResponse = null;
|
_tendersResponse = null;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
_currentOffset = 0;
|
_resetPagination();
|
||||||
_hasMoreData = true;
|
|
||||||
currentPage = 1;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +471,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search tenders with query
|
|
||||||
Future<void> searchTenders(String query) async {
|
Future<void> searchTenders(String query) async {
|
||||||
await getTendersDesktop(
|
await getTendersDesktop(
|
||||||
query: query.trim(),
|
query: query.trim(),
|
||||||
@@ -475,7 +481,6 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear all data (tenders, feedbacks, and approvals)
|
|
||||||
void clearAll() {
|
void clearAll() {
|
||||||
clearTenders();
|
clearTenders();
|
||||||
clearFeedbacks();
|
clearFeedbacks();
|
||||||
@@ -488,17 +493,32 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void jumpToPage(int page) async {
|
/// [selected] is the absolute 0-based target page index.
|
||||||
final meta = tendersResponse?.meta;
|
Future<void> handlePaginationChange(int selected) async {
|
||||||
if (meta?.pages == null) {
|
if (selected < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (page < 1 || page > meta!.pages!) {
|
final maxPage = totalPages - 1;
|
||||||
|
if (maxPage >= 0 && selected > maxPage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPage = page;
|
if (selected == currentPageIndex) {
|
||||||
_currentOffset = (page - 1) * _pageSize;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _goToOffsetPage(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _goToOffsetPage(int pageIndex) async {
|
||||||
|
final limit = _tendersResponse?.meta?.limit ?? _pageSize;
|
||||||
|
_currentOffset = pageIndex * limit;
|
||||||
|
await _fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchPage() async {
|
||||||
|
_isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
final result = await _tendersRepository.getTenders(
|
final result = await _tendersRepository.getTenders(
|
||||||
limit: _pageSize,
|
limit: _pageSize,
|
||||||
@@ -506,17 +526,20 @@ class TendersViewModel with ChangeNotifier {
|
|||||||
query: _searchQuery,
|
query: _searchQuery,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
sortOrder: sortOrder,
|
sortOrder: sortOrder,
|
||||||
|
publicationDateFrom: startDateUnix,
|
||||||
|
publicationDateTo: endDateUnix,
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (result) {
|
_handleListResult(result, fetchAllFeedbacks: true);
|
||||||
case Ok<TendersResponse>():
|
_isLoading = false;
|
||||||
_tendersResponse = result.value;
|
notifyListeners();
|
||||||
break;
|
_errorMessage = null;
|
||||||
case Error<TendersResponse>():
|
|
||||||
_errorMessage = result.error.toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Kept for backwards compatibility with existing callers.
|
||||||
|
/// Accepts a 1-based page number.
|
||||||
|
void jumpToPage(int page) {
|
||||||
|
handlePaginationChange(page - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/extensions/board_data_extension.dart';
|
|
||||||
import 'package:tm_app/core/theme/colors.dart';
|
import 'package:tm_app/core/theme/colors.dart';
|
||||||
|
import 'package:tm_app/core/utils/date_utils.dart';
|
||||||
|
import 'package:tm_app/core/utils/result.dart';
|
||||||
import 'package:tm_app/core/utils/size_config.dart';
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
|
import 'package:tm_app/data/services/board_service.dart';
|
||||||
import 'package:tm_app/data/services/model/board_response/board_response.dart';
|
import 'package:tm_app/data/services/model/board_response/board_response.dart';
|
||||||
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
||||||
|
|
||||||
import '../../../core/constants/assets.dart';
|
import '../../../core/constants/assets.dart';
|
||||||
import '../../../core/enums/task_status.dart';
|
|
||||||
import '../../../core/utils/app_toast.dart';
|
import '../../../core/utils/app_toast.dart';
|
||||||
import '../../../view_models/board_view_model.dart';
|
import '../../../view_models/board_view_model.dart';
|
||||||
import '../strings/board_strings.dart';
|
import '../strings/board_strings.dart';
|
||||||
@@ -32,38 +33,27 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateTaskStatus(BoardData task, TaskStatus newStatus) {
|
Future<void> _onCardDropped(BoardCard card, BoardColumn target) async {
|
||||||
if (task.statusEnum == newStatus) {
|
final source = _boardViewModel.columnForCard(card.id ?? '');
|
||||||
return;
|
if (source == null || source.id == target.id) return;
|
||||||
}
|
|
||||||
|
|
||||||
final oldStatus = task.statusEnum;
|
final result = await _boardViewModel.moveCard(
|
||||||
|
card: card,
|
||||||
// update task status in ViewModel
|
targetColumn: target,
|
||||||
_boardViewModel.updateTaskStatus(task.id!, newStatus.value);
|
|
||||||
|
|
||||||
_showStatusChangeMessage(context, task, oldStatus, newStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showStatusChangeMessage(
|
|
||||||
BuildContext context,
|
|
||||||
BoardData task,
|
|
||||||
TaskStatus oldStatus,
|
|
||||||
TaskStatus newStatus,
|
|
||||||
) {
|
|
||||||
final statusNames = {
|
|
||||||
TaskStatus.toDo: BoardStrings.toDo,
|
|
||||||
TaskStatus.inProgress: BoardStrings.inProgress,
|
|
||||||
TaskStatus.done: BoardStrings.done,
|
|
||||||
};
|
|
||||||
|
|
||||||
AppToast.success(
|
|
||||||
context,
|
|
||||||
BoardStrings.taskMovedMessage(
|
|
||||||
statusNames[oldStatus]!,
|
|
||||||
statusNames[newStatus]!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result is Ok<MoveCardResponse>) {
|
||||||
|
AppToast.success(
|
||||||
|
context,
|
||||||
|
BoardStrings.cardMovedMessage(
|
||||||
|
source.name ?? '',
|
||||||
|
target.name ?? '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (result is Error<MoveCardResponse>) {
|
||||||
|
AppToast.error(context, BoardStrings.moveFailed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -73,9 +63,7 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
backgroundColor: AppColors.backgroundColor,
|
backgroundColor: AppColors.backgroundColor,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
const DesktopNavigationWidget(
|
const DesktopNavigationWidget(currentIndex: 2),
|
||||||
currentIndex: 2, // Tenders index
|
|
||||||
),
|
|
||||||
Consumer<BoardViewModel>(
|
Consumer<BoardViewModel>(
|
||||||
builder: (context, viewModel, child) {
|
builder: (context, viewModel, child) {
|
||||||
if (viewModel.isLoading) {
|
if (viewModel.isLoading) {
|
||||||
@@ -92,12 +80,16 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
child: Center(child: Text(viewModel.errorMessage!)),
|
child: Center(child: Text(viewModel.errorMessage!)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (viewModel.hasData == false) {
|
if (!viewModel.hasData) {
|
||||||
return const Expanded(
|
return const Expanded(
|
||||||
child: Center(child: Text(BoardStrings.emptyListText)),
|
child: Center(child: Text(BoardStrings.emptyListText)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final columns = [...viewModel.columns]..sort(
|
||||||
|
(a, b) => (a.order ?? 0).compareTo(b.order ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -108,7 +100,7 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Text(
|
Text(
|
||||||
BoardStrings.boardTitle,
|
viewModel.board?.name ?? BoardStrings.boardTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20.0.sp(),
|
fontSize: 20.0.sp(),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -126,50 +118,15 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// To Do
|
for (int i = 0; i < columns.length; i++) ...[
|
||||||
Expanded(
|
if (i > 0) const SizedBox(width: 12),
|
||||||
child: _BoardColumn(
|
Expanded(
|
||||||
title: BoardStrings.toDo,
|
child: _BoardColumnView(
|
||||||
color: AppColors.primary20,
|
column: columns[i],
|
||||||
textColor: const Color(0xFF1976D2),
|
onCardDropped: _onCardDropped,
|
||||||
tasks: viewModel.getTasksByStatus(
|
|
||||||
TaskStatus.toDo,
|
|
||||||
),
|
),
|
||||||
status: TaskStatus.toDo,
|
|
||||||
onTaskDropped: _updateTaskStatus,
|
|
||||||
isValidMove: viewModel.isValidMove,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(width: 12),
|
|
||||||
// In Progress
|
|
||||||
Expanded(
|
|
||||||
child: _BoardColumn(
|
|
||||||
title: BoardStrings.inProgress,
|
|
||||||
color: AppColors.orange20,
|
|
||||||
textColor: AppColors.orange,
|
|
||||||
tasks: viewModel.getTasksByStatus(
|
|
||||||
TaskStatus.inProgress,
|
|
||||||
),
|
|
||||||
status: TaskStatus.inProgress,
|
|
||||||
onTaskDropped: _updateTaskStatus,
|
|
||||||
isValidMove: viewModel.isValidMove,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
// Done
|
|
||||||
Expanded(
|
|
||||||
child: _BoardColumn(
|
|
||||||
title: BoardStrings.done,
|
|
||||||
color: AppColors.secondary20,
|
|
||||||
textColor: AppColors.secondary70,
|
|
||||||
tasks: viewModel.getTasksByStatus(
|
|
||||||
TaskStatus.done,
|
|
||||||
),
|
|
||||||
status: TaskStatus.done,
|
|
||||||
onTaskDropped: _updateTaskStatus,
|
|
||||||
isValidMove: viewModel.isValidMove,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -187,43 +144,29 @@ class _BoardScreenState extends State<BoardScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BoardColumn extends StatelessWidget {
|
class _BoardColumnView extends StatelessWidget {
|
||||||
final String title;
|
final BoardColumn column;
|
||||||
final Color color;
|
final Future<void> Function(BoardCard, BoardColumn) onCardDropped;
|
||||||
final Color textColor;
|
|
||||||
final List<BoardData> tasks;
|
|
||||||
final TaskStatus status;
|
|
||||||
final Function(BoardData, TaskStatus) onTaskDropped;
|
|
||||||
final bool Function(TaskStatus, TaskStatus) isValidMove;
|
|
||||||
|
|
||||||
const _BoardColumn({
|
const _BoardColumnView({required this.column, required this.onCardDropped});
|
||||||
required this.title,
|
|
||||||
required this.color,
|
|
||||||
required this.textColor,
|
|
||||||
required this.tasks,
|
|
||||||
required this.status,
|
|
||||||
required this.onTaskDropped,
|
|
||||||
required this.isValidMove,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DragTarget<BoardData>(
|
final accent = _parseHexColor(column.color) ?? AppColors.textBlue;
|
||||||
onWillAcceptWithDetails: (details) {
|
return DragTarget<BoardCard>(
|
||||||
return isValidMove(details.data.statusEnum, status);
|
onWillAcceptWithDetails: (details) => details.data.columnId != column.id,
|
||||||
},
|
onAcceptWithDetails: (details) => onCardDropped(details.data, column),
|
||||||
onAcceptWithDetails: (details) {
|
|
||||||
onTaskDropped(details.data, status);
|
|
||||||
},
|
|
||||||
builder: (context, candidateData, rejectedData) {
|
builder: (context, candidateData, rejectedData) {
|
||||||
final isHovering = candidateData.isNotEmpty;
|
final isHovering = candidateData.isNotEmpty;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color:
|
||||||
isHovering ? color.withValues(alpha: 0.3) : AppColors.primary0,
|
isHovering
|
||||||
|
? accent.withValues(alpha: 0.15)
|
||||||
|
: AppColors.primary0,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isHovering ? textColor : AppColors.borderBlue,
|
color: isHovering ? accent : AppColors.borderBlue,
|
||||||
width: isHovering ? 2 : 1,
|
width: isHovering ? 2 : 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -235,26 +178,25 @@ class _BoardColumn extends StatelessWidget {
|
|||||||
height: 34,
|
height: 34,
|
||||||
margin: const EdgeInsets.all(8),
|
margin: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: accent.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
_columnHeader(column),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: textColor,
|
color: accent,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
itemCount: tasks.length,
|
itemCount: column.cards.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _TaskCard(task: tasks[index]);
|
return _TaskCard(card: column.cards[index], accent: accent);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -264,21 +206,25 @@ class _BoardColumn extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _columnHeader(BoardColumn column) {
|
||||||
|
final name = column.name ?? '';
|
||||||
|
final limit = column.limit;
|
||||||
|
if (limit == null || limit <= 0) return name;
|
||||||
|
return '$name (${column.cards.length}/$limit)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TaskCard extends StatelessWidget {
|
class _TaskCard extends StatelessWidget {
|
||||||
final BoardData task;
|
final BoardCard card;
|
||||||
|
final Color accent;
|
||||||
|
|
||||||
const _TaskCard({required this.task});
|
const _TaskCard({required this.card, required this.accent});
|
||||||
|
|
||||||
String _formatDate(DateTime date) {
|
|
||||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Draggable<BoardData>(
|
return Draggable<BoardCard>(
|
||||||
data: task,
|
data: card,
|
||||||
feedback: Material(
|
feedback: Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -319,91 +265,72 @@ class _TaskCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCardContent(BuildContext context) {
|
Widget _buildCardContent(BuildContext context) {
|
||||||
|
final due = card.dueDate;
|
||||||
|
final labels = card.labels;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
if (due != null)
|
||||||
height: 24.0,
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0.w()),
|
height: 24.0,
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.symmetric(horizontal: 10.0.w()),
|
||||||
color:
|
decoration: BoxDecoration(
|
||||||
task.statusEnum == TaskStatus.toDo
|
color: accent.withValues(alpha: 0.15),
|
||||||
? AppColors.primary20
|
borderRadius: BorderRadius.circular(4),
|
||||||
: task.statusEnum == TaskStatus.inProgress
|
),
|
||||||
? AppColors.orange10
|
child: Row(
|
||||||
: AppColors.secondary10,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
borderRadius: BorderRadius.circular(4),
|
children: [
|
||||||
),
|
Text(
|
||||||
child: Row(
|
'${BoardStrings.deadlineLabel} :',
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
style: TextStyle(
|
||||||
children: [
|
fontSize: 14.0,
|
||||||
Text(
|
fontWeight: FontWeight.w500,
|
||||||
'${BoardStrings.deadlineLabel} :',
|
color: AppColors.textBlue,
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 14.0,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: AppColors.textBlue,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
timeConvertor(due),
|
||||||
_formatDate(task.deadline ?? DateTime.now()),
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 14.0,
|
||||||
fontSize: 14.0,
|
fontWeight: FontWeight.w500,
|
||||||
fontWeight: FontWeight.w500,
|
color: AppColors.grey80,
|
||||||
color: AppColors.grey80,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
if (due != null) const SizedBox(height: 12),
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
Text(
|
||||||
task.description!,
|
card.title ?? '',
|
||||||
style: TextStyle(fontSize: 14, color: AppColors.grey70),
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.grey70,
|
||||||
|
),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
if (card.priority != null) _PriorityChip(priority: card.priority!),
|
||||||
Container(
|
if (labels.isNotEmpty) ...[
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
const SizedBox(height: 8),
|
||||||
decoration: BoxDecoration(
|
Wrap(
|
||||||
color: task.isReady ?? false ? AppColors.primary20 : AppColors.red0,
|
spacing: 6,
|
||||||
borderRadius: BorderRadius.circular(8),
|
runSpacing: 6,
|
||||||
|
children: [for (final label in labels) _LabelChip(label: label)],
|
||||||
),
|
),
|
||||||
child: Row(
|
],
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: task.isReady! ? AppColors.textBlue : AppColors.error,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
task.isReady!
|
|
||||||
? BoardStrings.readyLabel
|
|
||||||
: BoardStrings.incompleteLabel,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: task.isReady! ? AppColors.textBlue : AppColors.red20,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(AssetsManager.profile),
|
SvgPicture.asset(AssetsManager.profile),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
task.assignedTo!,
|
card.tenderId ?? '',
|
||||||
style: TextStyle(fontSize: 12, color: AppColors.grey60),
|
style: TextStyle(fontSize: 12, color: AppColors.grey60),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -411,3 +338,83 @@ class _TaskCard extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PriorityChip extends StatelessWidget {
|
||||||
|
final String priority;
|
||||||
|
const _PriorityChip({required this.priority});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final (bg, fg, dot) = _styleFor(priority);
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bg,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(color: dot, shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
priority,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: fg,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(Color bg, Color fg, Color dot) _styleFor(String priority) {
|
||||||
|
switch (priority.toLowerCase()) {
|
||||||
|
case 'urgent':
|
||||||
|
case 'high':
|
||||||
|
return (AppColors.red0, AppColors.red20, AppColors.error);
|
||||||
|
case 'low':
|
||||||
|
return (AppColors.primary20, AppColors.textBlue, AppColors.textBlue);
|
||||||
|
case 'medium':
|
||||||
|
default:
|
||||||
|
return (AppColors.orange10, AppColors.orange, AppColors.orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LabelChip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
const _LabelChip({required this.label});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary20,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(fontSize: 11, color: AppColors.textBlue),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? _parseHexColor(String? hex) {
|
||||||
|
if (hex == null) return null;
|
||||||
|
var value = hex.trim();
|
||||||
|
if (value.startsWith('#')) value = value.substring(1);
|
||||||
|
if (value.length == 6) value = 'FF$value';
|
||||||
|
if (value.length != 8) return null;
|
||||||
|
final parsed = int.tryParse(value, radix: 16);
|
||||||
|
if (parsed == null) return null;
|
||||||
|
return Color(parsed);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ class BoardStrings {
|
|||||||
BoardStrings._();
|
BoardStrings._();
|
||||||
|
|
||||||
static const String boardTitle = 'My Board';
|
static const String boardTitle = 'My Board';
|
||||||
static const String toDo = 'To Do';
|
|
||||||
static const String inProgress = 'In Progress';
|
|
||||||
static const String done = 'Done';
|
|
||||||
static const String emptyListText = 'No tasks found';
|
static const String emptyListText = 'No tasks found';
|
||||||
static const String deadlineLabel = 'Deadline';
|
static const String deadlineLabel = 'Deadline';
|
||||||
static const String readyLabel = 'Ready';
|
static const String moveFailed = 'Could not move card';
|
||||||
static const String incompleteLabel = 'Incomplete';
|
static String cardMovedMessage(String fromColumn, String toColumn) =>
|
||||||
static String taskMovedMessage(String oldStatus, String newStatus) =>
|
'Card moved from $fromColumn to $toColumn';
|
||||||
'Task moved from $oldStatus to $newStatus';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ class DeadlineItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (detail.submissionDeadline == null && detail.applicationDeadline == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
return isScreenBig
|
return isScreenBig
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0),
|
padding: const EdgeInsets.symmetric(vertical: 0),
|
||||||
@@ -51,7 +54,7 @@ class DeadlineItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 4.0.h()),
|
SizedBox(height: 4.0.h()),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(detail.submissionDeadline!),
|
timeConvertor(detail.submissionDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey80,
|
color: AppColors.grey80,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -80,7 +83,7 @@ class DeadlineItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: 4.0.h()),
|
SizedBox(height: 4.0.h()),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(detail.applicationDeadline!),
|
timeConvertor(detail.applicationDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey80,
|
color: AppColors.grey80,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -124,7 +127,7 @@ class DeadlineItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(detail.submissionDeadline!),
|
timeConvertor(detail.submissionDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey70,
|
color: AppColors.grey70,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -146,7 +149,7 @@ class DeadlineItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(detail.applicationDeadline!),
|
timeConvertor(detail.applicationDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey70,
|
color: AppColors.grey70,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
|
|||||||
@@ -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 ==
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ class DesktopHomePage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
||||||
homeViewModel.data != null &&
|
homeViewModel.data != null) {
|
||||||
homeViewModel.feedbackStats != null) {
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -181,13 +180,15 @@ class DesktopHomePage extends StatelessWidget {
|
|||||||
backgroundColor: AppColors.grey10,
|
backgroundColor: AppColors.grey10,
|
||||||
iconPath: AssetsManager.thumbLike,
|
iconPath: AssetsManager.thumbLike,
|
||||||
title: HomeStrings.likedTenders,
|
title: HomeStrings.likedTenders,
|
||||||
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
|
amount: homeViewModel.userLikedTendersCount.toString(),
|
||||||
textColor: AppColors.grey50,
|
textColor: AppColors.grey50,
|
||||||
enableTap: (homeViewModel.feedbackStats?.totalLikes ?? 0) != 0,
|
enableTap: true,
|
||||||
width: 178,
|
width: 178,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const LikedTendersRouteData().push(context);
|
const LikedTendersRouteData().push(context).then((value) {
|
||||||
|
homeViewModel.init();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ class MobileHomePage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
||||||
homeViewModel.data != null &&
|
homeViewModel.data != null) {
|
||||||
homeViewModel.feedbackStats != null) {
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -156,9 +155,9 @@ class MobileHomePage extends StatelessWidget {
|
|||||||
backgroundColor: AppColors.grey10,
|
backgroundColor: AppColors.grey10,
|
||||||
iconPath: AssetsManager.thumbLike,
|
iconPath: AssetsManager.thumbLike,
|
||||||
title: HomeStrings.likedTenders,
|
title: HomeStrings.likedTenders,
|
||||||
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
|
amount: homeViewModel.userLikedTendersCount.toString(),
|
||||||
textColor: AppColors.grey50,
|
textColor: AppColors.grey50,
|
||||||
enableTap: (homeViewModel.feedbackStats?.totalLikes ?? 0) != 0,
|
enableTap: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const LikedTendersRouteData().push(context).then((value) {
|
const LikedTendersRouteData().push(context).then((value) {
|
||||||
homeViewModel.init();
|
homeViewModel.init();
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ class TabletHomePage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
if (homeViewModel.tenderApprovalsStateResponse != null &&
|
||||||
homeViewModel.data != null &&
|
homeViewModel.data != null) {
|
||||||
homeViewModel.feedbackStats != null) {
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
@@ -186,13 +185,15 @@ class TabletHomePage extends StatelessWidget {
|
|||||||
backgroundColor: AppColors.grey10,
|
backgroundColor: AppColors.grey10,
|
||||||
iconPath: AssetsManager.thumbLike,
|
iconPath: AssetsManager.thumbLike,
|
||||||
title: HomeStrings.likedTenders,
|
title: HomeStrings.likedTenders,
|
||||||
amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0',
|
amount: homeViewModel.userLikedTendersCount.toString(),
|
||||||
textColor: AppColors.grey50,
|
textColor: AppColors.grey50,
|
||||||
enableTap: (homeViewModel.feedbackStats?.totalLikes ?? 0) != 0,
|
enableTap: true,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const LikedTendersRouteData().push(context);
|
const LikedTendersRouteData().push(context).then((value) {
|
||||||
|
homeViewModel.init();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class TendersListItem extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
timeConvertor(tender.publicationDate ?? 0),
|
timeConvertor(tender.publicationDate),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.0.sp(),
|
fontSize: 12.0.sp(),
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -64,7 +64,7 @@ class TendersListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(tender.tenderDeadline ?? 0),
|
timeConvertor(tender.tenderDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0.sp(),
|
fontSize: 14.0.sp(),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class LikedListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(tender.tenderDeadline!),
|
timeConvertor(tender.tenderDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0.sp(),
|
fontSize: 14.0.sp(),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -117,7 +117,7 @@ class LikedListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(tender.tenderDeadline!),
|
timeConvertor(tender.tenderDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.0.sp(),
|
fontSize: 14.0.sp(),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@@ -87,29 +87,42 @@ class _DesktopNotificationPageState extends State<DesktopNotificationPage>
|
|||||||
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: InkWell(
|
child: Builder(
|
||||||
onTap: () {
|
builder: (context) {
|
||||||
viewModel.markAllAsRead(context);
|
final hasNotifications = (viewModel
|
||||||
},
|
.allNotificationResponse
|
||||||
borderRadius: BorderRadius.circular(99),
|
?.data
|
||||||
child: Container(
|
?.isNotEmpty ??
|
||||||
width: 132.0.w(),
|
false);
|
||||||
height: 32.0.h(),
|
return InkWell(
|
||||||
decoration: BoxDecoration(
|
onTap: hasNotifications
|
||||||
color: AppColors.primary20,
|
? () => viewModel.markAllAsRead(context)
|
||||||
|
: null,
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
),
|
child: Container(
|
||||||
child: Center(
|
width: 132.0.w(),
|
||||||
child: Text(
|
height: 32.0.h(),
|
||||||
NotificationStrings.markAllAsReadButton,
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(
|
color: hasNotifications
|
||||||
fontSize: 14.0.sp(),
|
? AppColors.primary20
|
||||||
fontWeight: FontWeight.w500,
|
: AppColors.grey20,
|
||||||
color: AppColors.mainBlue,
|
borderRadius: BorderRadius.circular(99),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
NotificationStrings.markAllAsReadButton,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0.sp(),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: hasNotifications
|
||||||
|
? AppColors.mainBlue
|
||||||
|
: AppColors.grey50,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -101,29 +101,40 @@ class _MobileNotificationPageState extends State<MobileNotificationPage>
|
|||||||
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: InkWell(
|
child: Builder(
|
||||||
onTap: () {
|
builder: (context) {
|
||||||
viewModel.markAllAsRead(context);
|
final hasNotifications =
|
||||||
},
|
(viewModel.allNotificationResponse?.data?.isNotEmpty ??
|
||||||
borderRadius: BorderRadius.circular(99),
|
false);
|
||||||
child: Container(
|
return InkWell(
|
||||||
width: 132.0.w(),
|
onTap: hasNotifications
|
||||||
height: 32.0.h(),
|
? () => viewModel.markAllAsRead(context)
|
||||||
decoration: BoxDecoration(
|
: null,
|
||||||
color: AppColors.primary20,
|
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
),
|
child: Container(
|
||||||
child: Center(
|
width: 132.0.w(),
|
||||||
child: Text(
|
height: 32.0.h(),
|
||||||
NotificationStrings.markAllAsReadButton,
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(
|
color: hasNotifications
|
||||||
fontSize: 14.0.sp(),
|
? AppColors.primary20
|
||||||
fontWeight: FontWeight.w500,
|
: AppColors.grey20,
|
||||||
color: AppColors.mainBlue,
|
borderRadius: BorderRadius.circular(99),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
NotificationStrings.markAllAsReadButton,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0.sp(),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: hasNotifications
|
||||||
|
? AppColors.mainBlue
|
||||||
|
: AppColors.grey50,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -90,29 +90,42 @@ class _TabletNotificationPageState extends State<TabletNotificationPage>
|
|||||||
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
padding: EdgeInsetsDirectional.only(end: 24.0.w()),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: InkWell(
|
child: Builder(
|
||||||
onTap: () {
|
builder: (context) {
|
||||||
viewModel.markAllAsRead(context);
|
final hasNotifications = (viewModel
|
||||||
},
|
.allNotificationResponse
|
||||||
borderRadius: BorderRadius.circular(99),
|
?.data
|
||||||
child: Container(
|
?.isNotEmpty ??
|
||||||
width: 132.0.w(),
|
false);
|
||||||
height: 32.0.h(),
|
return InkWell(
|
||||||
decoration: BoxDecoration(
|
onTap: hasNotifications
|
||||||
color: AppColors.primary20,
|
? () => viewModel.markAllAsRead(context)
|
||||||
|
: null,
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
),
|
child: Container(
|
||||||
child: Center(
|
width: 132.0.w(),
|
||||||
child: Text(
|
height: 32.0.h(),
|
||||||
NotificationStrings.markAllAsReadButton,
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(
|
color: hasNotifications
|
||||||
fontSize: 14.0.sp(),
|
? AppColors.primary20
|
||||||
fontWeight: FontWeight.w500,
|
: AppColors.grey20,
|
||||||
color: AppColors.mainBlue,
|
borderRadius: BorderRadius.circular(99),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
NotificationStrings.markAllAsReadButton,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0.sp(),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: hasNotifications
|
||||||
|
? AppColors.mainBlue
|
||||||
|
: AppColors.grey50,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import 'package:tm_app/core/utils/size_config.dart';
|
|||||||
import 'package:tm_app/view_models/tenders_view_model.dart';
|
import 'package:tm_app/view_models/tenders_view_model.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';
|
||||||
import 'package:tm_app/views/shared/page_selection_dialog.dart';
|
|
||||||
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||||
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
|
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
|
||||||
import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart';
|
import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart';
|
||||||
import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart';
|
import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart';
|
||||||
|
import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart';
|
||||||
|
|
||||||
import '../../../core/constants/common_strings.dart';
|
import '../../../core/constants/common_strings.dart';
|
||||||
import '../../../core/utils/app_toast.dart';
|
import '../../../core/utils/app_toast.dart';
|
||||||
@@ -95,10 +95,13 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_DesktopPagination(
|
SizedBox(
|
||||||
totalPages: vm.totalPages,
|
width: 740,
|
||||||
currentPage: vm.currentPage,
|
child: WindowedPagination(
|
||||||
onPageSelected: vm.jumpToPage,
|
currentPage: vm.currentPageIndex,
|
||||||
|
totalPages: vm.totalPages,
|
||||||
|
onPageChanged: vm.handlePaginationChange,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -264,93 +267,3 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DesktopPagination extends StatelessWidget {
|
|
||||||
final int totalPages;
|
|
||||||
final int currentPage;
|
|
||||||
final ValueChanged<int> onPageSelected;
|
|
||||||
|
|
||||||
const _DesktopPagination({
|
|
||||||
required this.totalPages,
|
|
||||||
required this.currentPage,
|
|
||||||
required this.onPageSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 680,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 12.0.h()),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
TendersStrings.page,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13.0.sp(),
|
|
||||||
fontWeight: FontWeight.w300,
|
|
||||||
color: AppColors.grey60,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10.0.w()),
|
|
||||||
InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
final selectedPage = await showDialog<int>(
|
|
||||||
context: context,
|
|
||||||
builder:
|
|
||||||
(context) => PageSelectionDialog(
|
|
||||||
totalPages: totalPages,
|
|
||||||
currentPage: currentPage,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (selectedPage != null) {
|
|
||||||
onPageSelected(selectedPage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: AppColors.grey30, width: 1),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(width: 5.0.w()),
|
|
||||||
Text(
|
|
||||||
'$currentPage',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0.sp(),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: AppColors.grey70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.0.w()),
|
|
||||||
Icon(Icons.arrow_drop_down, color: AppColors.grey80),
|
|
||||||
SizedBox(width: 5.0.w()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.0.w()),
|
|
||||||
Text(
|
|
||||||
TendersStrings.of,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.0.sp(),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: AppColors.grey60,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.0.w()),
|
|
||||||
Text(
|
|
||||||
'$totalPages',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13.0.sp(),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: AppColors.grey60,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '../../../core/utils/app_toast.dart';
|
|||||||
import '../../shared/tender_app_bar.dart';
|
import '../../shared/tender_app_bar.dart';
|
||||||
import '../strings/tenders_strings.dart';
|
import '../strings/tenders_strings.dart';
|
||||||
import '../widgets/main_tenders_slider.dart';
|
import '../widgets/main_tenders_slider.dart';
|
||||||
|
import '../widgets/windowed_pagination.dart';
|
||||||
|
|
||||||
class MobileTendersPage extends StatefulWidget {
|
class MobileTendersPage extends StatefulWidget {
|
||||||
const MobileTendersPage({super.key});
|
const MobileTendersPage({super.key});
|
||||||
@@ -61,13 +62,29 @@ class _MobileTendersPageState extends State<MobileTendersPage> {
|
|||||||
return const Center(child: Text(CommonStrings.noData));
|
return const Center(child: Text(CommonStrings.noData));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsets.symmetric(vertical: 18.0.h()),
|
Expanded(
|
||||||
child: MainTendersSlider(
|
child: SingleChildScrollView(
|
||||||
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 18.0.h()),
|
||||||
|
child: MainTendersSlider(
|
||||||
|
key: ValueKey(
|
||||||
|
'tenders-slider-${viewModel.currentPageIndex}',
|
||||||
|
),
|
||||||
|
tenders:
|
||||||
|
viewModel.tendersResponse?.data?.tenders ?? [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
WindowedPagination(
|
||||||
|
currentPage: viewModel.currentPageIndex,
|
||||||
|
totalPages: viewModel.totalPages,
|
||||||
|
onPageChanged: viewModel.handlePaginationChange,
|
||||||
|
compact: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:tm_app/core/utils/size_config.dart';
|
|||||||
import 'package:tm_app/view_models/tenders_view_model.dart';
|
import 'package:tm_app/view_models/tenders_view_model.dart';
|
||||||
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
||||||
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
|
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
|
||||||
|
import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart';
|
||||||
|
|
||||||
import '../../../core/constants/common_strings.dart';
|
import '../../../core/constants/common_strings.dart';
|
||||||
import '../../shared/tender_app_bar.dart';
|
import '../../shared/tender_app_bar.dart';
|
||||||
@@ -53,16 +54,30 @@ class _TabletTendersPageState extends State<TabletTendersPage> {
|
|||||||
viewModel.isLoading == false) {
|
viewModel.isLoading == false) {
|
||||||
return const Center(child: Text(CommonStrings.noData));
|
return const Center(child: Text(CommonStrings.noData));
|
||||||
}
|
}
|
||||||
return SingleChildScrollView(
|
return Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsetsDirectional.only(
|
Expanded(
|
||||||
end: 24.0.w(),
|
child: SingleChildScrollView(
|
||||||
top: 128.0.h(),
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
end: 24.0.w(),
|
||||||
|
top: 128.0.h(),
|
||||||
|
),
|
||||||
|
child: MainTendersSlider(
|
||||||
|
key: ValueKey(
|
||||||
|
'tenders-slider-${viewModel.currentPageIndex}',
|
||||||
|
),
|
||||||
|
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: MainTendersSlider(
|
WindowedPagination(
|
||||||
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
|
currentPage: viewModel.currentPageIndex,
|
||||||
|
totalPages: viewModel.totalPages,
|
||||||
|
onPageChanged: viewModel.handlePaginationChange,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,31 +27,6 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
|||||||
final PageController pageController = PageController(initialPage: 0);
|
final PageController pageController = PageController(initialPage: 0);
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
|
|
||||||
ScrollController? _scrollController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
if (widget.isDesktop) {
|
|
||||||
_scrollController = ScrollController();
|
|
||||||
_scrollController!.addListener(_onScroll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onScroll() {
|
|
||||||
if (!widget.isDesktop) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final viewModel = context.read<TendersViewModel>();
|
|
||||||
if (_scrollController!.position.pixels >=
|
|
||||||
_scrollController!.position.maxScrollExtent - 200 &&
|
|
||||||
viewModel.hasMoreData &&
|
|
||||||
!viewModel.isLoadingMore) {
|
|
||||||
viewModel.loadMoreTenders();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _goToPreviousPage() {
|
void _goToPreviousPage() {
|
||||||
if (currentPage == 1) {
|
if (currentPage == 1) {
|
||||||
return;
|
return;
|
||||||
@@ -107,7 +82,6 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
pageController.dispose();
|
pageController.dispose();
|
||||||
_scrollController?.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,12 +115,6 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
|||||||
if (!viewModel.hasFeedbackForTender(id)) {
|
if (!viewModel.hasFeedbackForTender(id)) {
|
||||||
viewModel.getTenderFeedback(id);
|
viewModel.getTenderFeedback(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index >= widget.tenders.length - 1 &&
|
|
||||||
viewModel.hasMoreData &&
|
|
||||||
!viewModel.isLoadingMore) {
|
|
||||||
viewModel.loadMoreTenders();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final tender = widget.tenders[index];
|
final tender = widget.tenders[index];
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class TenderCard extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _dateText() {
|
Widget _dateText() {
|
||||||
return Text(
|
return Text(
|
||||||
timeConvertor(tender.publicationDate ?? 0),
|
timeConvertor(tender.publicationDate),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey60,
|
color: AppColors.grey60,
|
||||||
fontSize: 12.0.sp(),
|
fontSize: 12.0.sp(),
|
||||||
@@ -150,7 +150,7 @@ class TenderCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(width: 4.0.w()),
|
SizedBox(width: 4.0.w()),
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(tender.submissionDeadline ?? 0),
|
timeConvertor(tender.submissionDeadline),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey80,
|
color: AppColors.grey80,
|
||||||
fontSize: 14.0.sp(),
|
fontSize: 14.0.sp(),
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tm_app/core/theme/colors.dart';
|
||||||
|
|
||||||
|
/// A windowed numbered pagination widget that mirrors the Next.js
|
||||||
|
/// implementation: ±10 page-button window around the current page,
|
||||||
|
/// 1-based labels, prev/next chevrons.
|
||||||
|
class WindowedPagination extends StatelessWidget {
|
||||||
|
const WindowedPagination({
|
||||||
|
super.key,
|
||||||
|
required this.currentPage,
|
||||||
|
required this.totalPages,
|
||||||
|
required this.onPageChanged,
|
||||||
|
this.maxPageJump = 10,
|
||||||
|
this.compact = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 0-based absolute current page index.
|
||||||
|
final int currentPage;
|
||||||
|
|
||||||
|
/// Total number of pages (1-based count).
|
||||||
|
final int totalPages;
|
||||||
|
|
||||||
|
/// Called with the 0-based target page index.
|
||||||
|
final ValueChanged<int> onPageChanged;
|
||||||
|
|
||||||
|
/// Half-width of the page-button window (web uses 10).
|
||||||
|
final int maxPageJump;
|
||||||
|
|
||||||
|
/// If true, renders smaller buttons (for mobile).
|
||||||
|
final bool compact;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (totalPages <= 1) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final clampedCurrent = currentPage.clamp(0, totalPages - 1);
|
||||||
|
final windowStart = (clampedCurrent - maxPageJump).clamp(0, totalPages - 1);
|
||||||
|
final windowEnd = (clampedCurrent + maxPageJump).clamp(0, totalPages - 1);
|
||||||
|
|
||||||
|
final buttons = <Widget>[];
|
||||||
|
|
||||||
|
buttons.add(
|
||||||
|
_NavButton(
|
||||||
|
icon: Icons.chevron_left,
|
||||||
|
enabled: clampedCurrent > 0,
|
||||||
|
compact: compact,
|
||||||
|
onTap: () => onPageChanged(clampedCurrent - 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (windowStart > 0) {
|
||||||
|
buttons.add(_buildPageButton(0, clampedCurrent));
|
||||||
|
if (windowStart > 1) {
|
||||||
|
buttons.add(const _Ellipsis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = windowStart; i <= windowEnd; i++) {
|
||||||
|
buttons.add(_buildPageButton(i, clampedCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windowEnd < totalPages - 1) {
|
||||||
|
if (windowEnd < totalPages - 2) {
|
||||||
|
buttons.add(const _Ellipsis());
|
||||||
|
}
|
||||||
|
buttons.add(_buildPageButton(totalPages - 1, clampedCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.add(
|
||||||
|
_NavButton(
|
||||||
|
icon: Icons.chevron_right,
|
||||||
|
enabled: clampedCurrent < totalPages - 1,
|
||||||
|
compact: compact,
|
||||||
|
onTap: () => onPageChanged(clampedCurrent + 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final gap = compact ? 4.0 : 6.0;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: compact ? 8 : 12),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minWidth: constraints.maxWidth),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: _withSpacing(buttons, gap),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPageButton(int pageIndex, int currentPageIndex) {
|
||||||
|
final isActive = pageIndex == currentPageIndex;
|
||||||
|
return _PageButton(
|
||||||
|
label: '${pageIndex + 1}',
|
||||||
|
isActive: isActive,
|
||||||
|
compact: compact,
|
||||||
|
onTap: isActive ? null : () => onPageChanged(pageIndex),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Widget> _withSpacing(List<Widget> items, double gap) {
|
||||||
|
final result = <Widget>[];
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
result.add(items[i]);
|
||||||
|
if (i != items.length - 1) {
|
||||||
|
result.add(SizedBox(width: gap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PageButton extends StatelessWidget {
|
||||||
|
const _PageButton({
|
||||||
|
required this.label,
|
||||||
|
required this.isActive,
|
||||||
|
required this.compact,
|
||||||
|
this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final bool isActive;
|
||||||
|
final bool compact;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = compact ? 32.0 : 38.0;
|
||||||
|
return SizedBox(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
gradient: isActive
|
||||||
|
? const LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [AppColors.mainBlue, AppColors.primaryColor],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
color: isActive ? null : AppColors.grey0,
|
||||||
|
border: Border.all(
|
||||||
|
color: isActive ? Colors.transparent : AppColors.grey30,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
boxShadow: isActive
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.mainBlue.withValues(alpha: 0.25),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: compact ? 12 : 13,
|
||||||
|
fontWeight: isActive ? FontWeight.w600 : FontWeight.w500,
|
||||||
|
color: isActive ? Colors.white : AppColors.grey70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NavButton extends StatelessWidget {
|
||||||
|
const _NavButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.enabled,
|
||||||
|
required this.compact,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final bool enabled;
|
||||||
|
final bool compact;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = compact ? 32.0 : 38.0;
|
||||||
|
return SizedBox(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
onTap: enabled ? onTap : null,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: AppColors.grey0,
|
||||||
|
border: Border.all(
|
||||||
|
color: enabled ? AppColors.grey30 : AppColors.grey20,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: compact ? 18 : 20,
|
||||||
|
color: enabled ? AppColors.grey70 : AppColors.grey40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Ellipsis extends StatelessWidget {
|
||||||
|
const _Ellipsis();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
child: Text(
|
||||||
|
'…',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey60,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ class TenderCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
// Date
|
// Date
|
||||||
Text(
|
Text(
|
||||||
timeConvertor(int.parse(tender.publicationDate!.toString())),
|
timeConvertor(tender.publicationDate),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColors.grey60,
|
color: AppColors.grey60,
|
||||||
fontSize: 12.0.sp(),
|
fontSize: 12.0.sp(),
|
||||||
|
|||||||
+319
-295
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tm_app/core/utils/date_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('timeConvertor', () {
|
||||||
|
test('returns dash for null, zero, and negative', () {
|
||||||
|
expect(timeConvertor(null), '-');
|
||||||
|
expect(timeConvertor(0), '-');
|
||||||
|
expect(timeConvertor(-1), '-');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats valid seconds as yyyy-MM-dd', () {
|
||||||
|
final formatted = timeConvertor(1700000000);
|
||||||
|
expect(formatted, matches(RegExp(r'^\d{4}-\d{2}-\d{2}$')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formats valid seconds with time when hasTime is true', () {
|
||||||
|
final formatted = timeConvertor(1700000000, hasTime: true);
|
||||||
|
expect(
|
||||||
|
formatted,
|
||||||
|
matches(RegExp(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$')),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats values above 9999999999 as milliseconds', () {
|
||||||
|
// 1700000000000 ms and 1700000000 s should format to the same day.
|
||||||
|
expect(timeConvertor(1700000000000), timeConvertor(1700000000));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('unixTimestampFromJson', () {
|
||||||
|
test('parses int and num', () {
|
||||||
|
expect(unixTimestampFromJson(1700000000), 1700000000);
|
||||||
|
expect(unixTimestampFromJson(1700000000.7), 1700000000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses numeric strings', () {
|
||||||
|
expect(unixTimestampFromJson('1700000000'), 1700000000);
|
||||||
|
expect(unixTimestampFromJson(' 1700000000 '), 1700000000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for empty, null, and non-numeric input', () {
|
||||||
|
expect(unixTimestampFromJson(null), isNull);
|
||||||
|
expect(unixTimestampFromJson(''), isNull);
|
||||||
|
expect(unixTimestampFromJson('abc'), isNull);
|
||||||
|
expect(unixTimestampFromJson(true), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects non-positive values', () {
|
||||||
|
expect(unixTimestampFromJson(0), isNull);
|
||||||
|
expect(unixTimestampFromJson(-1), isNull);
|
||||||
|
expect(unixTimestampFromJson('-1'), isNull);
|
||||||
|
expect(unixTimestampFromJson('0'), isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user