Commit f38dff39 authored by Wei Han's avatar Wei Han

code update

parent 6da6b554
......@@ -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 */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
92F3BF4D0A6D1113D359EFA3 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 358AE48123A5C7B504741D4F /* libPods-example.a */; };
/* 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>"; };
358AE48123A5C7B504741D4F /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
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>"; };
9240166696F5B2EC6A36F688 /* 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>"; };
950B7AF3DD3B011B04438348 /* 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>"; };
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 = (
92F3BF4D0A6D1113D359EFA3 /* libPods-example.a in Frameworks */,
1AE5006B6719358FE5D08276 /* libPods-example.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -55,7 +55,7 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
358AE48123A5C7B504741D4F /* libPods-example.a */,
0C0CBFC285E76843D753A39C /* libPods-example.a */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
9240166696F5B2EC6A36F688 /* Pods-example.debug.xcconfig */,
950B7AF3DD3B011B04438348 /* Pods-example.release.xcconfig */,
0DD693481B6504D78252E1A4 /* Pods-example.debug.xcconfig */,
49B853AE2A174A2113AE36B8 /* Pods-example.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -105,13 +105,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = (
F073E18940A3F2D5B6D5B315 /* [CP] Check Pods Manifest.lock */,
421B484D7AFF49D66547AE2C /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
9E691098E1BDE21450BE0492 /* [CP] Embed Pods Frameworks */,
9554DF7B7A44E78B79E32173 /* [CP] Copy Pods Resources */,
1FD14DE327B38E9733D3F31E /* [CP] Embed Pods Frameworks */,
DCD1F955A75F9EFAB389857C /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -183,24 +183,7 @@
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";
};
9554DF7B7A44E78B79E32173 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9E691098E1BDE21450BE0492 /* [CP] Embed Pods Frameworks */ = {
1FD14DE327B38E9733D3F31E /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -217,7 +200,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F073E18940A3F2D5B6D5B315 /* [CP] Check Pods Manifest.lock */ = {
421B484D7AFF49D66547AE2C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -239,6 +222,23 @@
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;
};
DCD1F955A75F9EFAB389857C /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
......@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9240166696F5B2EC6A36F688 /* Pods-example.debug.xcconfig */;
baseConfigurationReference = 0DD693481B6504D78252E1A4 /* Pods-example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......@@ -283,7 +283,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 950B7AF3DD3B011B04438348 /* Pods-example.release.xcconfig */;
baseConfigurationReference = 49B853AE2A174A2113AE36B8 /* Pods-example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
......
......@@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<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_x86_64-simulator</string>
<string>ios-arm64</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>
</array>
<key>CFBundlePackageType</key>
......
......@@ -18,7 +18,7 @@
</data>
<key>Info.plist</key>
<data>
AaJE9H7xzNyQkDiLPw3sM9LfFjQ=
w/Rc8R+JLx3x4AhZWV+3tG1CxCM=
</data>
<key>Modules/module.modulemap</key>
<data>
......
......@@ -18,7 +18,7 @@
</data>
<key>Info.plist</key>
<data>
RNXd5p+E7NvIdc7OKurgeIO55/s=
bbhIYcGh7MnGD7V1rEns5GGhP1g=
</data>
<key>Modules/module.modulemap</key>
<data>
......
//
// HistoryDetailsViewController.h
// QmsPluginFramework
//
// Created by Wei Han Ng on 19/12/2025.
//
#import <UIKit/UIKit.h>
@interface HistoryDetailsViewController : UIViewController
@property (nonatomic, strong) NSDictionary *itemData;
@end
//
// HistoryDetailsViewController.mm
// QmsPluginFramework
//
// Created by Wei Han Ng on 19/12/2025.
//
#import "HistoryDetailsViewController.h"
@interface HistoryDetailsViewController ()
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@end
@implementation HistoryDetailsViewController
- (void)viewDidLoad {
[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);
[self setupHeader];
}
#pragma mark - Header Setup
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerView];
self.backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"]
forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.backButton];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"History";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:8],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:32],
[self.backButton.heightAnchor constraintEqualToConstant:32],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]];
}
#pragma mark - Detail Content
- (void)setupDetailContent {
UIScrollView *scroll = [[UIScrollView alloc] init];
scroll.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scroll];
UIView *container = [[UIView alloc] init];
container.translatesAutoresizingMaskIntoConstraints = NO;
[scroll addSubview:container];
[NSLayoutConstraint activateConstraints:@[
[scroll.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[scroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[scroll.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[scroll.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[container.topAnchor constraintEqualToAnchor:scroll.topAnchor],
[container.leadingAnchor constraintEqualToAnchor:scroll.leadingAnchor],
[container.trailingAnchor constraintEqualToAnchor:scroll.trailingAnchor],
[container.bottomAnchor constraintEqualToAnchor:scroll.bottomAnchor],
[container.widthAnchor constraintEqualToAnchor:scroll.widthAnchor],
]];
// Create vertical stack for labels
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[stack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:16],
[stack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-16],
[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"] ?: @"-"]];
}
#pragma mark - Helper
- (void)onBack {
[self dismissViewControllerAnimated:YES completion: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;
UILabel *valueLabel = [[UILabel alloc] init];
valueLabel.font = [UIFont systemFontOfSize:16];
valueLabel.textColor = UIColor.secondaryLabelColor;
valueLabel.text = value;
valueLabel.numberOfLines = 0;
valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
[view addSubview:titleLabel];
[view addSubview:valueLabel];
[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],
]];
return view;
}
@end
......@@ -3,6 +3,6 @@
@interface PendingDetailsViewController : UIViewController
@property (nonatomic, strong) NSDictionary *item;
@property (nonatomic, strong) NSDictionary *itemData;
@end
// PendingDetailsViewController.mm
#import "PendingDetailsViewController.h"
@interface PendingDetailsViewController ()
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@end
@implementation PendingDetailsViewController
- (void)viewDidLoad {
......@@ -12,9 +20,9 @@
label.numberOfLines = 0;
label.text = [NSString stringWithFormat:
@"Unit: %@\nBy: %@\nType: %@",
self.item[@"unitName"],
self.item[@"createdBy"],
self.item[@"type"]];
self.itemData[@"unitName"],
self.itemData[@"createdBy"],
self.itemData[@"type"]];
[self.view addSubview:label];
......@@ -22,6 +30,133 @@
[label.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[label.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
]];
[self setupHeader];
[self setupDetailContent];
}
#pragma mark - Header Setup
- (void)setupHeader {
self.headerView = [[UIView alloc] init];
self.headerView.backgroundColor = [UIColor colorWithRed:0.0 green:0.5 blue:0.6 alpha:1.0];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerView];
self.backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"]
forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.backButton];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Pending Items";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:8],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:32],
[self.backButton.heightAnchor constraintEqualToConstant:32],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]];
}
#pragma mark - Detail Content
- (void)setupDetailContent {
UIScrollView *scroll = [[UIScrollView alloc] init];
scroll.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scroll];
UIView *container = [[UIView alloc] init];
container.translatesAutoresizingMaskIntoConstraints = NO;
[scroll addSubview:container];
[NSLayoutConstraint activateConstraints:@[
[scroll.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[scroll.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[scroll.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[scroll.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[container.topAnchor constraintEqualToAnchor:scroll.topAnchor],
[container.leadingAnchor constraintEqualToAnchor:scroll.leadingAnchor],
[container.trailingAnchor constraintEqualToAnchor:scroll.trailingAnchor],
[container.bottomAnchor constraintEqualToAnchor:scroll.bottomAnchor],
[container.widthAnchor constraintEqualToAnchor:scroll.widthAnchor],
]];
// Create vertical stack for labels
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:container.topAnchor constant:16],
[stack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:16],
[stack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-16],
[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"] ?: @"-"]];
}
#pragma mark - Helper
- (void)onBack {
[self dismissViewControllerAnimated:YES completion: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;
UILabel *valueLabel = [[UILabel alloc] init];
valueLabel.font = [UIFont systemFontOfSize:16];
valueLabel.textColor = UIColor.secondaryLabelColor;
valueLabel.text = value;
valueLabel.numberOfLines = 0;
valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
[view addSubview:titleLabel];
[view addSubview:valueLabel];
[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],
]];
return view;
}
@end
// SyncProgressOverlay.h
@interface SyncProgressOverlay : UIView
@property (nonatomic, strong) UILabel *progressLabel;
@property (nonatomic, strong) UIProgressView *progressBar;
- (void)updateStep:(NSInteger)step total:(NSInteger)total title:(NSString *)title;
@end
// SyncProgressOverlay.m
#import <UIKit/UIKit.h>
#import "SyncProgressOverlay.h"
@implementation SyncProgressOverlay
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (!self) return nil;
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 12;
card.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:card];
[NSLayoutConstraint activateConstraints:@[
[card.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
[card.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
[card.widthAnchor constraintEqualToConstant:260]
]];
self.progressLabel = [[UILabel alloc] init];
self.progressLabel.textAlignment = NSTextAlignmentCenter;
self.progressLabel.font = [UIFont systemFontOfSize:14];
self.progressLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressBar.progress = 0;
self.progressBar.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.progressLabel, self.progressBar]];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:card.topAnchor constant:20],
[stack.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:16],
[stack.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[stack.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-20],
]];
return self;
}
- (void)updateStep:(NSInteger)step total:(NSInteger)total title:(NSString *)title {
self.progressLabel.text = title ?: [NSString stringWithFormat:@"%ld / %ld", step, total];
self.progressBar.progress = total > 0 ? (float)step / (float)total : 0;
}
@end
......@@ -2,6 +2,8 @@
#import "SyncHistoryViewController.h"
#import "OfflineSyncManager.h"
#import "SyncProgressOverlay.h"
#import "HistoryDetailsViewController.h"
typedef NS_ENUM(NSInteger, HistorySyncStatus) {
HistorySyncStatusSuccess,
......@@ -45,7 +47,7 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (!self) return nil;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.selectionStyle = UITableViewCellSelectionStyleDefault;
self.backgroundColor = UIColor.clearColor; // 👈 important
self.contentView.backgroundColor = UIColor.whiteColor;
......@@ -141,6 +143,8 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
@property (nonatomic, strong) NSArray<HistorySyncItem *> *filteredItems;
@property (nonatomic, assign) NSInteger selectedTab;
@property (nonatomic, strong) SyncProgressOverlay *progressOverlay;
@end
@implementation SyncHistoryViewController
......@@ -152,11 +156,11 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
self.selectedTab = 0;
[self setupHeader];
[self setupOfflineBanner];
// [self setupOfflineBanner];
[self setupTabBar];
[self setupBottomButton];
[self setupTableView];
[self setupEmptyState];
[self setupBottomButton];
[self loadHistoryData];
[self applyFilter];
......@@ -226,32 +230,31 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
]];
}
#pragma mark - Setup Offline Banner
- (void)setupOfflineBanner {
self.offlineBanner = [[UIView alloc] init];
self.offlineBanner.backgroundColor = [UIColor colorWithRed:1.0 green:0.9 blue:0.6 alpha:1.0];
self.offlineBanner.translatesAutoresizingMaskIntoConstraints = NO;
self.offlineBanner.hidden = YES;
UILabel *label = [[UILabel alloc] init];
label.text = @"Offline Mode";
label.font = [UIFont boldSystemFontOfSize:14];
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.offlineBanner addSubview:label];
[self.view addSubview:self.offlineBanner];
[NSLayoutConstraint activateConstraints:@[
[self.offlineBanner.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
[self.offlineBanner.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.offlineBanner.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.offlineBanner.heightAnchor constraintEqualToConstant:36],
[label.centerXAnchor constraintEqualToAnchor:self.offlineBanner.centerXAnchor],
[label.centerYAnchor constraintEqualToAnchor:self.offlineBanner.centerYAnchor],
]];
}
//- (void)setupOfflineBanner {
// self.offlineBanner = [[UIView alloc] init];
// self.offlineBanner.backgroundColor = [UIColor colorWithRed:1.0 green:0.9 blue:0.6 alpha:1.0];
// self.offlineBanner.translatesAutoresizingMaskIntoConstraints = NO;
// self.offlineBanner.hidden = YES;
//
// UILabel *label = [[UILabel alloc] init];
// label.text = @"Offline Mode";
// label.font = [UIFont boldSystemFontOfSize:14];
// label.translatesAutoresizingMaskIntoConstraints = NO;
//
// [self.offlineBanner addSubview:label];
// [self.view addSubview:self.offlineBanner];
//
// [NSLayoutConstraint activateConstraints:@[
// [self.offlineBanner.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
// [self.offlineBanner.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
// [self.offlineBanner.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
// [self.offlineBanner.heightAnchor constraintEqualToConstant:36],
//
// [label.centerXAnchor constraintEqualToAnchor:self.offlineBanner.centerXAnchor],
// [label.centerYAnchor constraintEqualToAnchor:self.offlineBanner.centerYAnchor],
// ]];
//}
#pragma mark - Setup Tabbar
- (void)setupTabBar {
......@@ -315,25 +318,33 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
[self.tableView.topAnchor constraintEqualToAnchor:self.tabBarView.bottomAnchor constant:30],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-60],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.syncAgainButton.topAnchor constant:15],
]];
}
#pragma mark - Data
- (void)loadHistoryData {
#pragma mark - Progress Overlay
- (void)showProgressOverlay {
if (self.progressOverlay) return;
NSArray *sections =
[[OfflineSyncManager shared] historyItemsForUI];
self.progressOverlay =
[[SyncProgressOverlay alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.progressOverlay];
}
#pragma mark - Data
- (void)loadHistoryData {
NSArray *sections = [[OfflineSyncManager shared] historyItemsForUI];
[self dumpHistoryToFile:sections];
NSMutableArray *items = [NSMutableArray array];
for (NSDictionary *section in sections) {
// Check if the section has "items" key or is itself an item
NSArray *rows = section[@"items"];
if (!rows) rows = @[section]; // <-- fallback
for (NSDictionary *row in rows) {
HistorySyncItem *item = [HistorySyncItem new];
item.unitName = row[@"unitName"];
item.createdBy = row[@"createdBy"];
......@@ -342,18 +353,17 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
item.actionMade = row[@"type"];
NSString *status = row[@"status"];
item.status =
[status isEqualToString:@"FAILED"]
item.status = [status isEqualToString:@"FAILED"]
? HistorySyncStatusFailed
: HistorySyncStatusSuccess;
item.raw = row;
[items addObject:item];
}
}
self.allItems = items;
[self updateSyncAgainButtonState];
}
// Dummy data
......@@ -382,12 +392,14 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(@"Tapped section %ld row %ld", (long)indexPath.section, (long)indexPath.row);
UIViewController *detail = [UIViewController new];
detail.view.backgroundColor = UIColor.whiteColor;
detail.title = @"History Detail";
HistorySyncItem *selectedItem = self.filteredItems[indexPath.row];
[self.navigationController pushViewController:detail animated:YES];
HistoryDetailsViewController *vc = [HistoryDetailsViewController new];
vc.itemData = selectedItem.raw;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
- (void)setupEmptyState {
......@@ -518,19 +530,50 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
self.emptyStateView.hidden = (self.filteredItems.count > 0);
[self.tableView reloadData];
[self updateSyncAgainButtonState];
}
- (void)onSyncAgain {
BOOL hasRetryItems = NO;
for (HistorySyncItem *item in self.allItems) {
if (item.status == HistorySyncStatusFailed) {
[[OfflineSyncManager shared] retryHistoryItem:item.raw];
hasRetryItems = YES;
}
}
NSLog(@"Starting sync again");
// [[OfflineSyncManager shared] startManualSync];
if (!hasRetryItems) {
NSLog(@"No failed items to retry");
return;
}
NSLog(@"🔁 Starting sync again");
[self showProgressOverlay];
[[OfflineSyncManager shared]
processNextItemWithProgress:^(NSInteger step,
NSInteger total,
NSString *title) {
NSLog(@"🔄 Retry %ld / %ld – %@", (long)step, (long)total, title);
[self.progressOverlay updateStep:step
total:total
title:@"Retrying History Items"];
} completion:^(BOOL finished) {
NSLog(@"✅ Retry sync completed");
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressOverlay removeFromSuperview];
self.progressOverlay = nil;
[self loadHistoryData];
[self applyFilter];
});
}];
}
- (void)dumpHistoryToFile:(id)history {
......@@ -560,5 +603,19 @@ typedef NS_ENUM(NSInteger, HistorySyncStatus) {
}
}
- (void)updateSyncAgainButtonState {
BOOL hasFailedItems = NO;
for (HistorySyncItem *item in self.allItems) {
if (item.status == HistorySyncStatusFailed) {
hasFailedItems = YES;
break;
}
}
self.syncAgainButton.enabled = hasFailedItems;
self.syncAgainButton.backgroundColor =
hasFailedItems ? [UIColor systemBlueColor] : [UIColor lightGrayColor];
}
@end
......@@ -31,7 +31,7 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
if (self) {
self.backgroundColor = UIColor.clearColor;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.selectionStyle = UITableViewCellSelectionStyleGray;
self.cardView = [[UIView alloc] init];
self.cardView.backgroundColor = UIColor.whiteColor;
......@@ -41,6 +41,7 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
self.cardView.layer.shadowRadius = 4;
self.cardView.layer.shadowOffset = CGSizeMake(0, 2);
self.cardView.translatesAutoresizingMaskIntoConstraints = NO;
self.cardView.userInteractionEnabled = YES;
[self.contentView addSubview:self.cardView];
......@@ -238,6 +239,7 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
self.tableView.dataSource = self;
self.tableView.backgroundColor = UIColor.clearColor;
self.tableView.estimatedRowHeight = 120;
self.tableView.allowsSelection = YES;
[self.tableView registerClass:SyncPendingCardCell.class
forCellReuseIdentifier:@"CardCell"];
......@@ -326,8 +328,8 @@ typedef NS_ENUM(NSInteger, SyncPendingViewMode) {
return cell;
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"Tapped section %ld row %ld", (long)indexPath.section, (long)indexPath.row);
NSDictionary *item =
self.sections[indexPath.section][@"items"][indexPath.row];
......@@ -365,8 +367,9 @@ heightForHeaderInSection:(NSInteger)section {
- (void)showDetailsForItem:(NSDictionary *)item {
PendingDetailsViewController *vc = [PendingDetailsViewController new];
vc.item = item;
[self.navigationController pushViewController:vc animated:YES];
vc.itemData = item;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
- (void)onShare {
......
......@@ -7,6 +7,7 @@
#import "SyncProjectViewController.h"
#import "LocalStorage.h"
#import "OfflineSyncManager.h"
#import "SyncProgressOverlay.h"
typedef NS_ENUM(NSInteger, SyncCardType) {
SyncCardTypeOfflineUnits = 1,
......@@ -50,9 +51,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
@property (nonatomic, strong) UIButton *submitPendingToggle;
@property (nonatomic, strong) UIButton *downloadUnitsToggle;
@property (nonatomic, strong) UIView *syncProgressOverlay;
@property (nonatomic, strong) UIProgressView *progressBar;
@property (nonatomic, strong) UILabel *progressLabel;
@property (nonatomic, strong) SyncProgressOverlay *progressOverlay;
@end
......@@ -432,54 +431,14 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
#pragma mark - Progress Overlay
- (void)showProgressOverlay {
self.syncProgressOverlay = [[UIView alloc] initWithFrame:self.view.bounds];
self.syncProgressOverlay.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.4];
self.syncProgressOverlay.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.syncProgressOverlay];
if (self.progressOverlay) return;
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 12;
card.translatesAutoresizingMaskIntoConstraints = NO;
[self.syncProgressOverlay addSubview:card];
self.progressOverlay =
[[SyncProgressOverlay alloc] initWithFrame:self.view.bounds];
[NSLayoutConstraint activateConstraints:@[
[card.centerXAnchor constraintEqualToAnchor:self.syncProgressOverlay.centerXAnchor],
[card.centerYAnchor constraintEqualToAnchor:self.syncProgressOverlay.centerYAnchor],
[card.widthAnchor constraintEqualToConstant:260]
]];
self.progressLabel = [[UILabel alloc] init];
self.progressLabel.textAlignment = NSTextAlignmentCenter;
self.progressLabel.font = [UIFont systemFontOfSize:14];
self.progressLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressBar.progress = 0;
self.progressBar.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[
self.progressLabel,
self.progressBar
]];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:card.topAnchor constant:20],
[stack.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:16],
[stack.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[stack.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-20],
]];
[self.view addSubview:self.progressOverlay];
}
#pragma mark - Helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
......@@ -572,11 +531,9 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[OfflineSyncManager shared]
processNextItemWithProgress:^(NSInteger step, NSInteger total, NSString *title) {
self.progressLabel.text =
[NSString stringWithFormat:@"%ld / %ld", step, total];
self.titleLabel.text = title;
self.progressBar.progress =
total > 0 ? (float)step / (float)total : 0;
[self.progressOverlay updateStep:step
total:total
title:title];
} completion:^(BOOL finished) {
......@@ -593,12 +550,9 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[OfflineSyncManager shared]
startDownloadUnitImagesWithProgress:^(NSInteger completed, NSInteger total) {
weakSelf.progressLabel.text =
[NSString stringWithFormat:@"Downloading Units (%ld/%ld)",
(long)completed, (long)total];
weakSelf.progressBar.progress =
total > 0 ? (float)completed / (float)total : 0;
[self.progressOverlay updateStep:completed
total:total
title:@"Downloading Units"];
} completion:^(BOOL success) {
......@@ -638,8 +592,8 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
}
- (void)finishSyncWithSuccess:(BOOL)success {
[self.syncProgressOverlay removeFromSuperview];
self.syncProgressOverlay = nil;
[self.progressOverlay removeFromSuperview];
self.progressOverlay = nil;
self.isSyncing = NO;
......
......@@ -40,6 +40,8 @@ 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;
......
......@@ -160,12 +160,16 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
}
__block NSInteger step = 0;
__weak typeof(self) weakSelf = self;
void (^processNext)(void);
__block void (^processNext)(void);
__weak __block void (^weakProcessNext)(void);
processNext = ^{
NSMutableArray *currentQueue = [weakSelf safeLoadQueue];
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
NSMutableArray *currentQueue = [strongSelf safeLoadQueue];
if (currentQueue.count == 0) {
if (completion) completion(YES);
......@@ -180,18 +184,18 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
progress(step, total, title);
}
[weakSelf processItem:item completion:^(BOOL success) {
[strongSelf processItem:item completion:^(BOOL success) {
// 🔥 Save to history
// Save to history
NSMutableDictionary *historyItem = [item mutableCopy];
historyItem[@"syncedAt"] = [weakSelf todayDateString];
historyItem[@"syncedAt"] = [strongSelf todayDateString];
historyItem[@"status"] = success ? @"SUCCESS" : @"FAILED";
NSMutableArray *history = [weakSelf safeLoadHistory];
NSMutableArray *history = [strongSelf safeLoadHistory];
[history insertObject:historyItem atIndex:0];
[weakSelf saveHistoryQueue:history];
[strongSelf saveHistoryQueue:history];
// 🔥 Cleanup files
// Cleanup files
for (NSString *path in item[@"imagePaths"]) {
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
......@@ -199,20 +203,19 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[[NSFileManager defaultManager] removeItemAtPath:item[@"mediaPath"] error:nil];
}
// 🔥 Remove processed item
NSMutableArray *updatedQueue = [weakSelf safeLoadQueue];
// Remove processed item
NSMutableArray *updatedQueue = [strongSelf safeLoadQueue];
if (updatedQueue.count > 0) {
[updatedQueue removeObjectAtIndex:0];
[weakSelf saveQueue:updatedQueue];
[strongSelf saveQueue:updatedQueue];
}
// 🔁 Continue
dispatch_async(dispatch_get_main_queue(), ^{
processNext();
if (weakProcessNext) weakProcessNext();
});
}];
};
weakProcessNext = processNext;
processNext();
}
......@@ -229,12 +232,15 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
NSTimeInterval timeout = 60.0; // seconds
__block BOOL completed = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!completed) {
NSLog(@"⚠️ [OfflineSync] Item timed out: %@, marking as FAILED", item[@"type"]);
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (!weakSelf || completed) return;
NSLog(@"⚠️ [OfflineSync] Item timed out: %@", item[@"type"]);
completed = YES;
completion(NO);
}
});
void (^finalize)(BOOL) = ^(BOOL success){
......@@ -251,7 +257,8 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[APIClient submitAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
completion:^(NSDictionary *res, NSError *err) {
finalize(err == nil);
BOOL success = [self isAPISuccess:res error:err];
finalize(success);
}];
} else {
// ADD_INFO with images/media
......@@ -261,7 +268,8 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
mediaURL:mediaURL
mediaType:item[@"mediaType"]
completion:^(NSDictionary *res, NSError *err) {
finalize(err == nil);
BOOL success = [self isAPISuccess:res error:err];
finalize(success);
}];
}
return;
......@@ -271,16 +279,25 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[APIClient submitAddIssue:item[@"payload"]
images:images
completion:^(NSDictionary *res, NSError *err){
finalize(err == nil);
[self logAddIssueDebugWithItem:item
images:images
res:res
err:err];
BOOL success = [self isAPISuccess:res error:err];
finalize(success);
}];
return;
}
else if ([type isEqualToString:@"UPDATE_ISSUE"]) {
[APIClient submitUpdateIssue:item[@"payload"]
images:images
completion:^(NSDictionary *res, NSError *err){
finalize(err == nil);
BOOL success = [self isAPISuccess:res error:err];
finalize(success);
}];
return;
}
......@@ -466,8 +483,12 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
- (void)retryHistoryItem:(NSDictionary *)historyItem {
NSMutableDictionary *queueItem = [historyItem mutableCopy];
[queueItem removeObjectForKey:@"status"];
[queueItem removeObjectForKey:@"syncedAt"];
NSMutableArray *queue = [self safeLoadQueue];
[queue addObject:historyItem];
[queue addObject:queueItem];
[self saveQueue:queue];
NSMutableArray *history = [self loadHistoryQueue];
......@@ -475,7 +496,6 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[self saveHistoryQueue:history];
}
- (NSString *)formatTimestamp:(NSNumber *)ts {
NSDate *date = [NSDate dateWithTimeIntervalSince1970:ts.doubleValue];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
......@@ -773,5 +793,55 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
forKey:kLastOfflineSyncDateKey];
}
#pragma mark - Pending Items Helpers
- (BOOL)isAPISuccess:(NSDictionary *)res error:(NSError *)err {
if (err || ![res isKindOfClass:[NSDictionary class]]) {
return NO;
}
NSDictionary *appData = res[@"AppData"];
if (![appData isKindOfClass:[NSDictionary class]]) {
return NO;
}
NSString *status = appData[@"status"];
return [status isKindOfClass:[NSString class]] &&
[[status lowercaseString] isEqualToString:@"success"];
}
- (void)logAddIssueDebugWithItem:(NSDictionary *)item
images:(NSArray<UIImage *> *)images
res:(NSDictionary *)res
err:(NSError *)err {
NSLog(@"\n🚀 [ADD_ISSUE] ================================");
NSLog(@"📤 Payload: %@", item[@"payload"]);
NSLog(@"📸 Images count: %lu", (unsigned long)images.count);
NSMutableArray *imageSizes = [NSMutableArray array];
for (UIImage *img in images) {
CGSize size = img.size;
[imageSizes addObject:
[NSString stringWithFormat:@"%.0fx%.0f", size.width, size.height]];
}
NSLog(@"📸 Image sizes: %@", imageSizes);
if (err) {
NSLog(@"❌ API Error: %@", err);
} else {
NSLog(@"📥 API Response: %@", res);
}
NSDictionary *appData = res[@"AppData"];
NSLog(@"📥 AppData: %@", appData);
NSLog(@"🏁 Status evaluated as: %@",
[self isAPISuccess:res error:err] ? @"SUCCESS" : @"FAILED");
NSLog(@"============================================\n");
}
@end
......@@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<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_x86_64-simulator</string>
<string>ios-arm64</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>
</array>
<key>CFBundlePackageType</key>
......
......@@ -18,7 +18,7 @@
</data>
<key>Info.plist</key>
<data>
AaJE9H7xzNyQkDiLPw3sM9LfFjQ=
w/Rc8R+JLx3x4AhZWV+3tG1CxCM=
</data>
<key>Modules/module.modulemap</key>
<data>
......
......@@ -18,7 +18,7 @@
</data>
<key>Info.plist</key>
<data>
RNXd5p+E7NvIdc7OKurgeIO55/s=
bbhIYcGh7MnGD7V1rEns5GGhP1g=
</data>
<key>Modules/module.modulemap</key>
<data>
......
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