Commit baaa8f3d authored by Wei Han's avatar Wei Han

code update

parent f38dff39
......@@ -8,22 +8,22 @@
/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
1AE5006B6719358FE5D08276 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C0CBFC285E76843D753A39C /* libPods-example.a */; };
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
705C78A9CBF3D37B370DC92C /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB77023451E7FACEB5CCC67A /* libPods-example.a */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0C0CBFC285E76843D753A39C /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
0DD693481B6504D78252E1A4 /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = "<group>"; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
49B853AE2A174A2113AE36B8 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = "<group>"; };
31E3569C4DACA1583750BB4B /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
67CF26DD341753F546BD183F /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = "<group>"; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
CB77023451E7FACEB5CCC67A /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
......@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1AE5006B6719358FE5D08276 /* libPods-example.a in Frameworks */,
705C78A9CBF3D37B370DC92C /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
0C0CBFC285E76843D753A39C /* libPods-example.a */,
CB77023451E7FACEB5CCC67A /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
0DD693481B6504D78252E1A4 /* Pods-example.debug.xcconfig */,
49B853AE2A174A2113AE36B8 /* Pods-example.release.xcconfig */,
31E3569C4DACA1583750BB4B /* Pods-example.debug.xcconfig */,
67CF26DD341753F546BD183F /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
421B484D7AFF49D66547AE2C /* [CP] Check Pods Manifest.lock */,
0C0B6164CF1596DDFC4F19C5 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
1FD14DE327B38E9733D3F31E /* [CP] Embed Pods Frameworks */,
DCD1F955A75F9EFAB389857C /* [CP] Copy Pods Resources */,
1784E64423995B1E7F285F50 /* [CP] Embed Pods Frameworks */,
6A78F44E33668E0CBF927584 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,46 +183,46 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
1FD14DE327B38E9733D3F31E /* [CP] Embed Pods Frameworks */ = {
0C0B6164CF1596DDFC4F19C5 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
421B484D7AFF49D66547AE2C /* [CP] Check Pods Manifest.lock */ = {
1784E64423995B1E7F285F50 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
DCD1F955A75F9EFAB389857C /* [CP] Copy Pods Resources */ = {
6A78F44E33668E0CBF927584 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0DD693481B6504D78252E1A4 /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 31E3569C4DACA1583750BB4B /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 49B853AE2A174A2113AE36B8 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 67CF26DD341753F546BD183F /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
//QmsPlugin.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QmsPluginUI : NSObject
+ (UIViewController *)makeViewController;
@end
NS_ASSUME_NONNULL_END
//
// QmsPluginFramework.h
// QmsPluginFramework
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//! Project version number for QmsPluginFramework.
FOUNDATION_EXPORT double QmsPluginFrameworkVersionNumber;
//! Project version string for QmsPluginFramework.
FOUNDATION_EXPORT const unsigned char QmsPluginFrameworkVersionString[];
// Public headers
#import <QmsPluginFramework/QmsPlugin.h>
framework module QmsPluginFramework {
umbrella header "QmsPluginFramework.h"
export *
module * { export * }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
4/wVNoFd+WcEg7nmH9Hm5v1nm7s=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
46196FWEMwEuy8Dd/6jLyvdFEok=
</data>
<key>Headers/QmsPluginFramework.h</key>
<data>
jlHmWnGf08fskfPJb//4vaShDlo=
</data>
<key>Info.plist</key>
<data>
w/Rc8R+JLx3x4AhZWV+3tG1CxCM=
</data>
<key>Modules/module.modulemap</key>
<data>
BD9YnWBkeFmDcuOl4cAjALd/7yw=
</data>
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
zhv68b8E95A9988/pvXA0JKzn5/KsdixWe45ATxfW3g=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
<data>
uuQ2Omt7lnrShVFriPpMiXZhx8kx6Km97GxC3m1ro+A=
</data>
</dict>
<key>Headers/QmsPluginFramework.h</key>
<dict>
<key>hash2</key>
<data>
/9Qe6QJ19ZC61+OfPl2+OZK1DI2Wa1lS++m7D41g3zI=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
e+P6RtiuicqWfZ3H2B43r960DPkFwUWqXKWU+w7OVD0=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
//QmsPlugin.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QmsPluginUI : NSObject
+ (UIViewController *)makeViewController;
@end
NS_ASSUME_NONNULL_END
//
// QmsPluginFramework.h
// QmsPluginFramework
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
//! Project version number for QmsPluginFramework.
FOUNDATION_EXPORT double QmsPluginFrameworkVersionNumber;
//! Project version string for QmsPluginFramework.
FOUNDATION_EXPORT const unsigned char QmsPluginFrameworkVersionString[];
// Public headers
#import <QmsPluginFramework/QmsPlugin.h>
framework module QmsPluginFramework {
umbrella header "QmsPluginFramework.h"
export *
module * { export * }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Assets.car</key>
<data>
4/wVNoFd+WcEg7nmH9Hm5v1nm7s=
</data>
<key>Headers/QmsPlugin.h</key>
<data>
46196FWEMwEuy8Dd/6jLyvdFEok=
</data>
<key>Headers/QmsPluginFramework.h</key>
<data>
jlHmWnGf08fskfPJb//4vaShDlo=
</data>
<key>Info.plist</key>
<data>
bbhIYcGh7MnGD7V1rEns5GGhP1g=
</data>
<key>Modules/module.modulemap</key>
<data>
BD9YnWBkeFmDcuOl4cAjALd/7yw=
</data>
</dict>
<key>files2</key>
<dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
zhv68b8E95A9988/pvXA0JKzn5/KsdixWe45ATxfW3g=
</data>
</dict>
<key>Headers/QmsPlugin.h</key>
<dict>
<key>hash2</key>
<data>
uuQ2Omt7lnrShVFriPpMiXZhx8kx6Km97GxC3m1ro+A=
</data>
</dict>
<key>Headers/QmsPluginFramework.h</key>
<dict>
<key>hash2</key>
<data>
/9Qe6QJ19ZC61+OfPl2+OZK1DI2Wa1lS++m7D41g3zI=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
e+P6RtiuicqWfZ3H2B43r960DPkFwUWqXKWU+w7OVD0=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
......@@ -22,6 +22,8 @@
@property (nonatomic, assign) BOOL isEditMode;
@property (nonatomic, strong) NSMutableArray *existingImages;
@property (nonatomic, strong) NSMutableArray *addedImages;
@property (nonatomic, strong) NSString *catName;
@property (nonatomic, strong) NSString *issueName;
@end
......@@ -75,10 +77,10 @@
}
if (self.categories.count > 0) {
NSDictionary *firstCat = self.categories.firstObject;
NSString *catName = firstCat[@"cat_name"];
self.catName = firstCat[@"cat_name"];
UIButton *catBtn = [self.view viewWithTag:2000];
if ([catBtn isKindOfClass:[UIButton class]] && catName) {
[catBtn setTitle:catName forState:UIControlStateNormal];
if ([catBtn isKindOfClass:[UIButton class]] && self.catName) {
[catBtn setTitle:self.catName forState:UIControlStateNormal];
[catBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
}
......@@ -110,16 +112,16 @@
}
// 🏷 Prefill dropdown titles
NSString *catName = issue[@"category"];
self.catName = issue[@"category"];
NSString *typeName = issue[@"type"];
NSString *issueName = issue[@"issue"];
self.issueName = issue[@"issue"];
UIButton *catBtn = [self.view viewWithTag:2000]; // Category
UIButton *typeBtn = [self.view viewWithTag:2001]; // Type
UIButton *issueBtn = [self.view viewWithTag:2002]; // Issue
if ([catBtn isKindOfClass:[UIButton class]] && catName.length > 0) {
[catBtn setTitle:catName forState:UIControlStateNormal];
if ([catBtn isKindOfClass:[UIButton class]] && self.catName.length > 0) {
[catBtn setTitle:self.catName forState:UIControlStateNormal];
[catBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
......@@ -129,8 +131,8 @@
self.selectedTypeName = typeName;
}
if ([issueBtn isKindOfClass:[UIButton class]] && issueName.length > 0) {
[issueBtn setTitle:issueName forState:UIControlStateNormal];
if ([issueBtn isKindOfClass:[UIButton class]] && self.issueName.length > 0) {
[issueBtn setTitle:self.issueName forState:UIControlStateNormal];
[issueBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
self.selectedIssueID = [NSString stringWithFormat:@"%@", issue[@"issue_id"] ?: @""];
}
......@@ -430,8 +432,9 @@
NSArray *categories = self.locationData[@"category"];
if (categories.count > 0) {
NSDictionary *firstCategory = categories.firstObject;
NSString *categoryName = locationField.text ?: @"";
[dropdown setTitle:categoryName forState:UIControlStateNormal];
NSDictionary *cat = self.categories.firstObject;
self.catName = cat[@"cat_name"] ?: @"-";
[dropdown setTitle:self.catName forState:UIControlStateNormal];
[dropdown setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
} else {
[dropdown setTitle:@"(No Category)" forState:UIControlStateNormal];
......@@ -626,8 +629,8 @@
// Category (fixed)
if (self.categories.count > 0) {
NSDictionary *cat = self.categories.firstObject;
NSString *catName = cat[@"cat_name"] ?: @"-";
[sender setTitle:catName forState:UIControlStateNormal];
self.catName = cat[@"cat_name"] ?: @"-";
[sender setTitle:self.catName forState:UIControlStateNormal];
[sender setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
return;
......@@ -664,6 +667,7 @@
if ([issueName isEqualToString:opt]) {
self.selectedIssueID = [NSString stringWithFormat:@"%@", issue[@"issue_id"]];
NSLog(@"✅ Selected Issue: %@ (ID: %@)", issueName, self.selectedIssueID);
self.issueName = issueName;
break;
}
}
......@@ -895,6 +899,15 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
}
- (void)performSubmit {
NSLog(@"Location: %@\nCategory: %@\nType: %@\nIssue: %@", self.locationField.text, self.catName, self.selectedTypeName, self.issueName);
NSDictionary *fieldNames = @{
@"locationName": self.locationField.text ?: @"",
@"categoryName": self.catName ?: @"",
@"typeName": self.selectedTypeName ?: @"",
@"issueName": self.issueName ?: @"",
};
if (self.isEditMode) {
NSLog(@"📝 Updating existing issue instead of adding new one");
......@@ -927,7 +940,8 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
[[OfflineSyncManager shared] queueUpdateIssue:updateData
display:displayData
images:self.addedImages];
images:self.addedImages
fieldNames:fieldNames];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
......@@ -1012,7 +1026,8 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
[[OfflineSyncManager shared] queueAddIssue:issueData
display:displayData
images:self.uploadedImages];
images:self.uploadedImages
fieldNames:fieldNames];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
......
......@@ -15,21 +15,15 @@
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
NSString *unitName = self.itemData[@"unitName"];
NSString *createdBy = self.itemData[@"createdBy"];
NSString *createdOn = self.itemData[@"createdOn"];
NSString *uploadedOn = self.itemData[@"uploadedOn"];
NSString *actionMade = self.itemData[@"type"];
NSString *status = self.itemData[@"status"];
NSLog(@"Unit: %@", unitName);
NSLog(@"Created by: %@", createdBy);
NSLog(@"Created on: %@", createdOn);
NSLog(@"Uploaded on: %@", uploadedOn);
NSLog(@"Action: %@", actionMade);
NSLog(@"Status: %@", status);
NSLog(@"📦 Received History Data: %@", self.itemData);
NSString *plainText = [NSString stringWithFormat:@"%@", self.itemData];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"pendingDetail.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 history details saved to %@", filePath);
[self setupHeader];
[self setupDetailContent];
}
#pragma mark - Header Setup
......@@ -95,7 +89,6 @@
[container.widthAnchor constraintEqualToAnchor:scroll.widthAnchor],
]];
// Create vertical stack for labels
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
......@@ -109,52 +102,91 @@
[stack.bottomAnchor constraintLessThanOrEqualToAnchor:container.bottomAnchor constant:-16],
]];
// Create labels for each field
[stack addArrangedSubview:[self makeDetailLabel:@"Unit" value:self.itemData[@"unitName"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Created On" value:self.itemData[@"createdAt"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Created By" value:self.itemData[@"createdBy"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Uploaded On" value:self.itemData[@"uploadedOn"] ?: @"-"]];
[stack addArrangedSubview:[self makeDetailLabel:@"Action" value:self.itemData[@"type"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Status" value:self.itemData[@"status"] ?: @"-"]];
}
UILabel *unitLabel = [[UILabel alloc] init];
unitLabel.text = self.itemData[@"unitName"];
unitLabel.font = [UIFont boldSystemFontOfSize:22];
unitLabel.textColor = UIColor.labelColor;
unitLabel.numberOfLines = 0;
#pragma mark - Helper
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
[stack addArrangedSubview:unitLabel];
// spacing
UIView *spacer = [[UIView alloc] init];
[spacer.heightAnchor constraintEqualToConstant:12].active = YES;
[stack addArrangedSubview:spacer];
NSString *status = self.itemData[@"status"];
UIColor *statusColor = [self colorForStatus:status];
// Add rows
[stack addArrangedSubview:[self makeRow:@"Created Date" value:self.itemData[@"createdOn"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Created By" value:self.itemData[@"createdBy"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Uploaded On" value:self.itemData[@"raw"][@"syncedAt"] color:statusColor]];
[stack addArrangedSubview:[self makeRow:@"Action Made" value:self.itemData[@"type"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Location" value:self.itemData[@"raw"][@"fieldNames"][@"locationName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Category" value:self.itemData[@"raw"][@"category"][@"categoryName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Type" value:self.itemData[@"raw"][@"type"][@"typeName"] color:nil]];
[stack addArrangedSubview:[self makeRow:@"Issue" value:self.itemData[@"raw"][@"issueDisplay"][@"issueName"] ?: @"-" color:nil]];
[stack addArrangedSubview:[self makeRow:@"Comment" value:self.itemData[@"raw"][@"remarks"] ?: @"-" color:nil]];
}
- (UIView *)makeDetailLabel:(NSString *)title value:(NSString *)value {
UIView *view = [[UIView alloc] init];
view.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textColor = UIColor.labelColor;
titleLabel.text = title;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
- (UIView *)makeRow:(NSString *)title
value:(NSString *)value
color:(UIColor *)color {
UIView *row = [[UIView alloc] init];
row.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *left = [[UILabel alloc] init];
left.text = [NSString stringWithFormat:@"%@ :", title];
left.font = [UIFont systemFontOfSize:15];
left.textColor = UIColor.secondaryLabelColor;
left.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *valueLabel = [[UILabel alloc] init];
valueLabel.font = [UIFont systemFontOfSize:16];
valueLabel.textColor = UIColor.secondaryLabelColor;
valueLabel.text = value;
valueLabel.numberOfLines = 0;
valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *right = [[UILabel alloc] init];
right.text = value ?: @"-";
right.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
right.textColor = color ?: UIColor.labelColor;
right.numberOfLines = 0;
right.translatesAutoresizingMaskIntoConstraints = NO;
[view addSubview:titleLabel];
[view addSubview:valueLabel];
[row addSubview:left];
[row addSubview:right];
[NSLayoutConstraint activateConstraints:@[
[titleLabel.topAnchor constraintEqualToAnchor:view.topAnchor],
[titleLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
[titleLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
[valueLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:4],
[valueLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
[valueLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
[valueLabel.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
[left.leadingAnchor constraintEqualToAnchor:row.leadingAnchor],
[left.topAnchor constraintEqualToAnchor:row.topAnchor],
[left.widthAnchor constraintEqualToConstant:120],
[right.leadingAnchor constraintEqualToAnchor:left.trailingAnchor constant:8],
[right.trailingAnchor constraintEqualToAnchor:row.trailingAnchor],
[right.topAnchor constraintEqualToAnchor:row.topAnchor],
[right.bottomAnchor constraintEqualToAnchor:row.bottomAnchor],
]];
return view;
return row;
}
#pragma mark - Helper
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (UIColor *)colorForStatus:(NSString *)status {
if (!status) return UIColor.labelColor;
if ([status isEqualToString:@"FAILED"]) {
return [UIColor systemRedColor];
}
if ([status isEqualToString:@"SUCCESS"] ||
[status isEqualToString:@"SYNCED"] ||
[status isEqualToString:@"COMPLETED"]) {
return [UIColor systemGreenColor];
}
return UIColor.labelColor;
}
@end
// PendingDetailsViewController.mm
#import "PendingDetailsViewController.h"
#import <objc/runtime.h>
#import "OfflineSyncManager.h"
@interface PendingDetailsViewController ()
static const char kZoomViewKey = 0;
@interface PendingDetailsViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
......@@ -15,21 +19,12 @@
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.numberOfLines = 0;
label.text = [NSString stringWithFormat:
@"Unit: %@\nBy: %@\nType: %@",
self.itemData[@"unitName"],
self.itemData[@"createdBy"],
self.itemData[@"type"]];
[self.view addSubview:label];
[NSLayoutConstraint activateConstraints:@[
[label.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[label.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
]];
NSLog(@"📦 Received Pending Data: %@", self.itemData);
NSString *plainText = [NSString stringWithFormat:@"%@", self.itemData];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"pendingDetail.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 pending details saved to %@", filePath);
[self setupHeader];
[self setupDetailContent];
......@@ -85,6 +80,7 @@
container.translatesAutoresizingMaskIntoConstraints = NO;
[scroll addSubview:container];
// Scroll & container constraints
[NSLayoutConstraint activateConstraints:@[
[scroll.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[scroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
......@@ -98,7 +94,6 @@
[container.widthAnchor constraintEqualToAnchor:scroll.widthAnchor],
]];
// Create vertical stack for labels
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
......@@ -112,12 +107,117 @@
[stack.bottomAnchor constraintLessThanOrEqualToAnchor:container.bottomAnchor constant:-16],
]];
// Create labels for each field
[stack addArrangedSubview:[self makeDetailLabel:@"Unit" value:self.itemData[@"unitName"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Created On" value:self.itemData[@"createdAt"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Created By" value:self.itemData[@"createdBy"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Action" value:self.itemData[@"type"]]];
[stack addArrangedSubview:[self makeDetailLabel:@"Status" value:self.itemData[@"status"] ?: @"-"]];
// Unit label
UILabel *unitLabel = [[UILabel alloc] init];
unitLabel.text = self.itemData[@"unitName"];
unitLabel.font = [UIFont boldSystemFontOfSize:22];
unitLabel.textColor = UIColor.labelColor;
unitLabel.numberOfLines = 0;
[stack addArrangedSubview:unitLabel];
// Spacer
UIView *spacer = [[UIView alloc] init];
[spacer.heightAnchor constraintEqualToConstant:12].active = YES;
[stack addArrangedSubview:spacer];
// Add all info rows
[stack addArrangedSubview:[self makeRow:@"Created Date" value:self.itemData[@"createdAt"]]];
[stack addArrangedSubview:[self makeRow:@"Created By" value:self.itemData[@"createdBy"]]];
[stack addArrangedSubview:[self makeRow:@"Action Made" value:self.itemData[@"issueDisplay"]]];
[stack addArrangedSubview:[self makeRow:@"Location" value:self.itemData[@"fieldNames"][@"locationName"] ?: @"-"]];
[stack addArrangedSubview:[self makeRow:@"Category" value:self.itemData[@"fieldNames"][@"categoryName"] ?: @"-"]];
[stack addArrangedSubview:[self makeRow:@"Type" value:self.itemData[@"fieldNames"][@"typeName"] ?: @"-"]];
[stack addArrangedSubview:[self makeRow:@"Issue" value:self.itemData[@"fieldNames"][@"issueName"] ?: @"-"]];
[stack addArrangedSubview:[self makeRow:@"Comment" value:self.itemData[@"payload"][@"comment"] ?: @"-"]];
[stack addArrangedSubview:[self makeRow:@"Timestamp" value:self.itemData[@"timestamp"] ?: @"-"]];
// Image + delete button
NSArray *imagePaths = self.itemData[@"imagePaths"];
if ([imagePaths isKindOfClass:[NSArray class]] && imagePaths.count > 0) {
NSString *path = imagePaths.firstObject;
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path lastPathComponent];
NSString *fullPath = [docsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"offline_files/%@", fileName]];
NSLog(@"📁 Loading image from %@", fullPath);
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
if (image) {
UIImageView *thumb = [[UIImageView alloc] initWithImage:image];
thumb.contentMode = UIViewContentModeScaleAspectFill;
thumb.clipsToBounds = YES;
thumb.layer.cornerRadius = 8;
thumb.userInteractionEnabled = YES;
thumb.translatesAutoresizingMaskIntoConstraints = NO;
[thumb.heightAnchor constraintEqualToConstant:120].active = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleImageTap:)];
[thumb addGestureRecognizer:tap];
thumb.accessibilityLabel = fullPath;
[stack addArrangedSubview:thumb];
} else {
NSLog(@"❌ Failed to load UIImage from path");
}
} else {
NSLog(@"❌ File does not exist at path");
}
}
// Delete button
UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeSystem];
[deleteButton setTitle:@"Delete Item" forState:UIControlStateNormal];
deleteButton.tintColor = UIColor.systemRedColor;
deleteButton.titleLabel.font = [UIFont boldSystemFontOfSize:16];
[deleteButton addTarget:self action:@selector(onDeleteItem) forControlEvents:UIControlEventTouchUpInside];
[stack addArrangedSubview:deleteButton];
}
- (UIView *)makeRow:(NSString *)title
value:(id)value {
NSString *displayValue = @"-";
if ([value isKindOfClass:[NSString class]]) {
displayValue = value;
}
else if ([value isKindOfClass:[NSNumber class]]) {
displayValue = [value stringValue];
}
else if ([value isKindOfClass:[NSNull class]] || value == nil) {
displayValue = @"-";
}
UIView *row = [[UIView alloc] init];
row.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *left = [[UILabel alloc] init];
left.text = [NSString stringWithFormat:@"%@ :", title];
left.font = [UIFont systemFontOfSize:15];
left.textColor = UIColor.secondaryLabelColor;
left.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *right = [[UILabel alloc] init];
right.text = displayValue ?: @"-";
right.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
right.textColor = UIColor.labelColor;
right.numberOfLines = 0;
right.translatesAutoresizingMaskIntoConstraints = NO;
[row addSubview:left];
[row addSubview:right];
[NSLayoutConstraint activateConstraints:@[
[left.leadingAnchor constraintEqualToAnchor:row.leadingAnchor],
[left.topAnchor constraintEqualToAnchor:row.topAnchor],
[left.widthAnchor constraintEqualToConstant:120],
[right.leadingAnchor constraintEqualToAnchor:left.trailingAnchor constant:8],
[right.trailingAnchor constraintEqualToAnchor:row.trailingAnchor],
[right.topAnchor constraintEqualToAnchor:row.topAnchor],
[right.bottomAnchor constraintEqualToAnchor:row.bottomAnchor],
]];
return row;
}
#pragma mark - Helper
......@@ -125,38 +225,85 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (UIView *)makeDetailLabel:(NSString *)title value:(NSString *)value {
UIView *view = [[UIView alloc] init];
view.translatesAutoresizingMaskIntoConstraints = NO;
- (void)handleImageTap:(UITapGestureRecognizer *)sender {
UIImageView *tappedImageView = (UIImageView *)sender.view;
if (!tappedImageView.image) return;
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textColor = UIColor.labelColor;
titleLabel.text = title;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
UIView *overlay = [[UIView alloc] initWithFrame:self.view.bounds];
overlay.backgroundColor = [UIColor colorWithWhite:0 alpha:0.95];
overlay.alpha = 0;
overlay.tag = 900;
overlay.userInteractionEnabled = YES;
[self.view addSubview:overlay];
UILabel *valueLabel = [[UILabel alloc] init];
valueLabel.font = [UIFont systemFontOfSize:16];
valueLabel.textColor = UIColor.secondaryLabelColor;
valueLabel.text = value;
valueLabel.numberOfLines = 0;
valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:overlay.bounds];
scroll.minimumZoomScale = 1.0;
scroll.maximumZoomScale = 4.0;
scroll.delegate = self;
scroll.showsHorizontalScrollIndicator = NO;
scroll.showsVerticalScrollIndicator = NO;
[overlay addSubview:scroll];
[view addSubview:titleLabel];
[view addSubview:valueLabel];
UIImageView *fullImageView = [[UIImageView alloc] initWithFrame:scroll.bounds];
fullImageView.image = tappedImageView.image;
fullImageView.contentMode = UIViewContentModeScaleAspectFit;
fullImageView.userInteractionEnabled = YES;
[scroll addSubview:fullImageView];
[NSLayoutConstraint activateConstraints:@[
[titleLabel.topAnchor constraintEqualToAnchor:view.topAnchor],
[titleLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
[titleLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
[valueLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:4],
[valueLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
[valueLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
[valueLabel.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
]];
// ✅ store reference for zooming
objc_setAssociatedObject(scroll, &kZoomViewKey, fullImageView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
UITapGestureRecognizer *dismissTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeFullImage)];
[overlay addGestureRecognizer:dismissTap];
[UIView animateWithDuration:0.25 animations:^{
overlay.alpha = 1;
}];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return (UIImageView *)objc_getAssociatedObject(scrollView, &kZoomViewKey);
}
- (void)closeFullImage {
UIView *overlay = [self.view viewWithTag:900];
if (!overlay) return;
[UIView animateWithDuration:0.25 animations:^{
overlay.alpha = 0;
} completion:^(BOOL finished) {
[overlay removeFromSuperview];
}];
}
- (void)onDeleteItem {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Delete Item"
message:@"Are you sure you want to delete this pending item?"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:@"Delete" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
[self deletePendingItem];
}]];
[self presentViewController:alert animated:YES completion:nil];
}
return view;
- (void)deletePendingItem {
NSString *itemID = self.itemData[@"id"];
if (!itemID) return;
NSMutableArray *queue = [[OfflineSyncManager shared] safeLoadQueue]; // load current queue
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"id != %@", itemID];
NSArray *filtered = [queue filteredArrayUsingPredicate:predicate];
[[OfflineSyncManager shared] saveQueue:[filtered mutableCopy]];
NSLog(@"📤 Deleted pending item %@", itemID);
// Close this screen after deletion
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
......@@ -450,7 +450,7 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
[self.syncAgainButton.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.syncAgainButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.syncAgainButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.syncAgainButton.heightAnchor constraintEqualToConstant:50],
[self.syncAgainButton.heightAnchor constraintEqualToConstant:30],
]];
}
......
......@@ -335,6 +335,7 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
self.sections[indexPath.section][@"items"][indexPath.row];
NSDictionary *raw = item[@"raw"] ?: item;
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self showDetailsForItem:raw];
}
......
......@@ -7,15 +7,20 @@ NS_ASSUME_NONNULL_BEGIN
@interface OfflineSyncManager : NSObject
+ (instancetype)shared;
- (NSMutableArray *)safeLoadQueue;
- (void)saveQueue:(NSArray *)queue;
// Queue actions
- (void)queueAddIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images;
images:(NSArray<UIImage *> *)images
fieldNames:(NSDictionary *)fieldNames;
- (void)queueUpdateIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images;
images:(NSArray<UIImage *> *)images
fieldNames:(NSDictionary *)fieldNames;
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
......@@ -40,8 +45,6 @@ NS_ASSUME_NONNULL_BEGIN
// Retry failed history item
- (void)retryHistoryItem:(NSDictionary *)historyItem;
- (void)retryAllFailedHistoryItems;
// Download Units
- (void)startDownloadUnitImagesWithProgress:(void (^)(NSInteger completed, NSInteger total))progress
completion:(void (^)(BOOL success))completion;
......
......@@ -57,22 +57,26 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
- (void)queueAddIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images {
images:(NSArray<UIImage *> *)images
fieldNames:(NSDictionary *)fieldNames {
[self enqueueAction:@"ADD_ISSUE"
payload:payload
images:images
displayInfo:display];
displayInfo:display
fieldNames:fieldNames];
}
- (void)queueUpdateIssue:(NSDictionary *)payload
display:(NSDictionary *)display
images:(NSArray<UIImage *> *)images {
images:(NSArray<UIImage *> *)images
fieldNames:(NSDictionary *)fieldNames {
[self enqueueAction:@"UPDATE_ISSUE"
payload:payload
images:images
displayInfo:display];
displayInfo:display
fieldNames:fieldNames];
}
- (void)queueAddInfo:(NSString *)issueID
......@@ -119,8 +123,9 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
- (void)enqueueAction:(NSString *)type
payload:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images
displayInfo:(NSDictionary *)displayInfo {
displayInfo:(NSDictionary *)displayInfo
fieldNames:(NSDictionary *)fieldNames
{
NSMutableArray *queue = [self safeLoadQueue];
NSArray *imagePaths = [self saveImagesToDisk:images];
......@@ -131,20 +136,22 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
item[@"imagePaths"] = imagePaths ?: @[];
item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);
// 🔥 UI fields (OG-style)
// 🔥 UI/display info
item[@"projectName"] = displayInfo[@"projectName"] ?: @"Unknown Project";
item[@"unitName"] = displayInfo[@"unitName"] ?: @"-";
item[@"createdBy"] = displayInfo[@"createdBy"] ?: @"-";
item[@"issueDisplay"] = displayInfo[@"issueDisplay"] ?: type;
item[@"createdAt"] = displayInfo[@"createdAt"] ?: [self todayDateString];
// 🔥 Add the fieldNames dictionary
item[@"fieldNames"] = fieldNames ?: @{};
[queue addObject:item];
[self saveQueue:queue];
NSLog(@"📥 [OfflineSync] Queued %@ (%lu)", type, (unsigned long)queue.count);
}
#pragma mark - Processing Queue
- (void)processNextItemWithProgress:(void (^)(NSInteger step,
NSInteger total,
......@@ -170,7 +177,6 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
if (!strongSelf) return;
NSMutableArray *currentQueue = [strongSelf safeLoadQueue];
if (currentQueue.count == 0) {
if (completion) completion(YES);
return;
......@@ -184,7 +190,9 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
progress(step, total, title);
}
[strongSelf processItem:item completion:^(BOOL success) {
NSString *taskKey = [NSString stringWithFormat:@"process_%@", item[@"id"]];
[strongSelf safeProcessItem:item taskKey:taskKey completion:^(BOOL success) {
// Save to history
NSMutableDictionary *historyItem = [item mutableCopy];
......@@ -203,110 +211,118 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[NSFileManager defaultManager] removeItemAtPath:item[@"mediaPath"] error:nil];
}
// Remove processed item
// Remove processed item from queue
NSMutableArray *updatedQueue = [strongSelf safeLoadQueue];
if (updatedQueue.count > 0) {
[updatedQueue removeObjectAtIndex:0];
[strongSelf saveQueue:updatedQueue];
}
// Move to next item
dispatch_async(dispatch_get_main_queue(), ^{
if (weakProcessNext) weakProcessNext();
});
}];
};
weakProcessNext = processNext;
processNext();
}
- (void)processItem:(NSDictionary *)item
- (void)safeProcessItem:(NSDictionary *)item
taskKey:(NSString *)taskKey
completion:(void (^)(BOOL success))completion {
NSString *type = item[@"type"] ?: @"";
__weak typeof(self) weakSelf = self;
// Load files
NSArray<UIImage *> *images = [self loadImagesFromPaths:item[@"imagePaths"]];
NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;
__block BOOL finished = NO;
// Define per-item timeout
NSTimeInterval timeout = 60.0; // seconds
__block BOOL completed = NO;
// Centralize completion + logging
void (^finalize)(BOOL) = ^(BOOL success) {
if (finished) return;
finished = YES;
__weak typeof(self) weakSelf = self;
// Log status
NSMutableDictionary *status =
[[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[taskKey] = success ? @"SUCCESS" : @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (!weakSelf || completed) return;
if (completion) completion(success);
};
NSLog(@"⚠️ [OfflineSync] Item timed out: %@", item[@"type"]);
completed = YES;
completion(NO);
// Timeout guard (in case API never returns)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (!finished) {
NSLog(@"⚠️ [OfflineSync] Timeout for item: %@", item[@"type"]);
finalize(NO);
}
});
void (^finalize)(BOOL) = ^(BOOL success){
if (!completed) {
completed = YES;
completion(success);
}
};
NSString *type = item[@"type"] ?: @"";
NSArray<UIImage *> *images = [self loadImagesFromPaths:item[@"imagePaths"]];
NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;
if ([type isEqualToString:@"ADD_INFO"]) {
if (images.count == 0 && !mediaURL) {
// Simple ADD_INFO without attachments
[APIClient submitAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
completion:^(NSDictionary *res, NSError *err) {
BOOL success = [self isAPISuccess:res error:err];
BOOL success = [weakSelf isAPISuccess:res error:err];
if (!success) NSLog(@"⚠️ ADD_INFO failed: %@", err ?: res);
finalize(success);
}];
} else {
// ADD_INFO with images/media
[APIClient uploadAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
images:images
mediaURL:mediaURL
mediaType:item[@"mediaType"]
completion:^(NSDictionary *res, NSError *err) {
BOOL success = [self isAPISuccess:res error:err];
BOOL success = [weakSelf isAPISuccess:res error:err];
if (!success) NSLog(@"⚠️ ADD_INFO upload failed: %@", err ?: res);
finalize(success);
}];
}
return;
}
else if ([type isEqualToString:@"ADD_ISSUE"]) {
if ([type isEqualToString:@"ADD_ISSUE"]) {
[APIClient submitAddIssue:item[@"payload"]
images:images
completion:^(NSDictionary *res, NSError *err){
completion:^(NSDictionary *res, NSError *err) {
[self logAddIssueDebugWithItem:item
images:images
res:res
err:err];
[weakSelf logAddIssueDebugWithItem:item images:images res:res err:err];
BOOL success = [self isAPISuccess:res error:err];
BOOL success = [weakSelf isAPISuccess:res error:err];
if (!success) NSLog(@"⚠️ ADD_ISSUE failed: %@", err ?: res);
finalize(success);
}];
return;
}
if ([type isEqualToString:@"UPDATE_ISSUE"]) {
else if ([type isEqualToString:@"UPDATE_ISSUE"]) {
[APIClient submitUpdateIssue:item[@"payload"]
images:images
completion:^(NSDictionary *res, NSError *err){
BOOL success = [self isAPISuccess:res error:err];
completion:^(NSDictionary *res, NSError *err) {
BOOL success = [weakSelf isAPISuccess:res error:err];
if (!success) NSLog(@"⚠️ UPDATE_ISSUE failed: %@", err ?: res);
finalize(success);
}];
return;
}
else {
NSLog(@"⚠️ [OfflineSync] Unknown offline type: %@", type);
// Unknown type — still finalize
NSLog(@"⚠️ [OfflineSync] Unknown type: %@", type);
finalize(NO);
return;
}
}
#pragma mark - Disk Handling
......@@ -512,6 +528,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
- (void)clearOfflineQueue {
[LocalStorage removeObjectForKey:kOfflineQueueKey];
[LocalStorage removeObjectForKey:kSyncHistoryKey];
NSLog(@"🧹 [OfflineSync] Offline queue cleared");
}
......@@ -526,81 +543,143 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSString *projectId = @"8";
NSString *drawingPlanId = @"135";
NSArray *selectedUnits = [LocalStorage loadArrayForKey:kSelectedOfflineUnitsKey] ?: @[];
NSArray *selectedUnits = [LocalStorage loadArrayForKey:kSelectedOfflineUnitsKey];
if (!selectedUnits) selectedUnits = @[];
NSMutableArray *tasks = [NSMutableArray array];
// 1️⃣ Build the sequential task queue
NSMutableArray<void (^)(void (^next)(void))> *tasks = [NSMutableArray array];
// 2️⃣ Add 6 independent API calls as tasks
// ------------------------------
// Task 1 — requestIssues
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task1" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"ISSUES_%@", drawingPlanId];
[APIClient requestIssues:^(NSDictionary *res, NSError *err) {
if (err) {
NSLog(@"⚠️ Task 1 failed: %@", err);
logSuccess(NO);
} else {
NSLog(@"✅ Task 1 succeeded");
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
next();
logSuccess(YES);
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 2 — requestDashboardInfo
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task2" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"DASHBOARD_INFO_%@", drawingPlanId];
[APIClient requestDashboardInfo:^(NSDictionary *res, NSError *err) {
if (err) {
NSLog(@"⚠️ Task 2 failed: %@", err);
logSuccess(NO);
} else {
NSLog(@"✅ Task 2 succeeded");
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
next();
logSuccess(YES);
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 3 — requestGetUnitPlan
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task3" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"PLAN_UNIT_%@", drawingPlanId];
[APIClient requestGetUnitPlan:^(NSDictionary *res, NSError *err) {
if (res) {
if (err || ![res isKindOfClass:[NSDictionary class]]) {
NSLog(@"⚠️ Task 3 failed: %@", err);
logSuccess(NO);
} else {
id dataArray = res[@"Data"];
NSDictionary *objData = @{};
if ([dataArray isKindOfClass:[NSArray class]] && [dataArray count] > 0) {
id firstItem = dataArray[0];
if ([firstItem isKindOfClass:[NSDictionary class]]) {
objData = firstItem;
}
if ([firstItem isKindOfClass:[NSDictionary class]]) objData = firstItem;
}
[LocalStorage saveDictionary:objData forKey:key];
logSuccess(YES);
}
next();
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 4 — requestGeneralSettings
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task4" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"SETTINGS_%@", projectId];
[APIClient requestGeneralSettings:^(NSDictionary *res, NSError *err) {
if (err) {
NSLog(@"⚠️ Task 4 failed: %@", err);
logSuccess(NO);
} else {
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
next();
logSuccess(YES);
NSLog(@"✅ Task 4 succeeded");
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 5 — requestCommonArea
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task5" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
NSString *key = [NSString stringWithFormat:@"COMMONAREA_%@", projectId];
[APIClient requestCommonArea:^(NSDictionary *res, NSError *err) {
if (err) {
NSLog(@"⚠️ Task 5 failed: %@", err);
logSuccess(NO);
} else {
if (res) [LocalStorage saveDictionary:res[@"Data"] forKey:key];
next();
logSuccess(YES);
NSLog(@"✅ Task 5 succeeded");
}
safeNext();
}];
} next:next];
}];
// ------------------------------
// Task 6 — requestDefectMatrix
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task6" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
[APIClient requestDefectMatrix:^(NSDictionary *res, NSError *err) {
if (!res || err) {
next();
NSLog(@"⚠️ Task 6 failed: %@", err);
logSuccess(NO);
safeNext();
return;
}
NSDictionary *data = res[@"Data"];
if (![data isKindOfClass:[NSDictionary class]]) {
next();
logSuccess(NO);
safeNext();
return;
}
// 1️⃣ Shared lists
NSArray *priority = data[@"priority"] ?: @[];
NSArray *contractor = data[@"contractor"] ?: @[];
NSArray *newIssueStatus = data[@"new_issue_status"] ?: @[];
......@@ -609,19 +688,13 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSArray *locationStatus = data[@"location_status"] ?: @[];
NSString *notMe = data[@"not_me"] ?: @"";
// 2️⃣ Defect matrix expansion
NSArray *defectMatrix = data[@"defect_matrix"];
if ([defectMatrix isKindOfClass:[NSArray class]]) {
for (NSDictionary *entry in defectMatrix) {
NSArray *locationIds = entry[@"location_id"];
if (![locationIds isKindOfClass:[NSArray class]] || locationIds.count == 0) {
continue;
}
if (![locationIds isKindOfClass:[NSArray class]] || locationIds.count == 0) continue;
NSArray *category = entry[@"category"] ?: @[];
NSDictionary *settings = @{
@"category": category,
@"priority": priority,
......@@ -633,75 +706,145 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
@"not_me": notMe
};
// 3️⃣ Save per location_id
for (id locationId in locationIds) {
NSString *locationKey =
[NSString stringWithFormat:@"LOCATION_SETTINGS_%@", locationId];
NSString *locationKey = [NSString stringWithFormat:@"LOCATION_SETTINGS_%@", locationId];
[LocalStorage saveDictionary:settings forKey:locationKey];
}
}
}
next();
logSuccess(YES);
safeNext();
}];
} next:next];
}];
// 3️⃣ Add image download task as the last task
// ------------------------------
// Task 7 — Download Images
// ------------------------------
[tasks addObject:^(void (^next)(void)) {
[self safeSequentialTask:@"task7" block:^(void (^safeNext)(void), void (^logSuccess)(BOOL)) {
if (selectedUnits.count == 0) {
next();
logSuccess(YES);
safeNext();
return;
}
[self fetchOfflineImageListForSelectedUnits:selectedUnits completion:^(NSArray<NSURL *> *urls, NSError *error) {
if (error || urls.count == 0) {
next();
logSuccess(NO);
safeNext();
return;
}
[self downloadImagesSequentially:urls progress:^(NSInteger completedImages, NSInteger totalImages) {
// Map image progress into overall progress
[self downloadImagesSequentially:urls
progress:^(NSInteger completedImages, NSInteger totalImages) {
NSInteger overallCompleted = 6 + completedImages;
NSInteger overallTotal = 6 + urls.count;
if (progress) progress(overallCompleted, overallTotal);
} next:^{
next();
logSuccess(YES);
safeNext();
}];
}];
} next:next];
}];
// 4️⃣ Sequential executor
// ------------------------------
// Sequential executor
// ------------------------------
__block NSInteger currentTaskIndex = 0;
void (^runNextTask)(void);
__block void (^runNextTask)(void) = nil;
__weak typeof(runNextTask) weakRunNextTask = nil;
runNextTask = ^{
__strong typeof(weakRunNextTask) strongRunNextTask = weakRunNextTask;
if (!strongRunNextTask) return;
if (currentTaskIndex >= tasks.count) {
[self saveLastOfflineSyncDate];
if (completion) completion(YES);
return;
}
void (^task)(void (^next)(void)) = tasks[currentTaskIndex];
void (^task)(void (^)(void)) = tasks[currentTaskIndex];
currentTaskIndex++;
// Update progress for API tasks
if (progress && currentTaskIndex <= 6) {
NSInteger overallTotal = 6 + selectedUnits.count;
progress(currentTaskIndex, overallTotal);
}
task(runNextTask);
dispatch_async(dispatch_get_main_queue(), ^{
task(strongRunNextTask);
});
};
weakRunNextTask = runNextTask;
runNextTask();
}
#pragma mark - Robust Image Download
- (void)downloadImagesSequentially:(NSArray<NSURL *> *)urls
progress:(void (^)(NSInteger completed, NSInteger total))progress
next:(void (^)(void))next {
if (urls.count == 0) {
if (next) next();
return;
}
__block NSInteger index = 0;
NSInteger total = urls.count;
__block void (^downloadNext)(void) = nil;
__weak __block void (^weakDownloadNext)(void) = nil;
downloadNext = ^{
__strong typeof(weakDownloadNext) strongDownloadNext = weakDownloadNext;
if (!strongDownloadNext) return;
if (index >= total) {
if (next) next();
return;
}
NSURL *url = urls[index];
NSString *urlKey = [NSString stringWithFormat:@"image_%ld", (long)index];
index++;
// Timeout guard
__block BOOL finished = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!finished) {
NSLog(@"⚠️ Image download timeout: %@", url);
NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
finished = YES;
if (progress) progress(index, total);
strongDownloadNext();
}
});
[APIClient downloadAndCacheImage:url completion:^(BOOL success) {
if (finished) return;
NSMutableDictionary *status = [[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy] ?: [NSMutableDictionary dictionary];
status[urlKey] = success ? @"SUCCESS" : @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
finished = YES;
if (progress) progress(index, total);
strongDownloadNext();
}];
};
weakDownloadNext = downloadNext;
downloadNext();
}
#pragma mark - Image Download Helpers
- (NSArray *)buildImageSyncPayloadFromUnits:(NSArray *)units {
NSMutableDictionary<NSString *, NSMutableArray *> *grouped = @{}.mutableCopy;
......@@ -752,37 +895,36 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
}];
}
- (void)downloadImagesSequentially:(NSArray<NSURL *> *)urls
progress:(void (^)(NSInteger completed, NSInteger total))progress
#pragma mark - Safe Sequential API Helper
- (void)safeSequentialTask:(NSString *)taskKey
block:(void (^)(void (^next)(void), void (^success)(BOOL)))block
next:(void (^)(void))next {
__block NSInteger index = 0;
NSInteger total = urls.count;
__weak typeof(self) weakSelf = self;
if (total == 0) {
// Safe next ensures we never get stuck
void (^safeNext)(void) = ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (next) next();
return;
}
});
};
__block void (^downloadNext)(void);
__weak __block void (^weakDownloadNext)(void);
// Success logger
void (^logSuccess)(BOOL) = ^(BOOL success){
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
downloadNext = ^{
if (index >= total) {
if (next) next();
return;
}
NSMutableDictionary *status =
[[LocalStorage loadDictionaryForKey:@"OFFLINE_DOWNLOAD_STATUS"] mutableCopy];
if (!status) status = [NSMutableDictionary dictionary];
NSURL *url = urls[index];
[APIClient downloadAndCacheImage:url completion:^(BOOL success) {
index++;
if (progress) progress(index, total);
if (weakDownloadNext) weakDownloadNext();
}];
status[taskKey] = success ? @"SUCCESS" : @"FAILED";
[LocalStorage saveDictionary:status forKey:@"OFFLINE_DOWNLOAD_STATUS"];
};
weakDownloadNext = downloadNext;
downloadNext();
// Execute the block with safeNext and success callback
block(safeNext, logSuccess);
}
- (void)saveLastOfflineSyncDate {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment